Spring中@Import注解源码分析

       Spring中@Import注解是用来向IOC容器出入bean的,关于其使用可以参考:Spring为IOC容器注入Bean的方式,@Import导入的类型分为三种:普通类、实现ImportBeanDefinitionRegistrar接口的类、实现ImportSelector接口的类,而对于ImportSelector、ImportBeanDefinitionRegistrar的实现类,必须被@Import导入到容器才会生效,不然没用,这Spring为IOC容器注入Bean的方式中,我自己在没有学习Spring源码之前就有一个错误的认识,还说了以后会写一篇博客,对于@Import注解进行源码分析,下面开始源码分析。

       @Import注解源码:

/**
 * Indicates one or more {@link Configuration @Configuration} classes to imports.
 *
 * <p>Provides functionality equivalent to the {@code <imports/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
 * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
 * injection. Either the bean itself can be autowired, or the configuration class instance
 * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
 * navigation between {@code @Configuration} class methods.
 *
 * <p>May be declared at the class level or as a meta-annotation.
 *
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to imports.
	 */
	Class<?>[] value();
}

        这里特意把注释也粘出来,注释的大致意思是:使用@import相当于在spring xml格式中的<imports/>标签,可以向容器中导入标注了@Configuration的配置类(关于@Configuration的使用可以参考Spring中@Configuration的使用)、ImportSelector的实现类、ImportBeanDefinitionRegistrar的实现类和普通的类,关于@import可以标注在类上或者注解上面,如果要导入xml文件,可以使用@ImportResource进行导入。

       @Import仅有一个value属性,是一个Class类型的数组,所以我们可以在一个@Import中导入多个类。

        这里需要大家对于Spring中bean的生命周期有个大致的了解,可以参考:Spring中bean的生命周期(最详细),其中在Spring的主流程中有一步是invokeBeanFactoryPostProcessors,在这一步会解析java类,并会解析Java类中所有的注解,如@Import、@Configruation、@ComponentScan、@ImportResource、@Component、@Service等等注解,都会进行解析,然后将普通类变成BeanDefinition对象,spring在后期会根据这个BeanDefinition进行实例化bean。

       在Spring中@Configuration源码深度解析(一)中,我们已经介绍了关于invokeBeanFactoryPostProcessors的方法,其最终会调用ConfigurationClassPostProcessor类来处理@Configuration注解,对于@Import注解也是在这个类中进行解析的,在Spring中@Configuration源码深度解析(一)中的代码块6中的第8步,会进行解析这个@Import注解,下面我们就从这里开始。

代码块1:ConfigurationClassParser#parse方法

public void parse(Set<BeanDefinitionHolder> configCandidates) {
	this.deferredImportSelectors = new LinkedList<>();
	//1.根据BeanDefinition 的类型 做不同的处理,一般都会调用ConfigurationClassParser#parse 进行解析
	for (BeanDefinitionHolder holder : configCandidates) {
		BeanDefinition bd = holder.getBeanDefinition();
		try {
			if (bd instanceof AnnotatedBeanDefinition) {
				//2.解析注解对象,大多数的我们写的对象,都是通过加注解的方式导入到Spring容器的,所以会走这一步
				//并且把解析出来的bd放到map,但是这里的bd指的是普通的
				//何谓不普通的呢?比如@Bean 和各种beanFactoryPostProcessor得到的bean不在这里put
				//但是是这里解析,只是不put而已
				parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
			}
			else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
				parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
			}
			else {
				parse(bd.getBeanClassName(), holder.getBeanName());
			}
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(
					"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
		}
	}

	//3.处理延迟加载的importSelect?为什么要延迟加载,估计就是为了延迟吧
	processDeferredImportSelectors();
}

         在第2步会解析BeanDefinition对象,在BeanDefinition对象中,会保存Class对象的所有信息,让我们在访问类中注解的时候会比较方便,具体看代码块2。

