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

摘要

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

  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配置类。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容