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

文章目录

自动配置类原理

java 复制代码
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 { }
}
java 复制代码
>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config

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

java 复制代码
    @Configuration
    @Import({Auto.class})
    static class Config {

    }

上述代码的打印结果为:

shell 复制代码
>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config
>>>com.example.auto_conf.AutoConfApplication$Auto
>>>bean1
>>>bean2

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

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

java 复制代码
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生效。

shell 复制代码
>>>org.springframework.context.annotation.ConfigurationClassPostProcessor
>>>config
>>>com.example.auto_conf.AutoConfApplication$Auto
>>>bean1
>>>bean2

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

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

xml 复制代码
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            

打印结果

shell 复制代码
>>>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 使用如下语句加载:

java 复制代码
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, null);

AopAutoConfigurartion

常用工具类

java 复制代码
 // 注册常用的后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());

示例代码

java 复制代码
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()};
        }
    }

}

打印以下结果

java 复制代码
>>>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

代码添加命令行参数

java 复制代码
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 设置了一些数据库连接信息。

java 复制代码
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 {

    }

}

打印

shell 复制代码
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 接口。

java 复制代码
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存在才生效的源码

java 复制代码
package org.springframework.boot.autoconfigure.condition;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
	...
}

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

java 复制代码
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 { }
}
相关推荐
ahauedu32 分钟前
jar命令提取 JAR 文件
java·jar
青岛少儿编程-王老师1 小时前
CCF编程能力等级认证GESP—C++1级—20250628
java·开发语言·c++
remCoding1 小时前
Java大厂面试实录:从Spring Boot到AI微服务架构的深度拷问
spring boot·spring cloud·kafka·java面试·jakarta ee·ai面试·ai微服务
lifallen2 小时前
KRaft 角色状态设计模式:从状态理解 Raft
java·数据结构·算法·设计模式·kafka·共识算法
LittleLoveBoy2 小时前
Java HashMap key为Integer时,遍历是有序还是无序?
java·开发语言
经典19922 小时前
Java 设计模式及应用场景
java·单例模式·设计模式
IguoChan2 小时前
9. Redis Operator (2) —— Sentinel部署
后端
ansurfen2 小时前
耗时一周,我的编程语言 Hulo 新增 Bash 转译和包管理工具
后端·编程语言
库森学长2 小时前
索引失效的场景有哪些?
后端·mysql·面试
oioihoii2 小时前
Visual Studio C++编译器优化等级详解:配置、原理与编码实践
java·c++·visual studio