代码块2:ConfigurationClassParser#parse(AnnotationMetadata,String)方法和processConfigurationClass方法

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
       //1.解析类
	processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        //2.这里会判断是否需要跳过解析,就是判断是否加了@Condition注解,来判断是否满足条件
	if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
		return;
	}
	//3.处理Imported 的情况
	//就是当前这个注解类有没有被别的类import,有递归调用的可能,这里就是说如果我们在多个配置类中
	//导入了同一个类的话,这个类不会被注册两次
	ConfigurationClass existingClass = this.configurationClasses.get(configClass);
	if (existingClass != null) {
		if (configClass.isImported()) {
			if (existingClass.isImported()) {
				existingClass.mergeImportedBy(configClass);
			}
			return;
		}
		else {
			// Explicit bean definition found, probably replacing an imports.
			// Let's remove the old one and go with the new one.
			this.configurationClasses.remove(configClass);
			this.knownSuperclasses.values().removeIf(configClass::equals);
		}
	}
	// Recursively process the configuration class and its superclass hierarchy.
	SourceClass sourceClass = asSourceClass(configClass);
	do {
		//4.进行类的解析工作,这一步进行循环解析,如果有父类的话,也会解析父类中的注解
		sourceClass = doProcessConfigurationClass(configClass, sourceClass);
	}
	while (sourceClass != null);
	//5.一个map,用来存放扫描出来的bean(注意这里的bean不是对象,仅仅bean的信息,因为还没到实例化这一步)
	//configClass中存在的有@import和@bean标注的注解,这些还没有被加入到bean定义中,需要在下面进行加入
	this.configurationClasses.put(configClass, configClass);
}

       在第2步中,会进行判断是否需要跳过解析,因为这个解析的是类,所有是进行判断类中是否标注了@Conditional注解,如果有的话,就拿出来进行判断一下是否需要跳过,这个以后有时间进行源码分析一下。

        在第4步就完成类的解析工作,并且递归调用父类,查看是否标注了注解,具体看代码块3。

        在第5步,会把这个类放到一个map中,在后面进行一些操作,configClass会包括@Import和@Bean注解扫描的类,普通的类,如标注了@Compent、@Service等注解的普通类,就在第4步进行解析的时候,放到BeanDefinitionMap中,而@Import和@Bean则不会放入其中,会在后面的操作中进行放入。Spring对于普通的注解类和@Import、@Bean注解方式导入到容器的类,是分开进行注册到BeanDefinitionMap中的,首先会把所有的普通类都放到BeanDefinitionMap中,然后才是@Import、@Bean注解方式导入的类。

代码块3:ConfigurationClassParser#doProcessConfigurationClass方法

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
		throws IOException {

	// Recursively process any member (nested) classes first
	//1.处理内部类
	processMemberClasses(configClass, sourceClass);

	// Process any @PropertySource annotations
	//2.处理@PropertySource注解
	for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
			sourceClass.getMetadata(), PropertySources.class,
			org.springframework.context.annotation.PropertySource.class)) {
		if (this.environment instanceof ConfigurableEnvironment) {
			processPropertySource(propertySource);
		}
	}

	// Process any @ComponentScan annotations
	//3.处理@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) {
			// The config class is annotated with @ComponentScan -> perform the scan immediately
			//扫描普通类=componentScan=com.luban
			//这里扫描出来所有@Component
			//并且把扫描的出来的普通bean放到map当中,这个map是单例的map
			//其处理流程是:通过扫描@ComponentScan注解,得到用户自己配置需要扫描的规则,
			//规则如是否来加载,需要加载的基础包,然后根据规则去扫描包中的各个类,然后把这些类
			//都获取到之后,封装到一个ConfigClass类中,然后就会再去解析这个类,解析这个类的过程就是一个递归调用的
			//过程,最终还是会检查这个类是否加了@ComponentScan注解,就是还会调用这个方法进行解析得到的每一个ConfigClass类
			Set<BeanDefinitionHolder> scannedBeanDefinitions =
					this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
			// Check the set of scanned definitions for any further config classes and parse recursively if needed
			//检查扫描出来的类当中是否还有configuration
			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
				BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
				if (bdCand == null) {
					bdCand = holder.getBeanDefinition();
				}
				//检查  todo
				if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
					parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
			}
		}
	}

	/**
	 * 上面的代码就是扫描普通类----@Component
	 * 并且放到了map当中
	 */
	// Process any @Import annotations
	//处理@Import  imports 3种情况
	//ImportSelector
	//普通类
	//ImportBeanDefinitionRegistrar
	//这里和内部地柜调用时候的情况不同
	/**
	 * 这里处理的import是需要判断我们的类当中时候有@Import注解
	 * 如果有这把@Import当中的值拿出来,是一个类
	 * 比如@Import(xxxxx.class),那么这里便把xxxxx传进去进行解析
	 * 在解析的过程中如果发觉是一个importSelector那么就回调selector的方法
	 * 返回一个字符串(类名),通过这个字符串得到一个类
	 * 继而在递归调用本方法来处理这个类
	 *
	 * 判断一组类是不是imports(3种import)
	 *
	 *其中第三个参数getImports(sourceClass),就是获取的@Imports标注的类,会递归的去获取,
	 * 比如
	 * @Target(ElementType.TYPE)
	 * @Retention(RetentionPolicy.RUNTIME)
	 * @Import(School.class)
	 * public @interface ImportTest {
	 * }
	 * 这个注解如果标注在类上,会拿到School
	 */
	 //4.处理@Import注解
	processImports(configClass, sourceClass, getImports(sourceClass), true);

	// Process any @ImportResource annotations
	//5.处理@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);
		}
	}

	// Process individual @Bean methods
	//6.处理@Bean注解
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}

	// Process default methods on interfaces
	processInterfaces(configClass, sourceClass);

	// Process superclass, if any
	//7.解析父类中的注解参数,以便递归调用
	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();
		}
	}

	// No superclass -> processing is complete
	return null;
}

       可以看到这里会处理很多注解,在第4步会先调用getImports方法,获取正在解析的类中所有@Import注解的value值,把这个值保存到集合中,因为@Import的value是class类型的数组,所以获取到的是calss的集合,注意,一个注解中可以在其上面标注@Import,所以,一个类上面,可以间隔的标注多个@Import,具体看代码块4

      处理@Import注解的情况,具体看代码块6.

