《精通Spring MVC 4》——1.6 幕後的Spring Boot
本節書摘來自異步社區《精通Spring MVC 4》一書中的第1章,第1.6節,作者:【美】Geoffroy Warin著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看
1.6 幕後的Spring Boot
如果你之前搭建過Spring MVC應用,那麼可能已經習慣於編寫相關的XML文件或Java注解配置類。
一般來講,初始的步驟如下所示:
1.初始化Spring MVC的DispatcherServlet;
2.搭建轉碼過濾器,保證客戶端請求進行正確地轉碼;
3.搭建視圖解析器(view resolver),告訴Spring去哪裏查找視圖,以及它們是使用哪種方言編寫的(JSP、Thymeleaf模板等);
4.配置靜態資源的位置(CSS、JS);
5.配置所支持的地域以及資源bundle;
6.配置multipart解析器,保證文件上傳能夠正常工作;
7.將Tomcat或Jetty包含進來,從而能夠在Web服務器上運行我們的應用;
8.建立錯誤頁麵(如404)。
不過,Spring Boot為我們處理了所有的事情。因為這些配置一般是與應用相關的,所以你可以無限製地將它們進行組合。
在一定程度上來講,Spring Boot是帶有一定傾向性的Spring項目配置器。它基於約定,並且默認會在你的項目中使用這些約定。
1.6.1 分發器和multipart配置
接下來,讓我們看一下在幕後到底發生了什麼。
我們使用默認生成的Spring Boot配置文件,並將其設置為debug模式。在src/main/resources/ application.properties中添加下麵這一行:
debug=true
現在,如果重新啟動應用的話,就能看到Spring Boot的自動配置報告。它分為兩部分:一部分是匹配上的(positive matches),列出了應用中,所有的自動配置,另一部分是沒有匹配上的(negative matches),這部分是應用在啟動的時候,需求沒有滿足的Spring Boot自動配置:
=========================
AUTO-CONFIGURATION REPORT
=========================
Positive matches:
-----------------
DispatcherServletAutoConfiguration
- @ConditionalOnClass classes found: org.Springframework.web.
servlet.DispatcherServlet (OnClassCondition)
- found web application StandardServletEnvironment
(OnWebApplicationCondition)
EmbeddedServletContainerAutoConfiguration
- found web application StandardServletEnvironment
(OnWebApplicationCondition)
ErrorMvcAutoConfiguration
- @ConditionalOnClass classes found: javax.servlet.Servlet,org.
springframework.web.servlet.DispatcherServlet (OnClassCondition)
- found web application StandardServletEnvironment
(OnWebApplicationCondition)
HttpEncodingAutoConfiguration
- @ConditionalOnClass classes found: org.springframework.web.
filter.CharacterEncodingFilter (OnClassCondition)
- matched (OnPropertyCondition)
<Input trimmed>
仔細看一下DispatcherServletAutoConfiguration:
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Spring
* {@link DispatcherServlet}. Should work for a standalone application
where an embedded
* servlet container is already present and also for a deployable
application using
* {@link SpringBootServletInitializer}.
*
* @author Phillip Webb
* @author Dave Syer
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
/*
* The bean name for a DispatcherServlet that will be mapped to the
root URL "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME =
"dispatcherServlet";
/*
* The bean name for a ServletRegistrationBean for the
DispatcherServlet "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_
REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
protected static class DispatcherServletConfiguration {
@Autowired
private ServerProperties server;
@Autowired(required = false)
private MultipartConfigElement multipartConfig;
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_
NAME)
public ServletRegistrationBean dispatcherServletRegistration()
{
ServletRegistrationBean registration = new
ServletRegistrationBean(
dispatcherServlet(), this.server.
getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_
NAME);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_
RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver
resolver) {
// Detect if the user has created a MultipartResolver but
named it incorrectly
return resolver;
}
}
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends
SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext
context,
AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.
getBeanFactory();
ConditionOutcome outcome = checkServlets(beanFactory);
if (!outcome.isMatch()) {
return outcome;
}
return checkServletRegistrations(beanFactory);
}
}
}
這是一個典型的Spring Boot配置類。
與其他的Spring配置類相同,它使用了@Configuration注解;
一般會通過@Order注解來聲明優先等級,可以看到DispatcherServletAutoConfiguration需要優先進行配置;
其中也可以包含一些提示信息,如@AutoConfigureAfter或@AutoConfigureBefore,從而進一步細化配置處理的順序;
它還支持在特定的條件下啟用某項功能。通過使用@ConditionalOnClass (DispatcherServlet.class)這個特殊的配置,能夠確保我們的類路徑下包含DispatcherServlet,這能夠很好地表明Spring MVC位於類路徑中,用戶當前希望將其啟動起來。
這個文件中還包含了Spring MVC分發器Servlet和multipart解析器的典型配置。整個Spring MVC配置被拆分到了多個文件之中。
另外,值得一提的是,這些bean會遵循特定的規則,以此來檢查是否處於激活狀態。在@Conditional(DefaultDispatcherServletCondition.class)條件滿足的情況下,ServletRegistrationBean函數才會啟用,這有些複雜,但是能夠檢查在你的配置中,是否已經注冊了分發器Servlet。
隻有在滿足@ConditionalOnMissingBean(name=DispatcherServlet.MULTIPART_RESOLVER_ BEAN_NAME)條件的情況下,MultipartResolver函數才會處於激活狀態,例如,當我們自己還沒有注冊的時候。
這意味著Spring Boot僅僅是基於常見的使用場景,幫助我們對應用進行配置。不過,可以在任意的地方覆蓋這些默認值,並聲明自己的配置。
因此,通過查看DispatcherServletAutoConfiguration,就了解了為什麼我們已經擁有了分發器Servlet和multipart解析器。
1.6.2 視圖解析器、靜態資源以及區域配置
另外一個密切相關的配置是WebMvcAutoConfiguration,它聲明了視圖解析器、地域解析器(localeresolver)以及靜態資源的位置。視圖解析器如下所示:
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends
WebMvcConfigurerAdapter {
@Value("${spring.view.prefix:}")
private String prefix = "";
@Value("${spring.view.suffix:}")
private String suffix = "";
@Bean
@ConditionalOnMissingBean(InternalResourceViewResolver.class)
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new
InternalResourceViewResolver();
resolver.setPrefix(this.prefix);
resolver.setSuffix(this.suffix);
return resolver;
}
}
視圖解析器的配置並沒有什麼特殊之處,這裏真正有意思的是使用了配置屬性,從而允許用戶對其進行自定義。
它的意思就是說“將會在用戶的application.properties文件中查找兩個變量,這兩個變量的名字是spring.view.prefix和spring.view.suffix”。在配置中隻需兩行代碼就能將視圖解析器搭建起來了,這是非常便利的。
為了下一章內容的講解,你需要牢記這一點,不過,我們現在會繼續瀏覽Spring Boot的代碼。
關於靜態資源,配置中包含了如下的內容:
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
private static final String[] RESOURCE_LOCATIONS;
static {
RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
+ SERVLET_RESOURCE_LOCATIONS.length];
System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
0,
SERVLET_RESOURCE_LOCATIONS.length);
System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_
LOCATIONS,
SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.
length);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/
webjars/")
.setCachePeriod(cachePeriod);
}
if (!registry.hasMappingForPattern("/**")) {
registry.addResourceHandler("/**")
.addResourceLocations(RESOURCE_LOCATIONS)
.setCachePeriod(cachePeriod);
}
}
資源位置的聲明有點複雜,但是通過它,我們可以了解到以下兩點:
對帶有“webjar”前綴的資源訪問將會在類路徑中解析。這樣的話,我們就能使用Mavan中央倉庫中預先打包好的JavaScript依賴;
我們的靜態資源需要放在類路徑中,並且要位於以下4個目錄中的任意一個之中,“/META-INF/resources/”“/resources/”“/static/”或“/public/”。
WebJars是JAR包格式的客戶端JavaScript庫,可以通過Maven中央倉庫來獲取。它們包含了Maven項目文件,這個文件允許定義傳遞性依賴,能夠用於所有基於JVM的應用之中。WebJars是JavaScript包管理器的替代方案,如bower或npm。對於隻需要較少JavaScript庫的應用來說,這種方案是很棒的。你可以在www.webjars.org站點上看到所有可用的WebJars列表。
在這個文件中,還專門有一部分用來聲明地域管理:
@Bean
@ConditionalOnMissingBean(LocaleResolver.class)
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
return new FixedLocaleResolver(
StringUtils.parseLocaleString(this.mvcProperties.getLocale()));
}
默認的地域解析器隻會處理一個地域,並且允許我們通過spring.mvc.locale配置屬性來進行定義。
最後更新:2017-05-27 15:01:43