[Springboot 源码系列] 浅析自动配置原理

简介: [Springboot 源码系列] 浅析自动配置原理


自动配置类原理

public class AutoConfApplication {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean("config", Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(">>>" + name);
        }
    }
    @Configuration
    static class Config {
    }
    static class Auto {
        @Bean
        public Bean1 bean1() {
            return new Bean1("自动配置");
        }
        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }
    @Data
    @AllArgsConstructor
    static class Bean1 {
        private String name;
    }
    static class Bean2 { }
}
>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config

可以看出 Auto没有加 @Configuration不会被 Spring容器管理,所以不会加载。此时我们进行以下改造

@Configuration
    @Import({Auto.class})
    static class Config {
    }

上述代码的打印结果为:

>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config
>>>com.example.auto_conf.AutoConfApplication$Auto
>>>bean1
>>>bean2

可以看到模拟自动配置的类正常起作用(bean1, bean2 被加载到容器)。

除了使用 @Import注解意外,可以使用导入选择器 ImportSelector,重写 selectImports() 方法,返回需要自动装配的 Bean 的全限定类名数组批量导入:

public class AutoConfApplication {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean("config", Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(">>>" + name);
        }
    }
    @Configuration
    @Import({MyImportSelector.class})
    static class Config {
    }
    static class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{Auto.class.getName()};
        }
    }
    static class Auto {
        @Bean
        public Bean1 bean1() {
            return new Bean1("自动配置");
        }
        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }
    @Data
    @AllArgsConstructor
    static class Bean1 {
        private String name;
    }
    static class Bean2 { }
}

打印 以下结果,说明 ImportSelector生效。

>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config
>>>com.example.auto_conf.AutoConfApplication$Auto
>>>bean1
>>>bean2

但这样的方式相比最初的方式并没有本质区别,甚至更麻烦,还多了一个类。如果 selectImports() 方法返回的全限定类名可以从文件中读取,就更方便了。

所以,在当前项目的类路径下创建 META-INF/spring.factories 文件,约定一个 key,对应的 value 即为需要指定装配的 Bean:

package com.example.auto_conf;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class AutoConfApplication {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean("config", Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(">>>" + name);
        }
    }
    @Configuration
    @Import({MyImportSelector.class})
    static class Config {
    }
    static class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return names.toArray(new String[0]);
        }
    }
    static class Auto {
        @Bean
        public Bean1 bean1() {
            return new Bean1("自动配置");
        }
    }
    static class Auto1 {
        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }
    @Data
    @AllArgsConstructor
    static class Bean1 {
        private String name;
    }
    static class Bean2 { }
}
// spring.factories
# 内部类使用$
com.example.auto_conf.AutoConfApplication$MyImportSelector=\
    com.example.auto_conf.AutoConfApplication.Auto, \
    com.example.auto_conf.AutoConfApplication.Auto1

打印结果

>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config
>>>com.example.auto_conf.AutoConfApplication$Auto
>>>bean1
>>>com.example.auto_conf.AutoConfApplication$Auto1
>>>bean2

SpringFactoriesLoader.loadFactoryNames() 不仅只扫描当前项目类型路径下的 META-INF/spring.factories 文件,而是会扫描包括 Jar 包里类路径下的 META-INF/spring.factories 文件。

针对 SpringBoot 来说,自动装配的 Bean 使用如下语句加载:

SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, null);

AopAutoConfigurartion

常用工具类

// 注册常用的后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

示例代码

package com.example.auto_conf;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import java.util.List;
public class AopAutoConfApplication {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        // 注册常用的后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean("config", Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(">>>" + name);
        }
    }
    @Configuration
    @Import({MyImportSelector.class})
    static class Config {
    }
    static class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{AopAutoConfiguration.class.getName()};
        }
    }
}

打印以下结果