代码块4.ConfigurationClassParser#getImports方法

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
	//获取到解析类中所有的@Import的value值,并把他们合成一个集合中
        Set<SourceClass> imports = new LinkedHashSet<>();
        //已经访问过的,放到这个集合中
	Set<SourceClass> visited = new LinkedHashSet<>();
        //1.处理解析类中的@Import
	collectImports(sourceClass, imports, visited);
	return imports;
}

      第1步,会处理解析类中的@Import,具体看代码块5.

代码块5.ConfigurationClassParser#collectImports方法

private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
		throws IOException {
        //1.将sourceClass添加到visited集合中,如果已经添加,再次添加visited.add(sourceClass)会返回false,第一次添加会返回true
	if (visited.add(sourceClass)) {
	        //2.获取当前解析对象(或者是注解)中标注的注解
		for (SourceClass annotation : sourceClass.getAnnotations()) {
		    //3.遍历解析对象标注的每一个注解的名称
			String annName = annotation.getMetadata().getClassName();
			//4.如果不是以java开头,而且不是@Import类,就递归再次调用collectImports方法
			if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
				collectImports(annotation, imports, visited);
			}
		}
		//5.添加@Import注解的value属性值到imports集合中,注意,一个注解中可以在其上面标注@Import,所以,一个类上面
		//可以间隔的标注多个@Import,而且每一个@Import的value都是一个class的数组,所以需要递归调用
		imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
	}
}

      在第五步,添加@Import注解的value属性值到imports集合中,注意,一个注解中可以在其上面标注@Import,所以,一个类上面,可以间隔的标注多个@Import,而且每一个@Import的value都是一个class的数组,所以需要递归调用找出所有的@Import注解。

代码块6.ConfigurationClassParser#processImports方法

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
		Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

	if (importCandidates.isEmpty()) {
		return;
	}
	 //1.判断是否存在循环进行Import的情况,
	if (checkForCircularImports && isChainedImportOnStack(configClass)) {
		this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
	}
	else {
	        //2.压入栈,进行判断循环Import
		this.importStack.push(configClass);
		try {
		        //3.importCandidates就是拿到所有@Import导入的的class类
			for (SourceClass candidate : importCandidates) {
				//4.判断是否是ImportSelector的子类
				if (candidate.isAssignable(ImportSelector.class)) {
					// Candidate class is an ImportSelector -> delegate to it to determine imports
					Class<?> candidateClass = candidate.loadClass();
					//反射实现一个对象
					ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
					ParserStrategyUtils.invokeAwareMethods(
							selector, this.environment, this.resourceLoader, this.registry);
					if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
						this.deferredImportSelectors.add(
								new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
					}
					else {
						//5.回调
						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
						//6.递归,这里第二次调用processImports
						//如果是一个普通类,会进else
						//importSourceClasses里面的值就是调用selectImports返回的String字符数组所创建的类
						//如返回是
						//@Override
						//	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
						//		return new String[]{MyImportSelector2.class.getName(), School.class.getName()};
						//	}
						//那么importSourceClasses就是MyImportSelector2和School
                                                //递归调用本方法
						processImports(configClass, currentSourceClass, importSourceClasses, false);
					}
				}
				else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                                        //7.判断是否是ImportBeanDefinitionRegistrar的子类
					// 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);
					//添加到一个list当中和importselector不同
					configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
				}
				else {
                                        //8.导入的是普通类
					// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
					// process it as an @Configuration class
					// 否则,加入到importStack后调用processConfigurationClass 进行处理
					//processConfigurationClass里面主要就是把类放到configurationClasses
					//configurationClasses是一个集合,会在后面拿出来解析成bd继而注册
					//可以看到普通类在扫描出来的时候就被注册了
					//如果是importSelector,会先放到configurationClasses后面进行出来注册
					this.importStack.registerImport(
							currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                                        //9.处理普通类
					processConfigurationClass(candidate.asConfigClass(configClass));
				}
			}
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(
					"Failed to process imports candidates for configuration class [" +
					configClass.getMetadata().getClassName() + "]", ex);
		}
		finally {
			//出栈
			this.importStack.pop();
		}
	}
}

        对于第1步和第2步是用来判断是否有循环导入的情况,如果有则直接抛出异常,所有@Import是不支持循环导入的,循环导入的例子:

@Import(B.class) //A中导入了B
public class A {}

@Import(A.class) //B中导入了A
public class B {}

@Configuration
@Import({A.class})
public class Config {

}

public class Test01 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
    }
}

//运行结果:
//Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: 
// A circular @Import has been detected: Illegal attempt by @Configuration class 'B' to import class 'A' as 'A' is already present 
// in the current import stack [B->A->Config]
//Offending resource: it.cast.cyclicImport.B
//	at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
//	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:599)
//	at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:303)
//	at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245)
//	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:636)
//	at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:303)
//	at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245)
//	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:636)
//	at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:303)
//	at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245)
//	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:636)
//	at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:303)
//	at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245)
//	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:202)
//	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:170)
//	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316)
//	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233)
//	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271)
//	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91)
//	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:692)
//	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:530)
//	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:88)
//	at it.cast.cyclicImport.Test01.main(Test01.java:11)

        可以发现运行时,会出现错误,关于Spring中对于循环方面的只是,我知道的有三种:循环导入Import,循环依赖构造器,循环依赖属性注入,其中Spring仅支持循环依赖属性注入,另外两种是不支持的,会直接报错。

        第3步是拿到这个类中所有的@Import导入的类,然后进行遍历,@Import导入的是一个数组类型,所有可以导入多个。

        对于下面的步骤比较绕,分不同情况进行分析:

        第4步,会判断@Import导入的是否为ImportSelector或者ImportBeanDefinitionRegistrar接口的子类,或者是普通类,对于ImportSelector子类的逻辑判断比较复杂,也比较绕,咱们先分析ImportBeanDefinitionRegistrar和普通类的情况。

        如果是ImportBeanDefinitionRegistrar的子类,那么就会通过反射拿到ImportBeanDefinitionRegistrar的子类,然后将这个ImportBeanDefinitionRegistrar对象放到当前解析对象的ImportBeanDefinitionRegistrar的Map中,此时还没有进行调用方法的,注意这一点。

        如果是普通类,那么就进行解析这个类,具体看代码块9,这里导入的普通类可以是一个配置类,这个配置类可以有注解,像@Import、@Bean、@ComponentScan等注解,导入的普通类,Spring会解析它所有的注解,这一点是不是很强大。

       如果是先通过反射获取到ImportSelector接口的子类,然后调用ImportSelector接口的selectImports方法,拿到一个字符串数组(类的全路径名),然后根据字符串数组反射得到对象的集合,然后再一次判断再一次调用代码块4的方法,判断这个对象是否是ImportSelector、ImportBeanDefinitionRegistrar的子类,如果是那么则按照相应的逻辑,进行处理,如果不是,则会走处理普通类的逻辑,普通类的逻辑解析上面已经说了。

     代码块6的第9步,有一个candidate.asConfigClass(configClass)方法,其中configClass的参数是正在解析的这个类,会把正在解析被@Import导入的类,封装成ConfigClass,从而进一步解析,咱们看一下这个方法,看代码块7

代码块7:ConfigurationClassParser的内部类SourceClass#asConfigClass方法

public ConfigurationClass asConfigClass(ConfigurationClass importedBy) throws IOException {
	if (this.source instanceof Class) {
		return new ConfigurationClass((Class<?>) this.source, importedBy);
	}
        //1.在创建ConfigurationClass类的时候,需要指明这个类,是被谁导入进来的
	return new ConfigurationClass((MetadataReader) this.source, importedBy);
}

        具体看代码块8中ConfigurationClass的构造方法。

