Spring Boot学习笔记02--深入了解自动配置

2,098 阅读7分钟
原文链接: blog.hanqunfeng.com

摘要

看完本文你将掌握如下知识点:

  1. SpringBoot都帮我们做了哪些自动配置
  2. 我们如何接管SpringBoot的自动配置
  3. 注册Servlet、Filter、Listener的方法

SpringBoot系列Spring Boot学习笔记


SpringBoot的自动配置

1.自动配置类都存放在spring-boot-autoconfigure-1.4.2.RELEASE.jar下的
org.springframework.boot.autoconfigure路径下;
2.application.properties中配置debug=true后启动容器,可以看到服务器初始化的自动配置如下:

  • DispatcherServletAutoConfiguration
    注册org.springframework.web.servlet.DispatcherServlet
  • EmbeddedServletContainerAutoConfiguration
    注册容器类型,如类路径下存在org.apache.catalina.startup.Tomcat,就会注册Tomcat容器
  • ErrorMvcAutoConfiguration
    注册异常处理器
  • HttpEncodingAutoConfiguration
    注册http编码过滤器
  • HttpMessageConvertersAutoConfiguration
    注册json或者xml处理器
  • JacksonAutoConfiguration
    注册json对象解析器
  • JmxAutoConfiguration
    注册JMX管理器

    JMX与Spring集成
    spring通过annotation注解注册MBean到JMX实现监控java运行状态

  • MultipartAutoConfiguration
    注册文件传输处理器
  • ServerPropertiesAutoConfiguration
    用于初始化容器相关的配置属性,如服务地址、端口、contextPath,并根据当前容器类型初始化各个容器的特有属性,如tomcat的maxThreads、uriEncoding等等,其对应的属性类为ServerProperties
  • WebClientAutoConfiguration
    注册RestTemplate
  • WebMvcAutoConfiguration
    注册SpringMvc相关处理器,如ResourceResolver、RequestMappingHandlerAdapter、ExceptionHandlerExceptionResolver、ViewResolver、LocaleResolver,等等
  • WebSocketAutoConfiguration
    注册webSocket相关处理器,根据容器类型注册不同的处理器

3.如果依赖中加入了其它功能的依赖,SpringBoot还会实现这些功能的自动适配,比如我们增加数据库的JPA的功能,就会启用对JpaRepositoriesAutoConfiguration的自动配置功能。关于数据库方面的内容将在后文介绍。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>


说明
从各个AutoConfiguration配置类中可以看到如下注解,基于这些注解可以确定这些AutoConfiguration的初始化顺序:

  • @AutoConfigureOrder(-2147483648):数越小越先初始化
  • @AutoConfigureAfter({EmbeddedServletContainerAutoConfiguration.class}):在指定的配置类初始化后再加载
  • @AutoConfigureBefore({WebMvcAutoConfiguration.class}):在指定的配置类初始化前加载

接管SpringBoot的自动配置

我们介绍过@SpringBootApplication这个注解,因其包含@EnableAutoConfiguration@ComponentScan注解,可以自动扫描相关的自动配置类,从而实现自动配置功能的。
上面介绍默认情况下SpringBoot默认会初始化很多的自动配置,这些配置有些我们在项目中可能用不到,那要如何去掉呢?

去掉不需要的自动配置类

比如我们不需要开启webSocket和JMX的自动配置,我们需要在@SpringBootApplication这个注解中指定exclude属性

@SpringBootApplication(exclude = {WebSocketAutoConfiguration.class,JmxAutoConfiguration.class})
public class SpringBootWebDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebDemoApplication.class, args);
    }
}

明确指定需要启用哪些自动配置

我们可以去掉@SpringBootApplication注解,改用@Configuration、@Import、@ComponentScan注解,在@Import注解中明确指定需要启用哪些自动配置

//@SpringBootApplication(exclude = {WebSocketAutoConfiguration.class,JmxAutoConfiguration.class})
@Configuration
@Import({
        DispatcherServletAutoConfiguration.class,
        EmbeddedServletContainerAutoConfiguration.class,
        ErrorMvcAutoConfiguration.class,
        HttpEncodingAutoConfiguration.class,
        HttpMessageConvertersAutoConfiguration.class,
        JacksonAutoConfiguration.class,
        MultipartAutoConfiguration.class,
        ServerPropertiesAutoConfiguration.class,
        WebMvcAutoConfiguration.class
})
@ComponentScan
public class SpringBootWebDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebDemoApplication.class, args);
    }
}


