假设有两个外部的第三方配置类:
static class OuterBean1{}
static class OuterBean2{}
/**
* 外部配置类1
*/
static class OuterConfig1{
public OuterBean1 outerBean1(){
return new OuterBean1();
}
}
/**
* 外部配置类2
*/
static class OuterConfig2{
public OuterBean2 outerBean2(){
return new OuterBean2();
}
}
需要在项目中导入这两个配置类,使用它们定义的 Bean。最简单的方式是:
/**
* 本项目的配置类
*/
OuterConfig1.class, OuterConfig2.class})
({static class Config{}
ImportSelector
如果需要导入的外部配置类很多,这样做就不现实,可以使用一个ImportSelector
类指定需要导入的外部类:
static class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 返回需要导入的类的名称
return new String[]{
OuterConfig1.class.getName(),
OuterConfig2.class.getName()
};
}
}
在配置类上直接导入 ImportSelector
类:
MyImportSelector.class)
(static class Config {
}
从配置读取
之前的 ImportSelector
是以硬编码的方式将要导入的类名返回,实际处理中需要使用配置文件保存要导入的类,配置文件的名称和位置都是固定的:
配置文件的内容:
com.example.demo.ImportTests3$MyImportSelector=com.example.demo.ImportTests3.OuterConfig1,com.example.demo.ImportTests3.OuterConfig2
属性名称是类名,值使用,
分隔。
为了方便阅读,可以使用\
换行:
com.example.demo.ImportTests3$MyImportSelector=\
com.example.demo.ImportTests3.OuterConfig1,\
com.example.demo.ImportTests3.OuterConfig2
注意,这里的
MyImportSelector
是一个内部类,因此是$MyImportSelector
而非.MyImportSelector
。
修改ImportSelector
,从配置文件中读取要导入的类名:
static class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 从配置文件读取需要导入的类
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, this.getClass().getClassLoader());
// 返回需要导入的类的名称
return classNames.toArray(new String[0]);
}
}
Bean 定义覆盖
有这么一个类型:
static class SameBean{
private String source;
}
外部第三方配置类和项目自己的配置类都添加了该类型的 Bean:
@Configuration
static class OuterConfig1 {
//...
@Bean
public SameBean sameBean(){
return new SameBean("第三方");
}
}
@Configuration
@Import(MyImportSelector.class)
static class Config {
@Bean
public SameBean sameBean(){
return new SameBean("本项目");
}
}
最终容器中产生的是 Bean 使用的是本项目的 Bean 定义。原因是容器在加载本地配置类Config
的时候,先通过@Import(MyImportSelector.class)
加载外部配置类中的 Bean 定义,再通过本地配置类的 Bean 方法添加 Bean 定义。而对于同一个类型的 Bean,后添加的 Bean 定义覆盖了先添加的。
容器可以设置是否允许 Bean 定义覆盖,Spring Boot 不允许:
ctx.setAllowBeanDefinitionOverriding(false);
此时会报错:
报错信息显示同类型的 Bean 定义已经被外部配置类 OulterConfig1
绑定过了,不能重复定义。
现在类定义不能覆盖了,就需要修改类定义的加载顺序,必须优先加载本项目的类定义:
static class MyImportSelector implements DeferredImportSelector {
// ...
}
改为使用ImportSelector
的子接口即可,它将延迟加载通过@Import
方式导入的外部配置类。
为了解决报错问题,可以在外部配置类的 Bean 方法上添加条件,只有相应的 Bean 定义不存在时才添加:
@Configuration
public static class OuterConfig1 {
// ...
@Bean
@ConditionalOnMissingBean
public SameBean sameBean(){
return new SameBean("第三方");
}
}
常见的自动配置类
AopAutoConfiguration
加载外部配置类AopAutoConfiguration
:
@Configuration
@Import(MyImportSelector.class)
static class Config{}
static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AopAutoConfiguration.class.getName()};
}
}
测试:
GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBean(Config.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(ctx);
ctx.refresh();
for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
ctx.close();
从打印可以看到,通过AopAutoConfiguration
加载了这么几个 Bean 定义:
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration forceAutoProxyCreatorToUseClassProxying org.springframework.boot.autoconfigure.aop.AopAutoConfiguration org.springframework.aop.config.internalAutoProxyCreator
查看类定义:
@AutoConfiguration
@ConditionalOnBooleanProperty(name = "spring.aop.auto", matchIfMissing = true)
public class AopAutoConfiguration {
// ...
}
@ConditionalOnBooleanProperty
注解的用途是当存在属性spring.aop.auto
,且值为 true
,或者属性不存在时配置类生效。
通过编程的方式为容器的环境添加属性:
GenericApplicationContext ctx = new GenericApplicationContext();
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addLast(new MapPropertySource("mapPropertySource",
Map.of("spring.aop.auto","false")));
ctx.setEnvironment(environment);
再次执行就发现已经没有 AopAutoConfiguration
添加的类定义了。
AopAutoConfiguration
中有两个内部类:
@ConditionalOnClass
注解表示当(类路径)存在Advice
类时,加载AspectJAutoProxyingConfiguration
,@ConditionalOnMissingClass
注解表示当不存在Advice
类时,加载ClassProxyingConfiguration
。
可以通过依赖引入 Advice
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
此时加载AspectJAutoProxyingConfiguration
,它里边又包含两个内部类:
当存在属性spring.aop.proxy-target-class
且值为false
时,JdkDynamicAutoProxyConfiguration
生效,否则CglibAutoProxyConfiguration
生效。
无论哪个内部类生效,其目的都是加载@EnableAspectJAutoProxy
注解。该注解的用途是导入AspectJAutoProxyRegistrar
:
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
这个类的用途是用编程的方式添加 Bean 定义:
查看源码就会发现,最终会添加一个org.springframework.aop.config.internalAutoProxyCreator
类型的 Bean 定义。
完整示例见。
DataSourceAutoConfiguration
自动配置类DataSourceAutoConfiguration
的用途是添加数据源,通过配置类加载:
@Configuration
@Import(MyImportSelector.class)
static class Config{}
static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
DataSourceAutoConfiguration.class.getName()
};
}
}
测试:
GenericApplicationContext context = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
context.registerBean(Config.class);
context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
context.close();
没有任何数据源自动配置相关的 Bean 定义输出,自动配置没有生效,原因是这个配置类有一个条件注解:
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
EmbeddedDatabaseType
类是 JDBC 的,因此需要为项目添加 JDBC 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
这也符合一般常识,数据源是建立在 JDBC 之上的。
Spring 的自动配置类支持这几种数据源:
当缺少自定义DataSource
Bean,且引入了相应数据源依赖,就会触发相应的自动配置:
这里使用 Hikari,添加其依赖:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>6.3.0</version>
</dependency>
再次运行,报错,表示缺少数据库驱动信息,需要添加数据库驱动依赖:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
DataSourceAutoConfiguration
还有一个@EnableConfigurationProperties(DataSourceProperties.class)
注解,该注解的用途是创建DataSourceProperties
Bean,并从环境信息中读取数据源(数据库)相关的信息作为其属性。
创建数据源时通过DataSourceProperties
获取数据库信息:
因此还需要在容器环境中加入数据库相关属性:
GenericApplicationContext context = new GenericApplicationContext();
ConfigurableEnvironment environment = context.getEnvironment();
environment.getPropertySources().addLast(new MapPropertySource("dataSource", Map.of(
"spring.datasource.url", "jdbc:mysql://localhost:3306/test",
"spring.datasource.username", "root",
"spring.datasource.password", "mysql"
)));
MybatisAutoConfiguration
添加 Mybatis 依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
Mybatis 内包含 Hikari 数据源依赖。
导入 Mybatis 的自动配置类:
static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
DataSourceAutoConfiguration.class.getName(),
MybatisAutoConfiguration.class.getName()
};
}
}
MybatisAutoConfiguration
定义:
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
// ...
}
注解的作用:
-
@ConditionalOnSingleCandidate
,当前容器中存在单个DataSource
Bean 作为候选时可触发。 -
@EnableConfigurationProperties
,在容器中添加 BeanMybatisProperties
,并且绑定属性。 -
@AutoConfigureAfter
,在DataSourceAutoConfiguration
自动配置类之后生效
其中比较重要的 Bean:
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// ...
}
@ConditionalOnMissingBean
注解决定了,如果容器中没有其他的SqlSessionFactory
Bean 定义,就会添加自动配置类中的 SqlSessionFactory
Bean 定义。
还有一个:
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
// ...
}
同样使用了SqlSessionTemplate
,在缺少 Bean 定义时添加。
其中还包含一个内部配置类:
@Import({AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
// ...
}
会在缺少MapperFactoryBean
和MapperScannerConfigurer
类型的 Bean 时通过@Import
加载默认的 Mapper 扫描实现AutoConfiguredMapperScannerRegistrar
。
默认的包扫描实现使用的包名需要设置:
AutoConfigurationPackages.register(context, this.getClass().getPackageName());
context.refresh();
之所以普通的 Spring Boot 项目不需要设置,是因为入口类的@SpringBootApplication
注解包含了@EnableAutoConfiguration
注解,而该注解又包含了@AutoConfigurationPackage
注解,@AutoConfigurationPackage
注解的用途就是将入口类所在的包注册为自动配置类可以使用的基础包名。
DataSourceTransactionManagerAutoConfiguration
自动配置类DataSourceTransactionManagerAutoConfiguration
会在缺少TransactionManager
时创建:
MVC 自动配置
添加 MVC 相关自动配置类:
static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
ServletWebServerFactoryAutoConfiguration.class.getName(),
DispatcherServletAutoConfiguration.class.getName(),
WebMvcAutoConfiguration.class.getName(),
ErrorMvcAutoConfiguration.class.getName()
};
}
}
这些自动配置类的用途:
-
ServletWebServerFactoryAutoConfiguration,添加 ServletWebServerFactory
-
DispatcherServletAutoConfiguration,添加 DispatcherServlet 和 DispatcherServletRegistrationBean
-
WebMvcAutoConfiguration,添加各种 HandlerMapping 和 HandlerAdapter
-
ErrorMvcAutoConfiguration,添加错误处理的相关 bean
Spring Boot 自动配置
新版的 Spring Boot 的 spring.factories
中并不包含自动配置类,而是通过这两个文件加载:
需要加载的自动配置类信息保存在AutoCOnfiguration.imports
文件中:
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration org.springframework.boot.autoconfigure.aop.AopAutoConfiguration org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
是否加载的条件在spring-autoconfigure-metadata.properties
文件中说明:
org.springframework.boot.autoconfigure.AutoConfiguration= org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration= org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration= org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration=
比如RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org...EnableRabbit
,就意味着只有加载了EnableRabbit
这个类(注解),才会加载自动配置类RabbitAnnotationDrivenConfiguration
。起作用等同于在自动配置类上使用@ConditionOnClass
注解。
这样做的好处是,传统使用spring.factories
文件加载自动配置类,需要先加载所有自动配置类的字节码,再通过自动配置类上的条件注解判断需要加载哪个不需要加载哪个,现在将加载条件由文件描述,直接读取文件就可以只加载需要的类,避免了对无效类的字节码的不必要加载。
自定义自动配置类
实际上 Spring Boot 的自动配置生效是因为@EnableAutoConfiguration
注解:
该注解导入了一个ImportSelector
:
这个 ImportSelector
从配置文件中读取自动配置类:
底层方法会从WEB-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
读取自动配置类。换言之,我们只需要将希望被 SpringBoot 加载的自动配置类添加到这个文件中就可以了。
com.example.demo.EnableAutoConfigTests$OuterConfig1 com.example.demo.EnableAutoConfigTests$OuterConfig2
此外,还可以使用依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
在编译时自动生成spring-autoconfigure-metadata.properties
以优化自动配置类的加载。
本文完整示例可以从获取。
The End.
文章评论