最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

spring掃描解析bean的方法詳解

 更新時間:2026年05月09日 08:34:24   作者:pq217  
我們常用的向spring容器中添加bean的方式主要有三種,主要是@Component注解,@Configuration 加 @bean和@Import,那么spring是如何解析這些注解的呢,下面小編就和大家詳細介紹一下吧

注解bean

我們常用的向spring容器中添加bean的方式主要有三種

  • @Component注解
  • @Configuration 加 @bean
  • @Import

那么spring是如何解析這些注解的,本文具體研究這個問題

parse

spring解析注解bean的代碼寫在ConfigurationClassParser類的parse方法,參數(shù)就是SpringApplication.run時傳入的啟動主類

spring啟動時會解析我們的主配置類(就是帶@SpringBootApplication的啟動類),解析的任務交給解析器ConfigurationClassParser,對應的方法就是parse

ConfigurationClassParser中一個屬性configurationClasses,用來存放解析出來的結果

private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();

對應的get方法

public Set<ConfigurationClass> getConfigurationClasses() {
	return this.configurationClasses.keySet();
}

processConfigurationClass

parse方法最終會走向processConfigurationClass方法

這個方法貼主要代碼

do {
	sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);

調(diào)用doProcessConfigurationClass方法,如果有返回值,遞歸調(diào)用doProcessConfigurationClass,看spring的注釋說// Recursively process the configuration class and its superclass hierarchy.,也就是遞歸解析配置類和他的父類,所以這代碼的意思就是解析配置類,如果有父類再解析父類,如果父類有父類再一直解析下去。 所以重點就來到了doProcessConfigurationClass方法

doProcessConfigurationClass

doProcessConfigurationClass(spring源碼中的doXXX一般都很重要),接下來就分析這個代碼,貼完整代碼

protected final SourceClass doProcessConfigurationClass(
		ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
		throws IOException {
	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
		// 1.內(nèi)部類  這一步看看有沒有內(nèi)部類,一般不咋用
		processMemberClasses(configClass, sourceClass, filter);
	}

	// 2.@PropertySource 這一步解析@PropertySource注解,更改配置文件位置時會使用,一般使用默認位置,不咋更改
	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) {
			// 開始掃描,把@ComponentScan指定包下的@Component類全部掃描出來
			Set<BeanDefinitionHolder> scannedBeanDefinitions =
					this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
			// 把所有掃描到的beanClass遞歸解析,所以我們也可以加多個@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 解析實現(xiàn)的接口中帶有@Bean注解的默認方法,加入到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();
		}
	}

	// 沒有父類,解析結束
	return null;
}

整個流程總結如下

  • 解析內(nèi)部類
  • 解析@PropertySource注解
  • 解析@ComponentScan注解,掃描指定包下的所有@Component類,并遞歸解析
  • 解析@Import注解
  • 解析@ImportResource注解
  • 解析@Bean
  • 解析實現(xiàn)接口中的@Bean
  • 返回父類

接下來一個個看

解析內(nèi)部類

if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
	// 1.內(nèi)部類  這一步看看有沒有內(nèi)部類,一般不咋用
	processMemberClasses(configClass, sourceClass, filter);
}

也就是說如果一個類有@Component注解,會解析他的內(nèi)部類如果也有@Component會注冊成bean,寫個代碼測試一下

@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)部類也可以注冊到bean容器

解析@PropertySource注解

這個真沒用過,我覺得配置文件放在規(guī)定的地就不錯,以后也好找,這個就不研究了

解析@ComponentScan

這個都懂,就是掃描的包路徑,值的注意的是掃描到的class都會遞歸調(diào)用parser 這一步的代碼細分析下

掃描包下的類

Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

這個componentScanParser內(nèi)部有個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)部有個集合,只要是這個集合里的注解,都算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;
		}
	}
	// 省略
}

總結**【1】帶@Configuration或@Component類都是ConfigurationClass**

最后做個小測試--主類制定了@ComponentScan包下又一個帶@ComponentScan的類指向另一個包,那么這兩個包下的bean都會被注入,測試一下,我們建兩個包parserparser2 parser2下一個普通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注解

這個也比較好理解,就是如果某個bean帶@Import注解,就把@Import注解指定的類也注冊成bean 測試一下,我們把上一步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
	}
}

結果也可以正常輸出 這個@Import的最大好處可以把一些第三方的類給注入到bean容器,因為第三方的類一般也改不了總不能去加@Component注解吧,而且如果你是一個第三方開發(fā)者,肯定不希望一直的工具依賴spring(萬一哪天沒人用了你的工具也廢了),所以通過@Import把你的工具引入spring是一個完美的解決方案。 還有個比較大的好處是可以做封裝,@Import注解可以被繼承,比如我們寫個自定義注解繼承了@Import,并指定import的類,就可以把這個類注冊到bean容器中,甚至可以讓這個類繼承一些后置處理器來給bean容器做調(diào)整,比如AOP的@EnableAspectJAutoProxy就是用到這一點,還有mybaits的也是用@MapperScan也是使用Import的方式完成一些mapper bean的生成工作 上例是@Configuration+@Import,用@Component+@Import也ok,因為【1】