说明:

  • 这里推荐使用第一种方式:@SpringBootApplication(exclude={})
  • 实际上,开启默认的自动配置功能,只是会影响项目启动时间,所以没有特殊需要,可以不需要关闭某个自动配置功能;
  • 在某些情况,比如项目需要多数据源时,在项目中就会包含多个DataSource的Bean,因为DataSourceAutoConfiguration自动配置只能绑定一个数据源,此时发现多个DataSource的Bean被Spring注册就会抛出异常。

    1.这时就可以采用去掉DataSourceAutoConfiguration的方式;
    2.或者也可以在某一个DataSource的Bean上声明@Primary注解,指定其为主数据源,这时DataSourceAutoConfiguration只会加载被指定@Primary注解的主数据源,这样就可以享受到SpringBoot自动配置带来的好处。


接管WebMvc自动配置

对于一个web项目,最重要的就是Mvc相关的控制,SpringBoot通过WebMvcAutoConfiguration来完成与Mvc有关的自动配置。如果希望完全接管WebMvc自动配置,可以在项目中创建一个注解了@EnableWebMvc的配置类,比如:

package com.example;
import org.apache.log4j.Logger;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.handler.SimpleServletHandlerAdapter;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.util.Properties;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example", useDefaultFilters = false, includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})
})
public class MvcConfig extends WebMvcConfigurationSupport {
    private static final Logger logger = Logger
            .getLogger(MvcConfig.class);
    /**
     * 描述 : <注册视图处理器>. <br>
     *<p>
     <使用方法说明>
     </p>
     * @return
     */
    @Bean
    public ViewResolver viewResolver() {
        logger.info("ViewResolver");
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/jsp/function/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
    /**
     * 描述 : <注册消息资源处理器>. <br>
     *<p>
     <使用方法说明>
     </p>
     * @return
     */
    @Bean
    public MessageSource messageSource() {
        logger.info("MessageSource");
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("config.messages.messages");
        return messageSource;
    }
    /**
     * 描述 : <注册servlet适配器>. <br>
     *<p>
     <只需要在自定义的servlet上用@Controller("映射路径")标注即可>
     </p>
     * @return
     */
    @Bean
    public HandlerAdapter servletHandlerAdapter(){
        logger.info("HandlerAdapter");
        return new SimpleServletHandlerAdapter();
    }
    /**
     * 描述 : <本地化拦截器>. <br>
     *<p>
     <使用方法说明>
     </p>
     * @return
     */
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor(){
        logger.info("LocaleChangeInterceptor");
        return new LocaleChangeInterceptor();
    }
    /**
     * 描述 : <基于cookie的本地化资源处理器>. <br>
     *<p>
     <使用方法说明>
     </p>
     * @return
     */
    @Bean(name="localeResolver")
    public CookieLocaleResolver cookieLocaleResolver(){
        logger.info("CookieLocaleResolver");
        return new CookieLocaleResolver();
    }
    /**
     * 描述 : <添加拦截器>. <br>
     *<p>
     <使用方法说明>
     </p>
     * @param registry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // TODO Auto-generated method stub
        logger.info("addInterceptors start");
        registry.addInterceptor(localeChangeInterceptor());
        logger.info("addInterceptors end");
    }
    /**
     * 描述 : <资源访问处理器>. <br>
     *<p>
     <可以在jsp中使用/static/**的方式访问/WEB-INF/static/下的内容>
     </p>
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        logger.info("addResourceHandlers");
        registry.addResourceHandler("/static/**").addResourceLocations("/WEB-INF/static/");
    }
    /**
     * 描述 : <文件上传处理器>. <br>
     *<p>
     <使用方法说明>
     </p>
     * @return
     */
    @Bean(name="multipartResolver")
    public CommonsMultipartResolver commonsMultipartResolver(){
        logger.info("CommonsMultipartResolver");
        return new CommonsMultipartResolver();
    }
    /**
     * 描述 : <异常处理器>. <br>
     *<p>
     <系统运行时遇到指定的异常将会跳转到指定的页面>
     </p>
     * @return
     */
    @Bean(name="exceptionResolver")
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
        logger.info("CP_SimpleMappingExceptionResolver");
        SimpleMappingExceptionResolver simpleMappingExceptionResolver= new SimpleMappingExceptionResolver();
        simpleMappingExceptionResolver.setDefaultErrorView("common_error");
        simpleMappingExceptionResolver.setExceptionAttribute("exception");
        Properties properties = new Properties();
        properties.setProperty("java.lang.RuntimeException", "common_error");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        return simpleMappingExceptionResolver;
    }
}

