閱讀773 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《精通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/”。
screenshot

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

  上一篇:go  《精通Spring MVC 4》——1.7 錯誤與轉碼配置
  下一篇:go  《精通Spring MVC 4》——1.5 那就正式開始吧