解析@ImportResource注解

主要為了兼容之前的xml寫法

解析@Bean

@Bean注解一般經(jīng)常使用,使用工廠方法創(chuàng)建一個bean,一般就是@Configuration+@Bean,由于上述原因【1】,所以@Component+@Bean也可以。

解析實現(xiàn)接口中的@Bean

這是對@Bean注解的一個擴展,解析實現(xiàn)的接口中帶有@Bean注解的默認方法,寫個例子測試一下

接口(包含默認方法帶@Bean注解,本身不帶任何注解)

public interface IBeanA {
	@Bean
	default BeanOut beanOut() {
		return new BeanOut();
	}
}

實現(xiàn)(帶@Component注解)

@Component
//@Import({BeanOut.class})
public class BeanA implements IBeanA {

}

測試類

@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
	}
}

也會正常輸出,這個真沒想到什么使用場景,遇到再說吧

返回父類

最后一步如果返回父類繼續(xù)遞歸解析,測試一下

父類(沒有@Component注解)

public class BeanFather {
	@Override
	public String toString() {
		return "BeanFather";
	}
}

子類(有@Component注解)

@Component
public class BeanSon extends BeanFather{
}

測試類(嘗試獲取父類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 
	}
}

正常可獲取

最后

完成這一系列的解析掃描再解析過程,就可以通過getConfigurationClasses拿到所有掃描并解析到的類。 spring拿到這些類之后再通過一個readerConfigurationClasse轉(zhuǎn)換為bean定義,注冊到beanFactory,所以parser+reader,就完成了這些bean的掃描&解析&注冊工作,代碼在ConfigurationClassPostProcessor中。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 創(chuàng)建一個解析器
    ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    // 開始解析,這個candidates就是我們傳入的MainApplication
    parser.parse(candidates);
    // 獲取解析到的類
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    // 初始化一個reader
    this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
    // 把上面解析到的類轉(zhuǎn)化為bean定義并注冊到bean定義注冊器(registry)
    this.reader.loadBeanDefinitions(configClasses);
}

擴展

@Configuration和@Component的區(qū)別

上文很多@Configuration的注解都可以用@Component代替,甚至主類使用@Component來替換@Configuration也能正常跑,那問題來了,他倆就沒有區(qū)別嗎,那要@Configuration有啥用。 卻別主要兩方面 一.首先,@Configuration和@Service,@Controller注解很像,都繼承@Component注解,沒有實際的什么作用只是告訴別人這個類是個配置類型的bean 二.其次,也是實際功能上的區(qū)別,使用@Configuration類+@Bean,sping會生成一個cglib動態(tài)代理,這個代理的工能就是第一次調(diào)用@Bean的方法會直接執(zhí)行并返回結果,同時存儲結果,下一次調(diào)用同樣方法直接返回結果,這樣可以保證單例,不管調(diào)用多少次@Bean的方法最終得到的結果是同一個對象,而使用@Component則不會

做個測試

通過@Bean注冊三個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));

輸出結果是true 如果把@Configuration改成@Component,輸出結果就變成false了,這個代理的代碼ConfigurationClassPostProcessor.enhanceConfigurationClasses中,有興趣的自己研究吧 其實這種寫法本來就不太好,還是覺得用依賴注入更好,如下

@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;
	}
}

這種寫法就算改成@Component也沒問題

@Import+ImportBeanDefinitionRegistrar

上面說了@Import可以導入bean,一個或多個bean,但如果比如把一個包下的所有類都注入到bean,它就不能實現(xiàn)了,除非你一個一個寫,但是這樣新增一個就得寫一個。 這種需求其實很常見,比如mybaits的@MapperScan,他需要你指定一個mapper的位置,然后把mapper全部注入到bean容器,不管你加多少mapper都會注入進去。 spring批量注冊bean是有個后置處理器支持的,就是BeanDefinitionRegistryPostProcessor,如果你某個bean實現(xiàn)了BeanDefinitionRegistryPostProcessor,就會拿到bean定義注冊器BeanDefinitionRegistry,然后愛怎么注冊bean定義、注冊多少隨你。 所以我最開始遇到這種需求解決思路是@Import+BeanDefinitionRegistryPostProcessor,雖然可行,但獲取不到注解的屬性,比如@MapperScan的value

所以spring要引入ImportBeanDefinitionRegistrar這個接口,其中重要方法

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}

