文章目录
- 自动配置类原理
- `AopAutoConfigurartion`
- 条件装配的底层原理
- `@Conditional`
- `@ConditionalOnXxx`
自动配置类原理
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
SpringBoot
中 Bean
存在才生效的源码
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 { }
}