>>>org.springframework.context.annotation.internalConfigurationAnnotationProcessor
>>>org.springframework.context.annotation.internalAutowiredAnnotationProcessor
>>>org.springframework.context.annotation.internalCommonAnnotationProcessor
>>>org.springframework.context.event.internalEventListenerProcessor
>>>org.springframework.context.event.internalEventListenerFactory
>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config
>>>com.example.auto_conf.AopAutoConfApplication

代码添加命令行参数

public static void main(String[] args) {
    GenericApplicationContext context = new GenericApplicationContext();
    StandardEnvironment env = new StandardEnvironment();
    env.getPropertySources().addLast(
        new SimpleCommandLinePropertySource("--spring.aop.auto=false")
    );
    context.setEnvironment(env);
}

AopAutoConfiguration类源码

AopAutoConfiguration```java

@AutoConfiguration

// 存在spring.aop=true 或者不存在时生效

@ConditionalOnProperty(prefix = “spring.aop”,name = {“auto”}, havingValue = “true”,matchIfMissing = true)

public class AopAutoConfiguration {

public AopAutoConfiguration() {

}

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
@ConditionalOnProperty(prefix = "spring.aop",name = {"proxy-target-class"},havingValue = "true",matchIfMissing = true
)
static class ClassProxyingConfiguration {
    ClassProxyingConfiguration() {
    }
    @Bean
    static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
        return (beanFactory) -> {
            if (beanFactory instanceof BeanDefinitionRegistry registry) {
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
        };
    }
}
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({Advice.class})
static class AspectJAutoProxyingConfiguration {
    AspectJAutoProxyingConfiguration() {
    }
    @Configuration(
        proxyBeanMethods = false
    )
    @EnableAspectJAutoProxy(
        proxyTargetClass = true
    )
    @ConditionalOnProperty(prefix = "spring.aop",name = {"proxy-target-class"},havingValue = "true",
        matchIfMissing = true
    )
    static class CglibAutoProxyConfiguration {
        CglibAutoProxyConfiguration() {
        }
    }
    @Configuration(
        proxyBeanMethods = false
    )
    @EnableAspectJAutoProxy(
        proxyTargetClass = false
    )
    @ConditionalOnProperty(prefix = "spring.aop",name = {"proxy-target-class"},havingValue = "false"
    )
    static class JdkDynamicAutoProxyConfiguration {
        JdkDynamicAutoProxyConfiguration() {
        }
    }
}

}

> 从源码中可以看出,`Spring`默认使用 `Cglib`只有 `spring.aop.proxy-target-class=false`的时候才会使用 `Jdk`代理。
## 数据库相关自动配置
代码准备
```xml
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
    </dependencies>

未使用配置文件,而是使用 StandardEnvironment 设置了一些数据库连接信息。

package com.example.auto_conf;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.type.AnnotationMetadata;
public class DbAutoConfApplication {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        StandardEnvironment env = new StandardEnvironment();
        env.getPropertySources().addLast(new SimpleCommandLinePropertySource(
            "--spring.datasource.url=jdbc:mysql://localhost:3306/advanced_spring",
            "--spring.datasource.username=root",
            "--spring.datasource.password=123456"
        ));
        context.setEnvironment(env);
        // 注册常用的后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean("config", Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
            if (resourceDescription != null){
                System.out.println(name + " 来源: \n" + resourceDescription);
                System.out.println("-----------");
            }
        }
    }
    @Configuration
    @Import({DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        MybatisAutoConfiguration.class,
        TransactionAutoConfiguration.class
    })
    static class Config {
    }
}

打印

jdbcConnectionDetailsHikariBeanPostProcessor 来源: 
org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari
-----------
dataSource 来源: 
org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari
-----------
jdbcConnectionDetails 来源: 
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$PooledDataSourceConfiguration
-----------
hikariPoolDataSourceMetadataProvider 来源: 
org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration
-----------
transactionManager 来源: 
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration
-----------
sqlSessionFactory 来源: 
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
-----------
sqlSessionTemplate 来源: 
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
-----------
org.springframework.transaction.config.internalTransactionAdvisor 来源: 
class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
-----------
transactionAttributeSource 来源: 
class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
-----------
transactionInterceptor 来源: 
class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
-----------
org.springframework.transaction.config.internalTransactionalEventListenerFactory 来源: 
class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
-----------
transactionTemplate 来源: 
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$TransactionTemplateConfiguration
-----------