通過實現(xiàn)這個方法也能拿到BeanDefinitionRegistry(ConfigurationClassPostProcessor本身繼承BeanDefinitionRegistryPostProcessor,所以可以拿到),然后可以按照自己的意思注冊bean定義,更重要的:通過第一個參數(shù)importingClassMetadata可以獲取使用@Import注解的類的元數(shù)據(jù),就可以獲取到外層注解的屬性值 那么問題來了,什么時候執(zhí)行吶。 剛才"最后"章節(jié)的代碼有一句this.reader.loadBeanDefinitions(configClasses);,就是在這個時候繼承這個接口的類(被@Import引入)執(zhí)行registerBeanDefinitions方法

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

到此這篇關于spring掃描解析bean的方法詳解的文章就介紹到這了,更多相關spring解析bean內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • spring依賴注入知識點分享

    spring依賴注入知識點分享

    在本篇文章里小編給大家整理的是關于spring依賴注入知識點以及相關代碼內(nèi)容,需要的朋友們學習下。
    2019-11-11
  • Spring MVC攔截器和跨域請求使用詳解

    Spring MVC攔截器和跨域請求使用詳解

    SpringMVC的攔截器也是AOP思想的一種實現(xiàn)方式,主要用于攔截用戶的請求并做相應的處理,通常應用在權限驗證、記錄請求信息的日志、判斷用戶是否登錄等功能上,這篇文章主要介紹了Spring MVC攔截器和跨域請求,需要的朋友可以參考下
    2023-07-07
  • SpringBoot彩色日志配置方式

    SpringBoot彩色日志配置方式

    這篇文章主要介紹了SpringBoot彩色日志配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Java實現(xiàn)一鍵獲取Mysql所有表字段設計和建表語句的工具類

    Java實現(xiàn)一鍵獲取Mysql所有表字段設計和建表語句的工具類

    這篇文章主要為大家詳細介紹了如何利用Java編寫一個工具類,可以實現(xiàn)一鍵獲取Mysql所有表字段設計和建表語句,感興趣的小伙伴可以了解一下
    2023-05-05
  • 如何通過java獲取文件名和擴展名

    如何通過java獲取文件名和擴展名

    這篇文章主要介紹了如何通過java獲取文件名和擴展名,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • Java中枚舉的使用詳解

    Java中枚舉的使用詳解

    這篇文章主要介紹了Java中枚舉的使用詳解的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-07-07
  • java開發(fā)之SQL語句中DATE_FORMAT函數(shù)舉例詳解

    java開發(fā)之SQL語句中DATE_FORMAT函數(shù)舉例詳解

    要將日期值格式化為特定格式,請使用DATE_FORMAT函數(shù),下面這篇文章主要給大家介紹了關于java開發(fā)之SQL語句中DATE_FORMAT函數(shù)的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-05-05
  • jboss配置方法簡明教程

    jboss配置方法簡明教程

    這篇文章主要介紹了jboss配置方法,較為簡明扼要的說明了jboss服務器所需要的JDK環(huán)境安裝設置以及jboss的安裝與下載,并分析了配置與使用中的常見問題,需要的朋友可以參考下
    2016-08-08
  • java多線程并發(fā)中使用Lockers類將多線程共享資源鎖定

    java多線程并發(fā)中使用Lockers類將多線程共享資源鎖定

    Lockers在多線程編程里面一個重要的概念是鎖定,如果一個資源是多個線程共享的,為了保證數(shù)據(jù)的完整性,在進行事務性操作時需要將共享資源鎖定,這樣可以保證在做事務性操作時只有一個線程能對資源進行操作,下面看一個示例
    2014-01-01
  • Java后端必會Linux常用命令之一篇搞懂日志、進程、端口、部署

    Java后端必會Linux常用命令之一篇搞懂日志、進程、端口、部署

    掌握Linux命令行是開發(fā)者的必備技能,本文用最簡方式梳理高頻基礎命令,每個命令配常用示例,適合新手快速查閱,這篇文章主要介紹了Java后端必會Linux常用命令之一篇搞懂日志、進程、端口、部署的相關資料,需要的朋友可以參考下
    2026-05-05

最新評論

呼图壁县| 屏南县| 西峡县| 镇宁| 牟定县| 上饶市| 佛山市| 手机| 嘉荫县| 文登市| 府谷县| 青岛市| 龙泉市| 旺苍县| 贵州省| 富锦市| 武功县| 锡林浩特市| 鄱阳县| 棋牌| 合山市| 徐闻县| 紫云| 江陵县| 仙桃市| 淳化县| 凤翔县| 阳曲县| 泾阳县| 天柱县| 石首市| 疏勒县| 六安市| 舟曲县| 鲁山县| 留坝县| 维西| 苍溪县| 金秀| 台北市| 博乐市|