Spring的Bean相关知识及Spring Boot自动配置 约 1675 个字 401 行代码 2 张图片 预计阅读时间 11 分钟
Bean的作用域 Bean的六种作用域与演示,除了不演示websocket(比较麻烦,不过可以进一步研究一下ServletContext作用域,结合微服务)
根据官方文档 的介绍,Bean有六种作用域,分别是:singleton(单例)、prototype(原型)、request(请求)、session(会话)、application(全局)和 websocket,其中后四种需要在 Web 环境下生效
测试代码:
下面是除了websocket作用域以外的五种作用域测试结果:
Bean的生命周期 简单了解生命周期的过程
实例化:Spring 通过反射调用构造方法创建 Bean 对象,但是这个时候还没有任何的属性内容 属性注入:通过 setter 方法或字段反射把依赖的其他 Bean 塞进去 Aware 接口回调:如果 Bean 实现了 BeanNameAware、BeanFactoryAware、ApplicationContextAware 这些接口,Spring 会把对应的容器信息注入进来 BeanPostProcessor 前置处理:所有 BeanPostProcessor 的 postProcessBeforeInitialization 方法会被调用 初始化:按顺序执行 @PostConstruct 注解方法、InitializingBean 的 afterPropertiesSet 方法、@Bean 指定的initMethod BeanPostProcessor 后置处理:所有 BeanPostProcessor 的 postProcessAfterInitialization 方法会被调用,AOP 代理通常就在这一步生成 Bean 就绪:可以被其他组件使用了 销毁:容器关闭时按顺序执行 @PreDestroy 注解方法、DisposableBean 的 destroy 方法、@Bean 指定的destroyMethod 参考图如下:
Spring Boot加载非本包下的自定义配置类 概览 在Spring中,可以通过下面几种方式实现加载第三方包中的类:
启动类上使用@ComponentScan,其中的basePackage和basePackages即可配置包路径 使用@Import注解,直接导入第三方配置类的 class 对象 自定义类实现ImportSelector接口,通过Import注解导入,结合自定义注解实现快速导入 创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件并写入需要Spring帮忙扫描的包路径 创建一个第三方包和类:
Java 1
2
3
4
5
6
7
8
9
10
11
12 package org.epsda.diyconfig ;
import org.springframework.context.annotation.Bean ;
import org.springframework.context.annotation.Configuration ;
@Configuration
public class SelfConfig {
public void use () {
System . out . println ( "使用自定义配置" );
}
}
以上面的配置类为例演示上面的四种方式
@ComponentScan 方式 Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 // 使用@ComponentScan,注意观察第三方配置类所在的包和启动程序所在的包
package org.epsda.springprinciple ;
import org.epsda.diyconfig.SelfConfig ;
import org.springframework.boot.SpringApplication ;
import org.springframework.boot.autoconfigure.SpringBootApplication ;
import org.springframework.context.ApplicationContext ;
import org.springframework.context.annotation.ComponentScan ;
@ComponentScan ( "org.epsda.diyconfig" )
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main ( String [] args ) {
ApplicationContext applicationContext = SpringApplication . run ( SpringPrincipleApplication . class , args );
SelfConfig config = ( SelfConfig ) applicationContext . getBean ( "selfConfig" );
config . use ();
}
}
但是需要注意的是,上面的代码只是在@ComponentScan中配置扫描了org.epsda.diyconfig,此时原来默认扫描启动程序所在包的行为就没了,所以一般情况下都是配置当前启动程序所在的包以及第三方配置的包:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.epsda.springprinciple ;
import org.epsda.diyconfig.SelfConfig ;
import org.springframework.boot.SpringApplication ;
import org.springframework.boot.autoconfigure.SpringBootApplication ;
import org.springframework.context.ApplicationContext ;
import org.springframework.context.annotation.ComponentScan ;
// 配置启动类所在的包和第三方配置所在的包
@ComponentScan ( basePackages = {
"org.epsda.diyconfig" ,
"org.epsda.springprinciple"
})
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main ( String [] args ) {
ApplicationContext applicationContext = SpringApplication . run ( SpringPrincipleApplication . class , args );
SelfConfig config = ( SelfConfig ) applicationContext . getBean ( "selfConfig" );
config . use ();
}
}
@Import 方式 Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.epsda.springprinciple ;
import org.epsda.diyconfig.SelfConfig ;
import org.springframework.boot.SpringApplication ;
import org.springframework.boot.autoconfigure.SpringBootApplication ;
import org.springframework.context.ApplicationContext ;
import org.springframework.context.annotation.Import ;
import java.util.Arrays ;
@Import ( SelfConfig . class )
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main ( String [] args ) {
ApplicationContext applicationContext = SpringApplication . run ( SpringPrincipleApplication . class , args );
// 使用下面的代码查看类的Bean名称
// String[] beanNamesForType = applicationContext.getBeanNamesForType(SelfConfig.class);
// System.out.println(Arrays.toString(beanNamesForType));
// 需要注意,使用@Import导入,Bean的名称必须为类的全限定名
SelfConfig config = ( SelfConfig ) applicationContext . getBean ( "org.epsda.diyconfig.SelfConfig" );
config . use ();
}
}
@Import最大的缺点就是需要一个一个导入,如果有多个配置类需要导入就需要写多个@Import
实现ImportSelector 使用ImportSelector接口实现类,可以在selectImports方法中返回需要导入的配置类的全限定类名数组,从而一次性导入多个配置类。这种方式比逐个使用@Import更加灵活,也便于结合自定义注解实现批量导入
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.epsda.springprinciple ;
import org.epsda.diyconfig.SelfConfig ;
import org.epsda.diyconfig.SelfImportSelector ;
import org.springframework.boot.SpringApplication ;
import org.springframework.boot.autoconfigure.SpringBootApplication ;
import org.springframework.context.ApplicationContext ;
import org.springframework.context.annotation.Import ;
@Import ( SelfImportSelector . class ) // 此时只需要导入实现ImportSelector的类
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main ( String [] args ) {
ApplicationContext applicationContext = SpringApplication . run ( SpringPrincipleApplication . class , args );
// 需要注意,使用@Import导入,Bean的名称必须为类的全限定名
SelfConfig config = ( SelfConfig ) applicationContext . getBean ( "org.epsda.diyconfig.SelfConfig" );
config . use ();
}
}
上面的使用方式还是相对繁琐,在常规的使用中,一般会配合自定义主键进行使用,例如:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.epsda.diyconfig ;
import org.springframework.context.annotation.Import ;
import java.lang.annotation.ElementType ;
import java.lang.annotation.Retention ;
import java.lang.annotation.RetentionPolicy ;
import java.lang.annotation.Target ;
@Retention ( RetentionPolicy . RUNTIME )
@Target ( ElementType . TYPE )
@Import ( SelfImportSelector . class ) // 自定义注解引入
public @interface EnableSelfConfig {
}
接着在启动类上使用自定义注解
Java @EnableSelfConfig // 使用自定义注解
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main ( String [] args ) {
ApplicationContext applicationContext = SpringApplication . run ( SpringPrincipleApplication . class , args );
SelfConfig config = ( SelfConfig ) applicationContext . getBean ( "org.epsda.diyconfig.SelfConfig" );
config . use ();
}
}
使用Spring配置文件 在第三方配置包下的resources目录添加META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件:
文件中可以写入SelfConfig类或者SelfImportSelector类:
Java org . epsda . diyconfig . SelfImportSelector
直接启动主程序即可看到可以正常识别到自定义配置类
Spring Boot的自动配置 核心流程 自动配置的核心在于 Spring Boot 启动时,通过 @EnableAutoConfiguration 注解,利用 AutoConfigurationImportSelector 加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定义的配置类,并结合 @Conditional 系列注解进行条件过滤,最终将有条件的、符合当前环境依赖的 Bean 自动注入到 IoC 容器中
自动配置和自动装配的区别 自动装配(Autowiring) 自动配置(Auto-configuration) 解决的问题 已存在的 Bean 之间,依赖怎么注入 根据 classpath 和配置,决定要不要创建某个 Bean 属于 Spring Framework 核心(DI 的一部分) Spring Boot 独有 触发点 @Autowired / @Resource / 构造器注入 @EnableAutoConfiguration(藏在 @SpringBootApplication 里) 发生时机 每个 Bean 创建时填属性 容器启动、扫描配置阶段
源码查看 进入@EnableAutoConfiguration注解的内部,核心是@Import(AutoConfigurationImportSelector.class),它会去读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中定义的配置类,有条件地将其导入到IoC容器中
Java 1
2
3
4
5
6
7
8
9
10
11
12
13 @Target ({ ElementType . TYPE })
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Inherited
@AutoConfigurationPackage
@Import ({ AutoConfigurationImportSelector . class })
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ;
Class <?>[] exclude () default {};
String [] excludeName () default {};
}
进入AutoConfigurationImportSelector:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 public class AutoConfigurationImportSelector implements DeferredImportSelector , BeanClassLoaderAware ,
ResourceLoaderAware , BeanFactoryAware , EnvironmentAware , Ordered {
// ...
@Override
public String [] selectImports ( AnnotationMetadata annotationMetadata ) {
if ( ! isEnabled ( annotationMetadata )) {
return NO_IMPORTS ;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry ( annotationMetadata );
return StringUtils . toStringArray ( autoConfigurationEntry . getConfigurations ());
}
}
进入getAutoConfigurationEntry方法:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 protected AutoConfigurationEntry getAutoConfigurationEntry ( AnnotationMetadata annotationMetadata ) {
if ( ! isEnabled ( annotationMetadata )) {
return EMPTY_ENTRY ;
}
AnnotationAttributes attributes = getAttributes ( annotationMetadata );
List < String > configurations = getCandidateConfigurations ( annotationMetadata , attributes );
configurations = removeDuplicates ( configurations );
Set < String > exclusions = getExclusions ( annotationMetadata , attributes );
checkExcludedClasses ( configurations , exclusions );
configurations . removeAll ( exclusions );
configurations = getConfigurationClassFilter (). filter ( configurations );
fireAutoConfigurationImportEvents ( configurations , exclusions );
return new AutoConfigurationEntry ( configurations , exclusions );
}
进入getCandidateConfigurations方法,该方法会读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports和META-INF/spring.factories两个文件中定义的所有自动配置类,并将它们返回作为候选配置集合:
Java protected List < String > getCandidateConfigurations ( AnnotationMetadata metadata , AnnotationAttributes attributes ) {
ImportCandidates importCandidates = ImportCandidates . load ( this . autoConfigurationAnnotation ,
getBeanClassLoader ());
List < String > configurations = importCandidates . getCandidates ();
Assert . state ( ! CollectionUtils . isEmpty ( configurations ),
"No auto configuration classes found in " + "META-INF/spring/"
+ this . autoConfigurationAnnotation . getName () + ".imports. If you "
+ "are using a custom packaging, make sure that file is correct." );
return configurations ;
}
进入load方法:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 public static ImportCandidates load ( Class <?> annotation , ClassLoader classLoader ) {
Assert . notNull ( annotation , "'annotation' must not be null" );
ClassLoader classLoaderToUse = decideClassloader ( classLoader );
String location = String . format ( "META-INF/spring/%s.imports" , annotation . getName ());
Enumeration < URL > urls = findUrlsInClasspath ( classLoaderToUse , location );
List < String > importCandidates = new ArrayList ();
while ( urls . hasMoreElements ()) {
URL url = ( URL ) urls . nextElement ();
importCandidates . addAll ( readCandidateConfigurations ( url ));
}
return new ImportCandidates ( importCandidates );
}
自动配置的过滤与条件装配 通过getCandidateConfigurations方法获取到的自动配置类数量非常多,但并不是所有配置类都会被加载到IoC容器中。Spring Boot在getAutoConfigurationEntry方法中会通过getConfigurationClassFilter().filter(configurations)对候选配置类进行过滤,这个过滤机制会结合每个自动配置类上的@Conditional系列注解(如@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等)来判断当前环境中是否满足该配置类的生效条件,只有满足所有条件注解的配置类才会被最终导入,从而实现按需加载和有条件装配。
例如下面的测试代码:
Java @Configuration
@ConditionalOnClass ( DataSource . class ) // classpath 里有 DataSource 类才生效
@ConditionalOnMissingBean ( DataSource . class ) // 容器里没有 DataSource Bean 才生效
@ConditionalOnProperty ( prefix = "spring.datasource" , name = "url" ) // 配置文件里有这个属性才生效
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource () {
return new HikariDataSource ();
}
}
禁用自动配置的几种方式 概览 在启动类上进行排除 在配置文件中进行排除 在启动类上进行排除 Java @SpringBootApplication ( exclude = { DataSourceAutoConfiguration . class }) // 不加载DataSourceAutoConfiguration
public class MyApplication {
public static void main ( String [] args ) {
SpringApplication . run ( MyApplication . class , args );
}
}
在配置文件中进行排除 YAML spring :
autoconfigure :
exclude :
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration