• SpringBoot自动装配原理解析


    本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等

    我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序:

    @SpringBootApplication
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    

    和我们之前使用普通Spring时繁琐的配置相比简直不要太方便,那么你知道SpringBoot实现这些的原理么

    首先我们看到类上方包含了一个@SpringBootApplication注解

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(
        excludeFilters = {@Filter(
        type = FilterType.CUSTOM,
        classes = {TypeExcludeFilter.class}
    ), @Filter(
        type = FilterType.CUSTOM,
        classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
    public @interface SpringBootApplication {
        @AliasFor(
            annotation = EnableAutoConfiguration.class
        )
        Class<?>[] exclude() default {};
    
        @AliasFor(
            annotation = EnableAutoConfiguration.class
        )
        String[] excludeName() default {};
    
        @AliasFor(
            annotation = ComponentScan.class,
            attribute = "basePackages"
        )
        String[] scanBasePackages() default {};
    
        @AliasFor(
            annotation = ComponentScan.class,
            attribute = "basePackageClasses"
        )
        Class<?>[] scanBasePackageClasses() default {};
    }
    

    这个注解上边包含的东西还是比较多的,咱们先看一下两个简单的热热身

    @ComponentScan 注解

    @ComponentScan(excludeFilters = {
    		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    

    这个注解咱们都是比较熟悉的,无非就是自动扫描并加载符合条件的Bean到容器中,这个注解会默认扫描声明类所在的包开始扫描,例如:
    cn.shiyujun.Demo类上标注了@ComponentScan 注解,则cn.shiyujun.controllercn.shiyujun.service等等包下的类都可以被扫描到

    这个注解一共包含以下几个属性:

    basePackages:指定多个包名进行扫描
    basePackageClasses:对指定的类和接口所属的包进行扫
    excludeFilters:指定不扫描的过滤器
    includeFilters:指定扫描的过滤器
    lazyInit:是否对注册扫描的bean设置为懒加载
    nameGenerator:为扫描到的bean自动命名
    resourcePattern:控制可用于扫描的类文件
    scopedProxy:指定代理是否应该被扫描
    scopeResolver:指定扫描bean的范围
    useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行检测
    

    @SpringBootConfiguration注解

    这个注解更简单了,它只是对Configuration注解的一个封装而已

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    }
    

    EnableAutoConfiguration注解

    这个注解可是重头戏了,SpringBoot号称的约定大于配置,也就是本文的重点自动装配的原理就在这里了

    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    

    简单概括一下,这个注解存在的意义就是:利用@Import注解,将所有符合自动装配条件的bean注入到IOC容器中,关于@Import注解原理这里就不再阐述,感兴趣的同学可以参考此篇文章:Spring @Import注解源码解析

    进入类AutoConfigurationImportSelector,观察其selectImports方法,这个方法执行完毕后,Spring会把这个方法返回的类的全限定名数组里的所有的类都注入到IOC容器中

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!this.isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            } else {
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                configurations = this.removeDuplicates(configurations);
                Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
                configurations.removeAll(exclusions);
                configurations = this.filter(configurations, autoConfigurationMetadata);
                this.fireAutoConfigurationImportEvents(configurations, exclusions);
                return StringUtils.toStringArray(configurations);
            }
        }
    

    观察上方代码:

    1. 第一行if时会首先判断当前系统是否禁用了自动装配的功能,判断的代码如下:
     protected boolean isEnabled(AnnotationMetadata metadata) {
            return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
        }
    
    1. 如果当前系统禁用了自动装配的功能则会返回如下这个空的数组,后续也就无法注入bean了
    private static final String[] NO_IMPORTS = new String[0];
    
    1. 此时如果没有禁用自动装配则进入else分枝,第一步操作首先会去加载所有Spring预先定义的配置条件信息,这些配置信息在org.springframework.boot.autoconfigure包下的META-INF/spring-autoconfigure-metadata.properties文件中

    2. 这些配置条件主要含义大致是这样的:如果你要自动装配某个类的话,你觉得先存在哪些类或者哪些配置文件等等条件,这些条件的判断主要是利用了@ConditionalXXX注解,关于@ConditionalXXX系列注解可以参考这篇文章:SpringBoot条件注解@Conditional

    3. 这个文件里的内容格式是这样的:

    ```
    

    org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
    org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,io.micrometer.core.instrument.MeterRegistry
    org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration

     3. 具体的加载代码就不列出了,无法就是个读取配置文件
     4. 这里放个加载之后的结果图:
     ![file](https://img2018.cnblogs.com/blog/1431571/201909/1431571-20190920090843541-1514005316.jpg)
    4. 获取`@EnableAutoConfiguration`注解上的exclude、excludeName属性,这两个属性的作用都是排除一些类的
    5. 这里又是关键的一步,可以看到刚才图片中spring-autoconfigure-metadata.properties文件的上方存在一个文件spring.factories,这个文件可就不止存在于`org.springframework.boot.autoconfigure`包里了,所有的包里都有可能存在这个文件,所以这一步是加载整个项目所有的spring.factories文件。这个文件的格式是这样的
    

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration

    6. 这里存在一个知识点,SpringBoot中的star就是依靠这个文件完成的,假如我们需要自定义一个SpringBoot的Star,就可以在我们的项目的META-INF文件夹下新建一个spring.factories文件
    

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.shiyujun.TestAutoConfiguration

    这样当别的项目依赖我们的项目时就会自动把我们的`TestAutoConfiguration`类注入到Spring容器中
    7. 删除重复的自动配置类
    8. 下面三行就是去除我们指定排除的配置类
    9. 接着这一行的逻辑稍微复杂一些,主要就是根据加载的配置条件信息来判断各个配置类上的`@ConditionalXXX`系列注解是否满足需求
    10. 最后就是发布自动装配完成事件,然后返回所有能够自动装配的类的全限定名
    
    
    到了这里我们已经把SpringBoot自动装配的原理搞清楚了,但是总感觉差点什么,那我们从这些自动装配的类里面挑一个我们比较熟悉的关于Servlet的类来看看咋回事吧:
    

    @Configuration
    @ConditionalOnWebApplication(
    type = Type.SERVLET
    )
    public class ServletEndpointManagementContextConfiguration {
    public ServletEndpointManagementContextConfiguration() {
    }

    @Bean
    public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(WebEndpointProperties properties) {
        Exposure exposure = properties.getExposure();
        return new ExposeExcludePropertyEndpointFilter(ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude(), new String[0]);
    }
    
    @Configuration
    @ConditionalOnClass({ResourceConfig.class})
    @ConditionalOnMissingClass({"org.springframework.web.servlet.DispatcherServlet"})
    public class JerseyServletEndpointManagementContextConfiguration {
        public JerseyServletEndpointManagementContextConfiguration() {
        }
    
        @Bean
        public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
            return new ServletEndpointRegistrar(properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
        }
    }
    
    @Configuration
    @ConditionalOnClass({DispatcherServlet.class})
    public class WebMvcServletEndpointManagementContextConfiguration {
        private final ApplicationContext context;
    
        public WebMvcServletEndpointManagementContextConfiguration(ApplicationContext context) {
            this.context = context;
        }
    
        @Bean
        public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
            DispatcherServletPathProvider servletPathProvider = (DispatcherServletPathProvider)this.context.getBean(DispatcherServletPathProvider.class);
            String servletPath = servletPathProvider.getServletPath();
            if (servletPath.equals("/")) {
                servletPath = "";
            }
    
            return new ServletEndpointRegistrar(servletPath + properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
        }
    }
    

    }

    自上而下观察整个类的代码,你会发现这些自动装配的套路都是一样的
    1. 如果当前是Servlet环境则装配这个bean
    2. 当存在类`ResourceConfig`以及不存在类`DispatcherServlet`时装配`JerseyServletEndpointManagementContextConfiguration`
    3. 当存在`DispatcherServlet`类时装配`WebMvcServletEndpointManagementContextConfiguration`
    4. 接下来如果还有面试官问你,你会了么?
  • 相关阅读:
    标准maven配置setting文件
    zxing生成和解析二维码工具类
    Postman上传文件
    sql server工具类
    springboot开启token校验一直报错No 'Access-Control-Allow-Origin' header is present on the requested resource
    Jpa分页查询
    Restful接口调用统一异常处理
    npm install --save 、--save-dev 、-D、-S 的区别与NODE_ENV的配置(转载)
    element-ui el-table 组件实现跨表格多选
    微信小程序设置页面背景色的方式(全局或单页面)
  • 原文地址:https://www.cnblogs.com/zhixiang-org-cn/p/11553850.html
Copyright © 2020-2023  润新知