您现在的位置是:首页 > 正文

Spring框架源码(五) @configuration源码深度解析

2024-02-01 04:47:30阅读 2

        @Configuration 注解是spring-context模块提供的一个给开发者使用的配置类注解,开发者可以通过@Configuration注解来定义配置类,也可以使用xml形式注入。

        例如配置数据库配置,定义一个配置类,注入数据源DataSource, 事务管理器TransactionManager, 多数据源管理等都可以使用@Configuration 类来标记该类是一个配置类,Spring 框架会扫描并解析该类里的Bean 并注入,下面就如何解析配置类源码解析。

        官方源码解释:

        1. Indicates that  a class declares one or more @Bean methods and may be processed by the spring container  to generate bean definitions and service requests for those  beans at runtime.

       表示一个类声明了一个或多个被@Bean注解标记的方法, 他们应该会在运行时被Spring 容器处理并会为这些Bean产生bean definitions 和服务请求。

 @Configuration
   public class AppConfig {
  
       @Bean
       public MyBean myBean() {
           // instantiate, configure and return bean ...
       }
   }

        简单讲: @Configuration 标记的类可以用@Bean 定义很多Bean method 。 

一、@Configuration 用法

1. 注解形式@Configuration

   @Configuration
   public class AppConfig {
  
       @Bean
       public MyBean myBean() {
           // instantiate, configure and return bean ...
       }
   }

        使用AnnotationConfigurationApplicationContext初始化配置类。 

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
   ctx.register(AppConfig.class);
   ctx.refresh();
   MyBean myBean = ctx.getBean(MyBean.class);
   // use myBean ...

2. xml形式

       使用<context:annotation-config >属性声明, xml形式需要使用ClassPathXmlApplicationContext来加载。


   <beans>
      <context:annotation-config/>
      <bean class="com.acme.AppConfig"/>
   </beans>

        官方: In the example above, <context: annotation-config> is required in order to enable ConfigurationClassPostProcessor

        我们从官方解释中看到,根据配置会进入到ConfigurationClassPostProcessor,那ConfigurationClassPostProcessor 会是扫描这些配置Bean的入口。

        真正调用ConfigurationClassPostProcessor是在AbstractApplication里的refresh()方法里。invokerBeanFactoryPostProcessors()能调用所有invokeBeanDefinitionRegistryPostProcessors和invokerBeanFactoryPostProcessors,即所有实现BeanFactoryPostProcessor接口和BeanDefinitionRegistryPostProcessor接口的所有实现类都会被回调。

invokeBeanFactoryPostProcessors: 

 invokeBeanDefitionRegistryPostProcessors:

二、ConfigurationClassPostProcessor源码解析

         ConfigurationClassPostProcessor 类实现了BeanFacotryPostProcessor和BeanDefinitionRegistryPostProcessor 两个接口, 是解析@Configuration类的入口。

        1. BeanFactoryPostProcessor: PostProcessBeanFactory 方法入参为ConfigurableListableBeanFactory, ConfigurableListableBeanFactory的默认实现为DefaultListableBeanFactory, 提供BeanFactory所有功能与服务。

        2.BeanDefinitionRegistryPostProcessor:  postProcessBeanDefinitionRegistry()方法入参为BeanDefinitionRegistry。该方法的作用主要是扫描并注册所有@Configuraiton注解标记的类下的bean。

         先进入到processConfigBeanDefinitions方法,看看做了哪些事?

        这里我们可以发现遍历所有的BeanDefinition, 过滤掉已经处理完的ConfigurationClass. 用isFullConfigurationClass和isLiteConfigurationClass来判断。

         如果已经设置了候选人身份,那么该判断就为true, 也就不会继续进行放入到configCondidates里。

        如果之前没有设置候选人身份,那么在执行CheckConfigurationClassCandidate时,会检查是否为FullConfigurationCandidate和LiteConfigurationCandidate, 接着看FullConfigurationCandidate和LiteConfigurationCandidate。