此时debug模式运行项目,会看到WebMvcAutoConfiguration没有被自动配置,说明我们自己定义的MvcConfig已经完全接管了默认的自动配置,这是因为WebMvcAutoConfiguration有一个条件注解:

@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

而我们本例中MvcConfig就是WebMvcConfigurationSupport的实现类,同时加入@EnableWebMvc注解也会导入一个WebMvcConfigurationSupport的实现类:DelegatingWebMvcConfiguration
,所以MvcConfig继承WebMvcConfigurationSupport不是必须的,但是可以方便我们编码。


参考:SpringMVC4零配置–Web上下文配置【MvcConfig】


如果希望可以继续使用WebMvcAutoConfiguration的自动配置,而只是需要修改或者增加MVC中的某些配置时,我们可以创建一个配置类,并继承于抽象类WebMvcConfigurerAdapter,我们可以通过实现抽象类的方法来注册自己的控制器。

public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
    public WebMvcConfigurerAdapter() {
    }
    public void configurePathMatch(PathMatchConfigurer configurer) {
    }
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }
    public void addFormatters(FormatterRegistry registry) {
    }
    public void addInterceptors(InterceptorRegistry registry) {
    }
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }
    public void addCorsMappings(CorsRegistry registry) {
    }
    public void addViewControllers(ViewControllerRegistry registry) {
    }
    public void configureViewResolvers(ViewResolverRegistry registry) {
    }
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    }
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
    }
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
    }
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
    }
    public Validator getValidator() {
        return null;
    }
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

比如我们可以增加一个视图跳转控制器,如下:

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/demo/123").setViewName("/demo");
    }
}


注册Servlet、Filter、Listener的方法

1.如果是war包项目,我们可以将Servlet、Filter、Listener注册到WebApplicationInitializer的实现类中

@Order(1)
public class CommonInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext)
            throws ServletException {
        //Log4jConfigListener
        servletContext.setInitParameter("log4jConfigLocation", "classpath:log4j.properties");
        servletContext.addListener(Log4jConfigListener.class);
        //OpenSessionInViewFilter
        OpenSessionInViewFilter hibernateSessionInViewFilter = new OpenSessionInViewFilter();
        FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(
                "hibernateFilter", hibernateSessionInViewFilter);
        filterRegistration.addMappingForUrlPatterns(
                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE), false, "/");
        //DemoServlet
        DemoServlet demoServlet = new DemoServlet();
        ServletRegistration.Dynamic dynamic = servletContext.addServlet(
                "demoServlet", demoServlet);
        dynamic.setLoadOnStartup(2);
        dynamic.addMapping("/demo_servlet");
    }
}

2.如果是jar包部署方式,则可以将其注册到任意一个@Configuration配置类中

@Configuration
public class WebConfig {
    @Bean
    public ServletRegistrationBean servletRegistrationBean_demo1(){
        return new ServletRegistrationBean(new DemoServlet(),"/demo-servlet1");
    }
    @Bean
    public ServletRegistrationBean servletRegistrationBean_demo2(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
        servletRegistrationBean.addUrlMappings("/demo-servlet2");
        servletRegistrationBean.setServlet(new DemoServlet2());
        return servletRegistrationBean;
    }
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new OpenSessionInViewFilter());
        Set<String> set = new HashSet<String>();
        set.add("/");
        filterRegistrationBean.setUrlPatterns(set);
        return filterRegistrationBean;
    }
    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean(){
        ServletListenerRegistrationBean servletListenerRegistrationBean =  new ServletListenerRegistrationBean();
        servletListenerRegistrationBean.setListener(new Log4jConfigListener());
        servletListenerRegistrationBean.addInitParameter("log4jConfigLocation","classpath:log4j.properties");
        return servletListenerRegistrationBean;
    }
}


总结

一句话概括SpringBoot的自动配置–就是一组基于条件注解实现Bean注册的Spring配置类。