注解bean
我們常用的向spring容器中添加bean的方式主要有三種
- @Component注解
- @Configuration 加 @bean
- @Import
那么spring是如何解析這些注解的,本文具體研究這個(gè)問(wèn)題
parse
spring解析注解bean的代碼寫(xiě)在ConfigurationClassParser類的parse方法,參數(shù)就是SpringApplication.run時(shí)傳入的啟動(dòng)主類
spring啟動(dòng)時(shí)會(huì)解析我們的主配置類(就是帶@SpringBootApplication的啟動(dòng)類),解析的任務(wù)交給解析器ConfigurationClassParser,對(duì)應(yīng)的方法就是parse
ConfigurationClassParser中一個(gè)屬性configurationClasses,用來(lái)存放解析出來(lái)的結(jié)果
private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();
對(duì)應(yīng)的get方法
public Set<ConfigurationClass> getConfigurationClasses() {
return this.configurationClasses.keySet();
}
processConfigurationClass
parse方法最終會(huì)走向processConfigurationClass方法

這個(gè)方法貼主要代碼
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
調(diào)用doProcessConfigurationClass方法,如果有返回值,遞歸調(diào)用doProcessConfigurationClass,看spring的注釋說(shuō)// Recursively process the configuration class and its superclass hierarchy.,也就是遞歸解析配置類和他的父類,所以這代碼的意思就是解析配置類,如果有父類再解析父類,如果父類有父類再一直解析下去。
所以重點(diǎn)就來(lái)到了doProcessConfigurationClass方法
doProcessConfigurationClass
doProcessConfigurationClass(spring源碼中的doXXX一般都很重要),接下來(lái)就分析這個(gè)代碼,貼完整代碼
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 1.內(nèi)部類 這一步看看有沒(méi)有內(nèi)部類,一般不咋用
processMemberClasses(configClass, sourceClass, filter);
}
// 2.@PropertySource 這一步解析@PropertySource注解,更改配置文件位置時(shí)會(huì)使用,一般使用默認(rèn)位置,不咋更改
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 3.@ComponentScan 這一步就很重要了,解析@ComponentScan注解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// 開(kāi)始掃描,把@ComponentScan指定包下的@Component類全部掃描出來(lái)
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 把所有掃描到的beanClass遞歸解析,所以我們也可以加多個(gè)@Configuration類
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
//判斷是不是ConfigurationClass 帶@Configuration注解和@Component注解都算
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 解析
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// 4.@Import 解析@Import注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 5.@ImportResource解析@ImportResource解析
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 6.@Bean 解析帶有@Bean注解的方法,加入到configClass的beanMethods屬性中
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 6.接口@Bean 解析實(shí)現(xiàn)的接口中帶有@Bean注解的默認(rèn)方法,加入到configClass的beanMethods屬性中
processInterfaces(configClass, sourceClass);
// 7.父類 如果有父類返回父類,以繼續(xù)解析
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// 沒(méi)有父類,解析結(jié)束
return null;
}
整個(gè)流程總結(jié)如下
- 解析內(nèi)部類
- 解析@PropertySource注解
- 解析@ComponentScan注解,掃描指定包下的所有@Component類,并遞歸解析
- 解析@Import注解
- 解析@ImportResource注解
- 解析@Bean
- 解析實(shí)現(xiàn)接口中的@Bean
- 返回父類
接下來(lái)一個(gè)個(gè)看
解析內(nèi)部類
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 1.內(nèi)部類 這一步看看有沒(méi)有內(nèi)部類,一般不咋用
processMemberClasses(configClass, sourceClass, filter);
}
也就是說(shuō)如果一個(gè)類有@Component注解,會(huì)解析他的內(nèi)部類如果也有@Component會(huì)注冊(cè)成bean,寫(xiě)個(gè)代碼測(cè)試一下
@Configuration
@ComponentScan("com.pqsir.parser")
public class ClassParserApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ClassParserApplication.class);
Nested nested = context.getBean(Nested.class);
System.out.println(nested); // com.pqsir.parser.ClassParserApplication$Nested@4e7912d8
}
@Component
class Nested {
}
}
所以內(nèi)部類也可以注冊(cè)到bean容器
解析@PropertySource注解
這個(gè)真沒(méi)用過(guò),我覺(jué)得配置文件放在規(guī)定的地就不錯(cuò),以后也好找,這個(gè)就不研究了
解析@ComponentScan
這個(gè)都懂,就是掃描的包路徑,值的注意的是掃描到的class都會(huì)遞歸調(diào)用parser
這一步的代碼細(xì)分析下
- 掃描包下的類
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
這個(gè)componentScanParser內(nèi)部有個(gè)scaner(掃描器),掃描@Component注解的類(包括子注解@Configuration,@Service等)
- 循環(huán)判斷掃描到的類是否是ConfigurationClass,如果是則遞歸解析
// 如果是ConfigurationClass
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 解析
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
這一步ConfigurationClass不單單是指帶@Configuration注解的類,帶@Component注解的也算ConfigurationClass,spring內(nèi)部有個(gè)集合,只要是這個(gè)集合里的注解,都算ConfigurationClass
// ConfigurationClassUtils
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// 省略
// 是否有候選注解
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// 省略
}
總結(jié)【1】帶@Configuration或@Component類都是ConfigurationClass
最后做個(gè)小測(cè)試--主類制定了@ComponentScan包下又一個(gè)帶@ComponentScan的類指向另一個(gè)包,那么這兩個(gè)包下的bean都會(huì)被注入,測(cè)試一下,我們建兩個(gè)包parser和parser2
parser2下一個(gè)普通bean:BeanOut
package com.pqsir.parser2;
@Component
public class BeanOut {
@Override
public String toString() {
return "BeanOut";
}
}
parser下ClassParserApplication主類掃描parser和 OtherConfiguration:在parser包下定義掃描parser2
package com.pqsir.parser
@Configuration
@ComponentScan("com.pqsir.parser") //掃描parser
public class ClassParserApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ClassParserApplication.class);
BeanOut bean = context.getBean(BeanOut.class);
System.out.println(bean); // BeanOut
}
}
@Configuration
@ComponentScan("com.pqsir.parser2")
public class OtherConfiguration {
}
最終正常輸出"BeanOut"
解析@Import注解
這個(gè)也比較好理解,就是如果某個(gè)bean帶@Import注解,就把@Import注解指定的類也注冊(cè)成bean
測(cè)試一下,我們把上一步BeanOut的@Component注解去掉,刪除OtherConfiguration
@Configuration
@ComponentScan("com.pqsir.parser")
@Import({BeanOut.class})
public class ClassParserApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ClassParserApplication.class);
BeanOut bean = context.getBean(BeanOut.class);
System.out.println(bean); // BeanOut
}
}
結(jié)果也可以正常輸出
這個(gè)@Import的最大好處可以把一些第三方的類給注入到bean容器,因?yàn)榈谌降念愐话阋哺牟涣丝偛荒苋ゼ?code>@Component注解吧,而且如果你是一個(gè)第三方開(kāi)發(fā)者,肯定不希望一直的工具依賴spring(萬(wàn)一哪天沒(méi)人用了你的工具也廢了),所以通過(guò)@Import把你的工具引入spring是一個(gè)完美的解決方案。
還有個(gè)比較大的好處是可以做封裝,@Import注解可以被繼承,比如我們寫(xiě)個(gè)自定義注解繼承了@Import,并指定import的類,就可以把這個(gè)類注冊(cè)到bean容器中,甚至可以讓這個(gè)類繼承一些后置處理器來(lái)給bean容器做調(diào)整,比如AOP的@EnableAspectJAutoProxy就是用到這一點(diǎn),還有mybaits的也是用@MapperScan也是使用Import的方式完成一些mapper bean的生成工作
上例是@Configuration+@Import,用@Component+@Import也ok,因?yàn)椤?】
解析@ImportResource注解
主要為了兼容之前的xml寫(xiě)法
解析@Bean
@Bean注解一般經(jīng)常使用,使用工廠方法創(chuàng)建一個(gè)bean,一般就是@Configuration+@Bean,由于上述原因【1】,所以@Component+@Bean也可以。
解析實(shí)現(xiàn)接口中的@Bean
這是對(duì)@Bean注解的一個(gè)擴(kuò)展,解析實(shí)現(xiàn)的接口中帶有@Bean注解的默認(rèn)方法,寫(xiě)個(gè)例子測(cè)試一下
- 接口(包含默認(rèn)方法帶@Bean注解,本身不帶任何注解)
public interface IBeanA {
@Bean
default BeanOut beanOut() {
return new BeanOut();
}
}
- 實(shí)現(xiàn)(帶@Component注解)
@Component
//@Import({BeanOut.class})
public class BeanA implements IBeanA {
}
- 測(cè)試類
@Configuration
@ComponentScan("com.pqsir.parser")
public class ClassParserApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ClassParserApplication.class);
BeanOut bean = context.getBean(BeanOut.class);
System.out.println(bean); // BeanOut
}
}
也會(huì)正常輸出,這個(gè)真沒(méi)想到什么使用場(chǎng)景,遇到再說(shuō)吧
返回父類
最后一步如果返回父類繼續(xù)遞歸解析,測(cè)試一下
- 父類(沒(méi)有@Component注解)
public class BeanFather {
@Override
public String toString() {
return "BeanFather";
}
}
- 子類(有@Component注解)
@Component
public class BeanSon extends BeanFather{
}
- 測(cè)試類(嘗試獲取父類bean)
@Configuration
@ComponentScan("com.pqsir.parser")
public class ClassParserApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ClassParserApplication.class);
BeanFather bean = context.getBean(BeanFather.class);
System.out.println(bean); // BeanFather
}
}
正??色@取
最后
完成這一系列的解析掃描再解析過(guò)程,就可以通過(guò)getConfigurationClasses拿到所有掃描并解析到的類。
spring拿到這些類之后再通過(guò)一個(gè)reader把ConfigurationClasse轉(zhuǎn)換為bean定義,注冊(cè)到beanFactory,所以parser+reader,就完成了這些bean的掃描&解析&注冊(cè)工作,代碼在ConfigurationClassPostProcessor中。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 創(chuàng)建一個(gè)解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 開(kāi)始解析,這個(gè)candidates就是我們傳入的MainApplication
parser.parse(candidates);
// 獲取解析到的類
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
// 初始化一個(gè)reader
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
// 把上面解析到的類轉(zhuǎn)化為bean定義并注冊(cè)到bean定義注冊(cè)器(registry)
this.reader.loadBeanDefinitions(configClasses);
}
擴(kuò)展
@Configuration和@Component的區(qū)別
上文很多@Configuration的注解都可以用@Component代替,甚至主類使用@Component來(lái)替換@Configuration也能正常跑,那問(wèn)題來(lái)了,他倆就沒(méi)有區(qū)別嗎,那要@Configuration有啥用。
卻別主要兩方面
一.首先,@Configuration和@Service,@Controller注解很像,都繼承@Component注解,沒(méi)有實(shí)際的什么作用只是告訴別人這個(gè)類是個(gè)配置類型的bean
二.其次,也是實(shí)際功能上的區(qū)別,使用@Configuration類+@Bean,sping會(huì)生成一個(gè)cglib動(dòng)態(tài)代理,這個(gè)代理的工能就是第一次調(diào)用@Bean的方法會(huì)直接執(zhí)行并返回結(jié)果,同時(shí)存儲(chǔ)結(jié)果,下一次調(diào)用同樣方法直接返回結(jié)果,這樣可以保證單例,不管調(diào)用多少次@Bean的方法最終得到的結(jié)果是同一個(gè)對(duì)象,而使用@Component則不會(huì)
做個(gè)測(cè)試
通過(guò)@Bean注冊(cè)三個(gè)bean A B C,其中BC依賴注入A,先使用@Configuration
@Configuration
public class BeanConfiguration {
@Bean
public A a() {
return new A();
}
@Bean
public B b() {
B b = new B();
b.a = a();
return b;
}
@Bean
public C c() {
C c = new C();
c.a = a();
return c;
}
static class A {
}
static class B {
public A a;
}
static class C {
public A a;
}
}
試一下
ApplicationContext context = new AnnotationConfigApplicationContext(ClassParserApplication.class);
BeanConfiguration.B b = context.getBean(BeanConfiguration.B.class);
BeanConfiguration.C c = context.getBean(BeanConfiguration.C.class);
System.out.println(b.a.equals(c.a));
輸出結(jié)果是true
如果把@Configuration改成@Component,輸出結(jié)果就變成false了,這個(gè)代理的代碼ConfigurationClassPostProcessor.enhanceConfigurationClasses中,有興趣的自己研究吧
其實(shí)這種寫(xiě)法本來(lái)就不太好,還是覺(jué)得用依賴注入更好,如下
@Configuration
public class BeanConfiguration {
@Bean
public A a() {
return new A();
}
@Bean
public B b(A a) {
B b = new B();
b.a = a;
return b;
}
@Bean
public C c(A a) {
C c = new C();
c.a = a;
return c;
}
static class A {
}
static class B {
public A a;
}
static class C {
public A a;
}
}
這種寫(xiě)法就算改成@Component也沒(méi)問(wèn)題
@Import+ImportBeanDefinitionRegistrar
上面說(shuō)了@Import可以導(dǎo)入bean,一個(gè)或多個(gè)bean,但如果比如把一個(gè)包下的所有類都注入到bean,它就不能實(shí)現(xiàn)了,除非你一個(gè)一個(gè)寫(xiě),但是這樣新增一個(gè)就得寫(xiě)一個(gè)。
這種需求其實(shí)很常見(jiàn),比如mybaits的@MapperScan,他需要你指定一個(gè)mapper的位置,然后把mapper全部注入到bean容器,不管你加多少mapper都會(huì)注入進(jìn)去。
spring批量注冊(cè)bean是有個(gè)后置處理器支持的,就是BeanDefinitionRegistryPostProcessor,如果你某個(gè)bean實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor,就會(huì)拿到bean定義注冊(cè)器BeanDefinitionRegistry,然后愛(ài)怎么注冊(cè)bean定義、注冊(cè)多少隨你。
所以我最開(kāi)始遇到這種需求解決思路是@Import+BeanDefinitionRegistryPostProcessor,雖然可行,但獲取不到注解的屬性,比如@MapperScan的value
所以spring要引入ImportBeanDefinitionRegistrar這個(gè)接口,其中重要方法
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
通過(guò)實(shí)現(xiàn)這個(gè)方法也能拿到BeanDefinitionRegistry(ConfigurationClassPostProcessor本身繼承BeanDefinitionRegistryPostProcessor,所以可以拿到),然后可以按照自己的意思注冊(cè)bean定義,更重要的:通過(guò)第一個(gè)參數(shù)importingClassMetadata可以獲取使用@Import注解的類的元數(shù)據(jù),就可以獲取到外層注解的屬性值
那么問(wèn)題來(lái)了,什么時(shí)候執(zhí)行吶。
剛才"最后"章節(jié)的代碼有一句this.reader.loadBeanDefinitions(configClasses);,就是在這個(gè)時(shí)候繼承這個(gè)接口的類(被@Import引入)執(zhí)行registerBeanDefinitions方法

可以自己去找代碼,整個(gè)過(guò)程再ConfigurationClassPostProcessor執(zhí)行的生命周期執(zhí)行完畢