i3geek.com
闫庚哲的个人博客

Spring Boot源码分析——自动配置

Spring Boot以spring-boot-starter-xx命名的模块都是“开箱即用”模块,意思是说,当开发者完成依赖添加后(即pom文件的依赖添加),这个功能就会自动创建和注入到上下文中,不需要再编写麻烦的配置,只需要提供参数属性即可。十分的方便,那么如此巧妙的开箱即用是怎么实现的呢?

本文将探索其中的奥秘,笔者模拟编写自动装载JDBC的过程:

为了方便学习,将自动配置分为三部分进行理解:

  1. 业务类(待检测类)DemoJDBCService:通常使用Maven引入依赖,或Jar包等方式引入类。
  2. 自动配置类JDBCAutoConfiguration:判断业务类是否存在,若存在对业务类进行初始化、装载,比如完成读取参数、初始化等操作。
  3. 扫描加载自动配置类AutoConfigurationImportSelector:调用自动配置类。

业务类

自动配置的触发点,被检测的核心业务类,若该类存在才会进行自动配置,实现“开箱即用”的功能,否则不执行配置。

例子中,采用最简单的业务,代码如下:

     public class DemoJDBCService {
        public String url;
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public void connect(){
        	//链接操作
        }
    }

注意:这个类不能被component-scan扫描到。该bean应该由自动配置进行装载,如果被扫描到顺序不同,不符合逻辑会产生错误。(这其实是不可避免的“缺陷”,读者可以尝试在自己的项目中扫描 org.spring 会发现项目无法启动)

通俗点理解,假设本类中还依赖JDBC的相关库,如果没有加载相关库,被component-scan扫描到必然会报错NoClassDefFoundError。如果是自动装载类加载,装载类会先判断是否存在相关库,存在再加载 这时就不会报错。

自动配置类

自动配置分为三步:

(1)从application.properties中读取参数;

(2)完成对业务类Bean的初始化、装载;

(3)配置spring.factories

读取参数

方法一:

Spring 提供了一个注解用于导入配置文件中的数据 — @Value 。

@Value("${key:value}")
private String url;

参数的key代表properties中的key,value是默认值,即若不存在该key时的取值。

注意:直接填写会当做字符串处理,如果想设null应填写#{null}

方法二:(本例采用该方法)

创建属性参数类JDBCProperties,用来读取并管理参数

    @ConfigurationProperties(prefix = "custom")//属性前缀
    public class JDBCProperties {
        public static final String DEFAULT_URL = "localhost";
        public String url = DEFAULT_URL;
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
    }

在属性参数文件application.properties 中,读取前缀为custom的属性。如针对上述代码,配置中应写:

custom.url = localhost:3306....

初始化装载

这是最核心的地方

  1. 首先利用@Conditional等注解判断业务类是否存在。若存在则继续。
  2. @EnableConfigurationProperties注解来加载配置参数对象
  3. 编写Resolver方法,利用参数初始化装载业务Bean
    @Configuration
    @ConditionalOnClass({ DemoJDBCService.class }) //判断业务类是否存在
    @EnableConfigurationProperties(JDBCProperties.class) //加载配置参数对象
    public class JDBCAutoConfiguration {
        @Resource
        private JDBCProperties jdbcProperties;
        //开始装载Bean
        @Bean
        @ConditionalOnMissingBean(DemoJDBCService.class)
        @ConditionalOnProperty(name = "custom.url.enabled", matchIfMissing = true) //配置中加个属性,灵活控制开关
        public DemoJDBCService jdbcResolver() {
            DemoJDBCService jdbcService = new DemoJDBCService();
            jdbcService.setUrl(jdbcProperties.getUrl());
            return jdbcService;
        }
    }

spring.factories

配置spring.factories,添加上刚刚定义的自动配置类。用于运行时扫描自动加载类时使用,否则将无法加载该配置

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.i3geek.springboot.demo.autoConfig.JDBCAutoConfiguration

这里是采用系统的自动装载扫描类EnableAutoConfiguration,也可以用户自行编写。(具体后面会详细介绍)

扫描加载配置

自动扫描的过程可以自行编写,本例中采用spring中自带的流程进行源码讲解。

  1. 主函数通过@EnableAutoConfiguration注解,利用其内@Import方法导入利用AutoConfigurationImportSelector类
  2. 利用AutoConfigurationImportSelector类完成spring.factories文件的扫描,从而加载配置。

@EnableAutoConfiguration

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class}) //关键
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }

@Import , 这个注解可以导入一个配置类到另一个配置类中。在 Spring4.2 中对这个注解进行了加强,可以直接将一个类加入Spring容器。那么只要写上 @Import(AutoConfigurationImportSelector.class) 即可。

AutoConfigurationImportSelector类

该类是实现与ImportSelector接口,该接口作用与注解 @Import类似。

    public interface ImportSelector {
    
        /**
         * Select and return the names of which class(es) should be imported based on
         * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
         */
        String[] selectImports(AnnotationMetadata importingClassMetadata);
    }

其中核心方法selectImports,根据 importingClassMetadata 的值,从带有注解 @Configuration 的类中选择并返回合适的类名数组,将其导入 Spring 容器。因此,查看AutoConfigurationImportSelector类中的ImportSelector方法,就是导入自动配置的地方

    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 configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//关键,获得类名列表
            configurations = this.removeDuplicates(configurations);//去除重复
            Set 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);
        }
    }

可见本方法中,主要是getCandidateConfigurations 方法获取类名,之后经过一些处理,把名返回完成spring的导入。

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());//从META-INF/spring.factories中获取
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

getCandidateConfigurations方法中通过 SpringFactoriesLoader.loadFactoryNames 扫描 spring.factories 文件获得类名。这里就不再深入看了。

Main函数测试

    @RestController
    @EnableAutoConfiguration //扫描自动配置类,并进行加载
    public class JDBCAutoConfigDemo {
        @Resource
        private DemoJDBCService jdbcService;
 
        @RequestMapping("/")
        String test() {
            return "url:"+ jdbcService.getUrl();
        }
        //main函数省略
    }
赞(1)
未经允许不得转载:爱上极客 » Spring Boot源码分析——自动配置
分享到: 更多 (0)

相关推荐

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址