spring笔记⑪——@Import和@Configuration的底层实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_35262405/article/details/100555704

@Import

@Import的作用主要是用来向spring容器中注入bean,对于这个注解可以添加的参数一共有三种。

  • 普遍类,就是需要注入的类
  • 实现ImportSelector的类
  • 实现ImportBeanDefinitionRegistrar的类

对于着几种类的作用就不再这里赘述了,首先第一种就是普通的注入。

ImportSelector的实现类的作用参考ImportSelector使用——spring容器bean的注入

ImportBeanDefinitionRegistrar的作用参考spring的扩展点总结

这里探讨的是@Import的底层实现,首先我们必须明白这个注解到底是从哪里调用的,通过源码的分析,这个注解调用的入口方法就是spring上下文对象的refresh方法,这个方法可能是用户自己调用的,也可能是在调用spring上下文对象的有参构造器的时候调用的,当不论如何这个方法都是必不可少的。它是spring初始化的三大步骤之一,也是最后一个步骤。

整个的方法调用链如下,到最后是通过processImports这个方法来处理Import注解的
在这里插入图片描述
这个放法更多的是对ImportSelector和ImportBeanDefinitionRegistrar的实现类的回调方法的实现,对于其中真正注册bean的地方并不在这,这里只是把需要注册的bean的类放在了一个map中。

	/**
	 *
	 * @param configClass	封装了bd
	 * @param currentSourceClass	封装了类全名
	 * @param importCandidates	一个集合,集合中的元素封装了import中的值
	 * @param checkForCircularImports	传进来的是true
	 */
	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		//如果是空集合就直接返回不分析
		if (importCandidates.isEmpty()) {
			return;
		}

		//判断这个bd是不是已经被解析过了
		//如果是抛出异常
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		//不是就开始分析
		else {
			this.importStack.push(configClass);
			try {
				//循环遍历每一个import注解
				for (SourceClass candidate : importCandidates) {
					//判断注解的内容是不是ImportSelector实现类
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();


						//反射实现一个ImportSelector对象
						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);

						ParserStrategyUtils.invokeAwareMethods(
								selector, this.environment, this.resourceLoader, this.registry);
						//判断是不是设置了延迟执行,并且ImportSelector对象实现了DeferredImportSelector
						if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
							//加入到了一个集合中
							this.deferredImportSelectors.add(
									new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
						}
						//如果不是就立即执行
						else {
							//先将实现ImportSelector对象的selectImports方法返回的字符串数组拿出来
							//这里的字符串数组存储着需要注入的类的全名
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							//将数组转换成一个集合
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							//递归调用
							//注意这里是直接把返回的类名集合传了进去
							//所以主要是判断返回的类是不是ImportSelector或者ImportBeanDefinitionRegistrar
							//如果都不是就会直接进入else将这些类的bd加入bean工厂
							//所以对于所有需要加入bean工厂的类都会进入最后的else语句
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					//判断注解的内容是不是ImportBeanDefinitionRegistrar实现类
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
						ParserStrategyUtils.invokeAwareMethods(
								registrar, this.environment, this.resourceLoader, this.registry);
						//同样是将这个类加入到一个map变量importBeanDefinitionRegistrars中去,后面会去处理
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					//这个是直接传入了需要注入的类的class对象
					else {
						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						// 将这些需要注入的类信息加入了到一个Map中
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						//处理需要注入的类
						//这里并没有直接将类的bd注册进bean工厂中去
						//而是放在一个map变量configurationClasses中
						//到后面才会从这个map中拿出来转换成bd注册进bean工厂
						processConfigurationClass(candidate.asConfigClass(configClass));
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

需要注意的是这里并没有把需要注入的类直接放入bean工厂中去,而是先是放在了一个map容器中。

在后面会对这个map中的类做一个集体注册,所以完整的流程图如下
在这里插入图片描述
需要注意真正的注册步骤是在右边
在这里插入图片描述

@Configuration

@Configuration这个注解相信大家都很熟悉,对于配置类来说加上这个注解准没错,相当于对这个类做了一个标识,但是这个注解的实际作用可以说并没有看上去那么简单。

我们知道对于配置类中我们同样可以向spring容器中注入bean对象,通过@Bean注解就可以实现了。

我们知道spring中bean默认是单例的,可是如果通过这种方法注入的bean是如何保证单例的呢?假如有以下spring配置类

@Configuration
public class SpringConfig {

	@Bean
	public UserTest userTest(){
		return new UserTest();
	}

	@Bean
	public UserTest1 userTest1(){
		userTest();
		return new UserTest1();
	}
}
public class UserTest {


	public UserTest(){
		System.out.println("UserTest");
	}

	public void test(){
		System.out.println("test");
	}
}
public class UserTest1 {

	public UserTest1(){
		System.out.println("UserTest1");
	}

	public void test(){
		System.out.println("test1");
	}
}

就这种写法上来看UserTest应该是会被初始化两次的,就是会调用两次构造方法

public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
		annotationConfigApplicationContext.register(SpringConfig.class);
		annotationConfigApplicationContext.refresh();
  }
}

打印结果如下

UserTest
UserTest1

但是显然并没有输出两次,意味着spring只调用了一次构造方法

接下来我们去掉@Configuration注解

UserTest
UserTest
UserTest1

这次输出的结果就和我们预期的一样了,但是着显然不是spring想要达到的结果,这样也不能保证bean的单例特征。

那么实现这种单例表现自然就是@Configuration的作用了,我们可以想象到如果没有加@Configuration的时候spring在实例化@Bean注入的bean时就是单纯调用了这个方法,这样也显然会造成多例的情况。

那么@Configuration时怎么避免的呢,同样是调用方法,它想改变这种情况只能改变方法的是实现,那么如果改变方法的实现呢,自然就是通过代理实现的。

我们可以先知道,加过@Configuration注解的配置类会被spring代理实现,也并不是jdk的动态代理而是cglib的动态代理。

下面是调用链
在这里插入图片描述
在这个方法中基本完成了是否需要动态代理和cglib动态代理的实现

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
	Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
	for (String beanName : beanFactory.getBeanDefinitionNames()) {
		BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
		if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
			if (!(beanDef instanceof AbstractBeanDefinition)) {
				throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
						beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
			}
			else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
				logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
						"' since its singleton instance has been created too early. The typical cause " +
						"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
						"return type: Consider declaring such methods as 'static'.");
			}
			configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
		}
	}

	/**
	 * 这里如果为true就会直接返回,后面的方法就不会执行
	 * 这里是对之前的一个标识判断的
	 * 这个标识是CONFIGURATION_CLASS_ATTRIBUTE
	 * 这个标识同样是存储在这个对象中的,是在实现了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法中设置的
	 * 这个标识如果设置为CONFIGURATION_CLASS_FULL就不会进,如果为CONFIGURATION_CLASS_LITE就会进
	 * 如果非到前面就会知道如果之前在处理最先注册进入bean工厂中的类中如果加了@configuration注解的就会设置CONFIGURATION_CLASS_FULL
	 * 这里如果没有进入if就会执行后面cglib动态代理
	 * 所以总的来说如果加了@configuration注解就会开启cglib动态代理
	 * 这里的cglib动态代理是对有@configuration注解的类做的代理
	 * 如果没加就不会
	 * 主要目的就是保证bean的单例特点
	 */
	if (configBeanDefs.isEmpty()) {
		// nothing to enhance -> return immediately
		return;
	}

	ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
	for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
		AbstractBeanDefinition beanDef = entry.getValue();
		// If a @Configuration class gets proxied, always proxy the target class
		beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
		try {
			// Set enhanced subclass of the user-specified bean class
			Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
			if (configClass != null) {
				//实现一个cglib代理对象
				Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
				if (configClass != enhancedClass) {
					if (logger.isDebugEnabled()) {
						logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
								"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
					}
					beanDef.setBeanClass(enhancedClass);
				}
			}
		}
		catch (Throwable ex) {
			throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
		}
	}
}

对于这个cglib动态代理的具体实现就不在这阐述,大致的逻辑就是在碰到调用构造方法时会先判断是不是已经有了这个实例,如果存在就返回这个实例,如果不存在就直接创建一个。

猜你喜欢

转载自blog.csdn.net/qq_35262405/article/details/100555704