自动配置类原理
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 { } }