可以看到 dataSource 来源: org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari

条件装配的底层原理

@Conditional

SpringBoot 的自动配置中,经常看到 @Conditional 注解的使用,使用该注解可以按条件加载配置类。

@Conditional 注解并不具备条件判断功能,而是通过指定的 Class 列表来进行判断,指定的 Class 需要实现 Condition 接口。

public class ConditionalApplication {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean("config", Config.class);
        context.refresh();
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
    @Configuration
    static class Config {
        @Bean
        @Conditional(MyConditional.class)
        public Bean1 bean1() {
            return new Bean1();
        }
        @Bean
        @Conditional(MyConditional2.class)
        public Bean2 bean2() {
            return new Bean2();
        }
    }
    static class H {}
    static class MyConditional implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return ClassUtils.isPresent("com.example.auto_conf.ConditionalApplication.H", null);
        }
    }
    static class MyConditional2 implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return !ClassUtils.isPresent("com.example.auto_conf.ConditionalApplication.H", null);
        }
    }
    static class Bean1 { }
    static class Bean2 { }
}

输出: 把 H类注释掉时,bean2注册, 否则 bean1注册。

@ConditionalOnXxx

SpringBootBean存在才生效的源码

package org.springframework.boot.autoconfigure.condition;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
  ...
}

模仿 SpringBoot 的组合注解进行条件注册 bean

public class ConditionalApplication {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean("config", Config.class);
        context.refresh();
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
    @Configuration
    static class Config {
        @Bean
        @ConditionalOnClass(className = "com.example.auto_conf.ConditionalApplication.H")
        public Bean1 bean1() {
            return new Bean1();
        }
        @Bean
        @ConditionalOnClass(exist = false, className = "com.example.auto_conf.ConditionalApplication.H")
        public Bean2 bean2() {
            return new Bean2();
        }
    }
    static class H {}
    static class MyConditional implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnClass.class.getName());
            boolean exist = (boolean) attributes.get("exist");
            String className = attributes.get("className").toString();
            boolean present = ClassUtils.isPresent(className, null);
            return exist == present;
        }
    }
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Conditional(MyConditional.class)
    @interface ConditionalOnClass {
        boolean exist() default true;
        String className();
    }
    static class Bean1 { }
    static class Bean2 { }
}


相关文章
|
2天前
|
IDE Java Maven
SpringBoot自定义starter及自动配置
SpringBoot自定义starter及自动配置
|
2天前
|
存储 Java 数据库
Spring Boot中如何配置和使用多数据源
Spring Boot中如何配置和使用多数据源
|
2天前
|
Java UED Spring
Spring Boot中的国际化配置
Spring Boot中的国际化配置
|
2天前
|
监控 Java 开发者
Spring Boot中的热部署配置
Spring Boot中的热部署配置
|
2天前
|
Java API Spring
Spring Boot中配置Swagger用于API文档
Spring Boot中配置Swagger用于API文档
|
2天前
|
缓存 监控 Java
Spring Boot中的缓存配置与优化
Spring Boot中的缓存配置与优化
|
2天前
|
监控 IDE Java
探索 IntelliJ IDEA 中 Spring Boot 运行配置选项及其作用
探索 IntelliJ IDEA 中 Spring Boot 运行配置选项及其作用
5 0
|
2天前
|
开发框架 Java 开发者
Spring Boot中的自动装配原理
Spring Boot中的自动装配原理
|
2天前
|
Java 数据管理 关系型数据库
Spring Boot中实现多数据源配置
Spring Boot中实现多数据源配置
|
2天前
|
Java
SpringBoot起步依赖原理分析
SpringBoot起步依赖原理分析