FullConfigurationCandidate和LiteConfigurationCandidate

        从字面意思上理解,一个为满,一个为简的候选人,那他们真正的含义是什么呢?

        FullConfigurationCandidate  直接被@Configuration注解标记的类被称为FullConfigurationCandidate。

	public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
		return metadata.isAnnotated(Configuration.class.getName());
	}

         LiteConfigurationCandidate 主要是指被@Component、@ComponentScan、@Import、@ImportSource注解标记的类。

	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 isLiteConfigurationCandidate(AnnotationMetadata metadata) {
		// Do not consider an interface or an annotation...
		if (metadata.isInterface()) {
			return false;
		}

		// Any of the typical annotations found?
		for (String indicator : candidateIndicators) {
			if (metadata.isAnnotated(indicator)) {
				return true;
			}
		}

		// Finally, let's look for @Bean methods...
		try {
			return metadata.hasAnnotatedMethods(Bean.class.getName());
		}
		catch (Throwable ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
			}
			return false;
		}
	}

         如果是FullConfigurationCandidate或者LiteConfigurationCandidate, 那么给该Definition打个标记表示该BeanDefinition已经候选过, 那下次扫描的时候就会跳过该BeanDefinition。


		if (isFullConfigurationCandidate(metadata)) {
         // 表示已经候选过
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		else if (isLiteConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}

        走到这里,configCandidates有一个值了,该值我配置的MybatisConfig类:

         接下来就是解析候选ConfigurationClass

解析所有的候选ConfigurationClass并加载所有的Bean

1.  由ConfigurationClassParser 解析所有的candidates。

2.  由ConfigurationClassBeanDefinitionReader 来解析该configurationClass里的所有Bean。

3.  再次检查是否有新的ConfigurationClass加入到candidates集合里,如果有那么继续解析并加载所有的Bean。

源码如下:  

do {
			//解析所有的候选Bean
			parser.parse(candidates);
			//对ConfigurationClass校验, 如果是Final类会报错。
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			// 加载所有的BeanDefinitions
			this.reader.loadBeanDefinitions(configClasses);
			// 已经解析了加入到alreadyParsed集合了
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			// 再次检查以防有新的ConfigurationClass 加入
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							// 补偿未添加上的Candidates
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

三、@Configuration配合Profile使用

        官方源码给出了一个用法例子,对于不同的开发环境可以使用@Profile注解来指定配置文件。

 @Profile("development")
   @Configuration
   public class EmbeddedDatabaseConfig {
  
       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return embedded DataSource
       }
   }
  
   @Profile("production")
   @Configuration
   public class ProductionDatabaseConfig {
  
       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return production DataSource
       }
   }

        spring boot项目使用spring.profiles.active=development来指定激活的profile, 指定哪个就用哪个。

网站文章

  • Aria2下载软件的Linux安装、配置文件编辑、开机启动、浏览器插件连接

    Aria2下载软件的Linux安装、配置文件编辑、开机启动、浏览器插件连接

    1. 安装。 这里主要是在Debian及衍生发行版上的操作。 直接用 apt install aria2 即可 apt update apt install aria2 # aria2 的命令是 ar...

    2024-02-01 04:47:23
  • 新起点

    2019年7月9日:进入新的工作环境,面对全新的电脑,就像现在的自己一样,犹如一张白纸,可以随意涂画。年轻的心斗志昂扬,重新出发创出属于自己的精彩。1. 不断学习2.认真负责3.勤学好问...

    2024-02-01 04:47:17
  • monkey环境搭建

    monkey环境搭建

    monkey的安装环境需要4块:1.Java环境JDK的安装2.Android SDK环境3.启动模拟器(夜神模拟器)或者真机4.执行monkey测试命令第一部分:Java环境的安装1)下载好JDK的安装包后,双击安装2)配置环境变量:我的电脑--右键属性--高级系统设置--环境变量在系统变量处点击新建:填写变量名和JDK的安装路径JAVA...

    2024-02-01 04:46:47
  • 记录自己第一次科研经历

    这里写自定义目录标题科研之路研究方向选择功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右Sma...

    2024-02-01 04:46:40
  • 什么是Intel Elkhart Lake?专用于物联网的处理器系列

    什么是Intel Elkhart Lake?专用于物联网的处理器系列

    Elkhart Lake将部署的关键应用程序将包括长时间的正常运行时间、不间断的操作、在空间受限的位置实施,以及频繁的冲击、振动和温度变化。与Bay Trail相比,Elkhart Lake处理器的P...

    2024-02-01 04:46:33
  • Missing type in composite literal

    首先来看一个示例,它在idea中会直接提示错误。 package main import ( &quot;fmt&quot; ) type Person struct { Name string Ag...

    2024-02-01 04:46:06
  • react-router-dom v6.5.0实现路由守卫RouterBeforeEach及缓存

    react-router-dom v6.5.0实现路由守卫RouterBeforeEach及缓存

    最近留意下react-router-dom有更新到6.6.1的版本,在这个版本增加了不少的功能。研究了下,可以利用其提供的API实现一个类似Vue的路由守卫,从而简便达到路由鉴权的业务场景。这里我是使...

    2024-02-01 04:45:58
  • Java中抽象类和接口

    一.抽象类  在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为: 1 abstract void fun();   抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法...

    2024-02-01 04:45:53
  • Grafana任意文件读取 (CVE-2021-43798)

    Grafana任意文件读取 (CVE-2021-43798)

    Grafana是一个跨平台、开源的数据可视化网络应用程序平台。用户配置连接的数据源之后,Grafana可以在网络浏览器里显示数据图表和警告。Grafana 存在未授权任意文件读取漏洞,攻击者在未经身份验证的情况下可通过该漏洞读取主机上的任意文件。

    2024-02-01 04:45:25
  • dubbox在异构系统中的使用-补充1

    除了添加必要的dependencies外,其他需要设置的文件包括:1. web.xml,加载dubbox dispatch servlet:&lt;?xml version=&quot;1.0&quo...

    2024-02-01 04:45:19