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

Spring Boot以spring-boot-starter-xx命名的模块都是“开箱即用”模块,意思是说,当开发者完成依赖添加后(即pom文件的依赖添加),这个功能就会自动创建和注入到上下文中,不需要再编写麻烦的配...

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

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

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

  1. 业务类(待检测类)DemoJDBCService:通常使用Maven引入依赖,或Jar包等方式引入类。

  2. 自动配置类JDBCAutoConfiguration:判断业务类是否存在,若存在对业务类进行初始化、装载,比如完成读取参数、初始化等操作。

  3. 扫描加载自动配置类AutoConfigurationImportSelector:调用自动配置类。

业务类

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

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


1

2

3

4

5

6

7

8

9

10

11

12

     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 。


1

2

@Value("${key:value}")

private String url;

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

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

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

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


1

2

3

4

5

6

7

8

9

10

11

    @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的属性。如针对上述代码,配置中应写:


1

custom.url = localhost:3306....


初始化装载

这是最核心的地方

  1. 首先利用@Conditional等注解判断业务类是否存在。若存在则继续。

  2. @EnableConfigurationProperties注解来加载配置参数对象

  3. 编写Resolver方法,利用参数初始化装载业务Bean



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

    @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,添加上刚刚定义的自动配置类。用于运行时扫描自动加载类时使用,否则将无法加载该配置


1

2

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

    com.i3geek.springboot.demo.autoConfig.JDBCAutoConfiguration

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

扫描加载配置

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

  1. 主函数通过@EnableAutoConfiguration注解,利用其内@Import方法导入利用AutoConfigurationImportSelector类

  2. 利用AutoConfigurationImportSelector类完成spring.factories文件的扫描,从而加载配置。

@EnableAutoConfiguration



1

2

3

4

5

6

7

8

9

10

11

12

13

    @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类似。


1

2

3

4

5

6

7

8

    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方法,就是导入自动配置的地方


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

    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的导入。


1

2

3

4

5

6

    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函数测试


1

2

3

4

5

6

7

8

9

10

11

12

    @RestController

    @EnableAutoConfiguration //扫描自动配置类,并进行加载

    public class JDBCAutoConfigDemo {

        @Resource

        private DemoJDBCService jdbcService;

        @RequestMapping("/")

        String test() {

            return "url:"+ jdbcService.getUrl();

        }

        //main函数省略

    }


如未说明则本站原创,转载请注明出处:爱上极客 » Spring Boot源码分析——自动配置


  • 发表于 2018-04-18 15:51
  • 阅读 ( 406 )
  • 分类:J2EE框架

条评论

请先 登录 后评论
不写代码的码农
三叔

421 篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除