代码块8:ConfigurationClass#ConfigurationClass构造方法

public ConfigurationClass(MetadataReader metadataReader, @Nullable ConfigurationClass importedBy) {
	this.metadata = metadataReader.getAnnotationMetadata();
	this.resource = metadataReader.getResource();
	//importedBy是一个set集合,意思当前正在解析的类,是被那个配置类所导入的
	this.importedBy.add(importedBy);
}

       importedBy会指明是谁导入的当前的类,可以存在多个,所以,当我们使用@Import导入同一个类多次的时候,只会解析并注册一次这个类,在代码块2的第3步和第5步就是用来验证这个操作的。

代码块9:ConfigurationClassParser#processConfigurationClass方法

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        //2.这里会判断是否需要跳过解析,就是判断是否加了@Condition注解,来判断是否满足条件
	if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
		return;
	}
	//3.处理Imported 的情况
	//就是当前这个注解类有没有被别的类import,有递归调用的可能,这里就是说如果我们在多个配置类中
	//导入了同一个类的话,这个类不会被注册两次
	ConfigurationClass existingClass = this.configurationClasses.get(configClass);
	if (existingClass != null) {
		if (configClass.isImported()) {
			if (existingClass.isImported()) {
				existingClass.mergeImportedBy(configClass);
			}
			return;
		}
		else {
			// Explicit bean definition found, probably replacing an imports.
			// Let's remove the old one and go with the new one.
			this.configurationClasses.remove(configClass);
			this.knownSuperclasses.values().removeIf(configClass::equals);
		}
	}
	// Recursively process the configuration class and its superclass hierarchy.
	SourceClass sourceClass = asSourceClass(configClass);
	do {
		//4.进行类的解析工作,这一步进行循环解析,如果有父类的话,也会解析父类中的注解
		sourceClass = doProcessConfigurationClass(configClass, sourceClass);
	}
	while (sourceClass != null);
	//5.一个map,用来存放扫描出来的bean(注意这里的bean不是对象,仅仅bean的信息,因为还没到实例化这一步)
	//configClass中存在的有@import和@bean标注的注解,这些还没有被加入到bean定义中,需要在下面进行加入
	this.configurationClasses.put(configClass, configClass);
}

        你会发现代码块7和代码块2里面的方法是一个方法,这里比较绕,是一个方法说明了什么?说明了对于导入的普通类,可以理解里面所有Spring能够识别的注解,比如方法的@Bean、@Import、@ComponentScan等注解,这一点需要理解一下。

总结:

       Spring中@Import可以向容器中注册组件(bean),@Import可以标注在有@Component,@Service,@Repository,@Configuration注解的类上面,还可以标注在@Import导入的ImportSelector实现类和普通类上面,这些都是允许的,标注在@Component,@Service,@Repository,@Configuration注解的类上面时,会在解析类的时候进行解析@Import注解,当标注在@Import导入的ImportSelector实现类和普通类上面时,会解析@Import注解的时候进行判断导入的类上面是否有其他注解。而如果使用@Import导入ImportBeanDefinitionRegistrar接口的实现类,是不会进行判断的,这一点很重要,ImportBeanDefinitionRegistrar接口的实现类会直接变成一个BeanDefinition,不会进行类上面注解的判断。

       所以,我们在看Spring Boot源码的时候,会有很多的地方都使用到了@Import注解,在没学习之前,百思不得其解,为啥使用@Import可以这样做,例如:

@Configuration
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class })
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {
    //代码忽略
}

@Configuration
@EnableConfigurationProperties(HibernateProperties.class)
@ConditionalOnSingleCandidate(DataSource.class)
class HibernateJpaConfiguration extends JpaBaseConfiguration {
   //被导入的类,省略代码
}

@Configuration
@EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
   //被带入的父类,他又实现了BeanFactoryAware 接口,会把BeanFactory注册进来
}

      这个例子就充分体现了@Import的强大之处,HibernateJpaConfiguration是被导入的类,这个类上面的注解也会被进行解析,因为这个HibernateJpaConfiguration是一个普通类,所以会走代码块6的第8步和第9步进行解析,然后进行处理解析HibernateJpaConfiguration类上面的注解。

     还有就是@Import可以递归的解析导入类的父类,所以会解析JpaBaseConfiguration类,然后再次解析器类上面的注解,会发现有一个@Import注解,就会继续解析这个注解,是不是很强大。

发布了235 篇原创文章 · 获赞 65 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_35634181/article/details/105005635