El rol y el uso de la anotación @Import de Spring

@Importar anotación

@Import es el componente principal de Spring basado en la configuración de anotaciones de Java. La anotación @Import proporciona la función de anotación @Bean, así como la función Spring original para organizar múltiples archivos xml dispersos basados ​​en la etiqueta <import> en el archivo de configuración xml. Por supuesto, aquí es para organizar múltiples clases @Configuration dispersas .

Las funciones de la anotación @Import se explicarán a continuación.

1. Introduzca otra @Configuration

Supongamos que existen las siguientes interfaces y dos clases de implementación:

package com.test
interface ServiceInterface {
    void test();
}

class ServiceA implements ServiceInterface {

    @Override
    public void test() {
        System.out.println("ServiceA");
    }
}

class ServiceB implements ServiceInterface {

    @Override
    public void test() {
        System.out.println("ServiceB");
    }
}
复制代码

Dos @Configuration, ConfigA, @Import, ConfigB:

package com.test
@Import(ConfigB.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}

@Configuration
class ConfigB {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceB() {
        return new ServiceB();
    }
}
复制代码

Cree AnnotationConfigApplicationContext a través de ConfigA y obtenga ServiceInterface para ver qué implementación es:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigA.class);
    ServiceInterface bean = ctx.getBean(ServiceInterface.class);
    bean.test();
}
复制代码

El resultado es: ServiceB. Demuestra que @Import tiene prioridad sobre su propia carga de definición de clase.

2. Inicialice directamente otros tipos de Bean

Después de Spring 4.2 , @Import puede especificar directamente la clase de entidad y cargar esta definición de clase en el contexto. Por ejemplo, si @Import of ConfigA en el código anterior se modifica a @Import (ServiceB.class), el Bean of ServiceB se generará en el contexto del contenedor, y luego se ejecutará el método principal y la salida será : ServiceB. Demuestre que @Import tiene prioridad sobre su propia carga de definición de clase.

3. Especifique la clase que implementa ImportSelector (y DefferredServiceImportSelector) para la carga personalizada

Especifique la clase que implementa ImportSelector y cargue dinámicamente la clase a través de los atributos en AnnotationMetadata. AnnotationMetadata es el atributo de la clase donde se encuentra la anotación de importación (si la clase es una clase de anotación, se extiende a la clase sin anotación a la que se aplica esta clase de anotación).

El método selectImports debe implementarse para devolver una matriz de cadenas de @Configuation o el nombre completo de la clase de Bean específica que se cargará.

package com.test;
class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //可以是@Configuration注解修饰的类,也可以是具体的Bean类的全限定名称
        return new String[]{"com.test.ConfigB"};
    }
}

@Import(ServiceImportSelector.class)
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}
复制代码

Ejecute el método principal nuevamente y genere: ServiceB. Demuestra que @Import tiene prioridad sobre su propia carga de definición de clase. Por lo general, si el marco implementa la carga dinámica de clases según los parámetros de AnnotationMetadata, generalmente se escribe una anotación Enable adicional para su uso en conjunto. P.ej:

package com.test;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportSelector.class)
@interface EnableService {
    String name();
}

class ServiceImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //这里的importingClassMetadata针对的是使用@EnableService的非注解类
        //因为`AnnotationMetadata`是`Import`注解所在的类属性,如果所在类是注解类,则延伸至应用这个注解类的非注解类为止
        Map<String , Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}
复制代码

Después de eso, agregue la anotación @EnableService (nombre = "B") en ConfigA

package com.test;
@EnableService(name = "B")
@Configuration
class ConfigA {
    @Bean
    @ConditionalOnMissingBean
    public ServiceInterface getServiceA() {
        return new ServiceA();
    }
}
复制代码

Ejecute el método principal de nuevo y muestre: ServiceB.

También puede implementar la interfaz DeferredImportSelector, de modo que las clases devueltas por selectImports se carguen todas en último lugar, en lugar de cargarse primero como la anotación @Import. P.ej:

package com.test;
class DefferredServiceImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        if (Objects.equals(name, "B")) {
            return new String[]{"com.test.ConfigB"};
        }
        return new String[0];
    }
}
复制代码

Modifique la anotación EnableService:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(DefferredServiceImportSelector.class)
@interface EnableService {
    String name();
}
复制代码

De esta manera, ConfigA se cargará antes de la ConfigB devuelta por DefferredServiceImportSelector, ejecutará el método principal y generará: ServiceA

4. Especifique la clase que implementa ImportBeanDefinitionRegistrar para la carga personalizada

El uso y propósito son similares a ImportSelector, pero si queremos redefinir el Bean, como inyectar propiedades dinámicamente, cambiar el tipo y alcance del Bean, etc., necesitamos especificar la clase que implementa ImportBeanDefinitionRegistrar. P.ej:

Definir ServiceC

package com.test;
class ServiceC implements ServiceInterface {

    private final String name;

    ServiceC(String name) {
        this.name = name;
    }

    @Override
    public void test() {
        System.out.println(name);
    }
}
复制代码

Definir ServiceImportBeanDefinitionRegistrar para registrar dinámicamente ServiceC, modificar EnableService

package com.test;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(ServiceImportBeanDefinitionRegistrar.class)
@interface EnableService {
    String name();
}

class ServiceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> map = importingClassMetadata.getAnnotationAttributes(EnableService.class.getName(), true);
        String name = (String) map.get("name");
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ServiceC.class)
                //增加构造参数
                .addConstructorArgValue(name);
        //注册Bean
        registry.registerBeanDefinition("serviceC", beanDefinitionBuilder.getBeanDefinition());
    }
}
复制代码

Y de acuerdo con el análisis del código fuente posterior, puede saber que ImportBeanDefinitionRegistrar se carga después de la anotación @Bean, así que modifique ConfigA para eliminar el Bean anotado por @ConditionalOnMissingBean, de lo contrario se generará la ServiceInterface de ConfigA

package com.test;
@EnableService(name = "TestServiceC")
@Configuration
class ConfigA {
//    @Bean
//    @ConditionalOnMissingBean
//    public ServiceInterface getServiceA() {
//        return new ServiceA();
//    }
}
复制代码

Después de ejecutar main, salida: TestServiceC

@Importar análisis de código fuente relacionado

Cargue y analice la anotación @Import cuando BeanFactoryPostProcessor la procese:

El método de actualización de AbstractApplicationContext

-> invokeBeanFactoryPostProcessors (beanFactory);

-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors (beanFactory, getBeanFactoryPostProcessors ());

-> registryProcessor.postProcessBeanDefinitionRegistry (registro);

El registroProcesador aquí, nos referimos a ConfigurationClassPostProcessor

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry (registro)

-> processConfigBeanDefinitions (registro):

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    //省略一些配置检查与设置的逻辑

    //根据@Order注解,排序所有的@Configuration类
    configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

	// 创建ConfigurationClassParser解析@Configuration类
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    //剩余没有解析的@Configuration类
	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	//已经解析的@Configuration类
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
	    //解析
		parser.parse(candidates);
		parser.validate();

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

		// 生成类定义读取器读取类定义
		if (this.reader == null) {
			this.reader = new ConfigurationClassBeanDefinitionReader(
					registry, this.sourceExtractor, this.resourceLoader, this.environment,
					this.importBeanNameGenerator, parser.getImportRegistry());
		}
		this.reader.loadBeanDefinitions(configClasses);
		alreadyParsed.addAll(configClasses);

		candidates.clear();
		if (registry.getBeanDefinitionCount() > candidateNames.length) {
			//省略检查是否有其他需要加载的配置的逻辑
		}
	}
	while (!candidates.isEmpty());

	//省略后续清理逻辑
}
复制代码

Entre ellos, la lógica de parser.parse (candidatos) es implementada principalmente por org.springframework.context.annotation.ConfigurationClassParser, y la función es cargar anotaciones @Import y también anotaciones @Import. La lógica de reader.loadBeanDefinitions (configClasses); se implementa principalmente mediante el método loadBeanDefinitionsForConfigurationClass de org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader. La función es convertir la configuración analizada anteriormente en BeanDefinition, que es la definición de Bean.

1. Cargar la anotación @Import

org.springframework.context.annotation.ConfigurationClassParser

El primero es el método de análisis

public void parse(Set<BeanDefinitionHolder> configCandidates) {
	for (BeanDefinitionHolder holder : configCandidates) {
		BeanDefinition bd = holder.getBeanDefinition();
		try {
			if (bd instanceof AnnotatedBeanDefinition) {
			    //这里的parse实际上就是调用下面即将分析的doProcessConfigurationClass
				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 (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(
					"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
		}
	}
    //最后处理所有的`DeferredImportSelector`,符合上面提到的`DeferredImportSelector`的功能
	this.deferredImportSelectorHandler.process();
}


@Nullable
protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
	//处理`@Component`注解的MemberClass相关代码...
	//处理`@PropertySource`注解相关代码...
	//处理`@ComponentScan`注解相关代码...
    //处理`@Import`注解:
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    //处理`@ImportResource`注解相关代码...
    //处理`@Bean`注解相关代码...
    //处理接口方法相关代码...
    //处理父类相关代码...
}
复制代码

Recopile clases @Import relacionadas mediante el método getImports.

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
	Set<SourceClass> imports = new LinkedHashSet<>();
	Set<SourceClass> visited = new LinkedHashSet<>();
	//递归查询所有注解以及注解的注解是否包含@Import
	collectImports(sourceClass, imports, visited);
	return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {
    //记录是否已经扫描过这个类,如果扫描过就不重复添加,防止重复或者死循环
	if (visited.add(sourceClass)) {
		for (SourceClass annotation : sourceClass.getAnnotations()) {
			String annName = annotation.getMetadata().getClassName();
			//对于非@Import注解,递归查找其内部是否包含@Import注解
			if (!annName.equals(Import.class.getName())) {
				collectImports(annotation, imports, visited);
			}
		}
		//添加@Import注解里面的所有配置类
		imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
	}
}
复制代码

Después de recolectarlo, se puede analizar.

2. Analizar la anotación @Import

El método analítico es: proceso Importaciones

//在解析时,入栈,解析结束后,出栈,通过检查栈中是否有当前类,判断是否有循环依赖
private final ImportStack importStack = new ImportStack();

//记录所有的ImportBeanDefinitionRegistrar
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();

//解析也是递归方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

	if (importCandidates.isEmpty()) {
		return;
	}
    //通过importStack检查循环依赖
	if (checkForCircularImports && isChainedImportOnStack(configClass)) {
		this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
	}
	else {
	    //入栈
		this.importStack.push(configClass);
		try {
			for (SourceClass candidate : importCandidates) {
				if (candidate.isAssignable(ImportSelector.class)) {
					//处理ImportSelector接口的实现类
					Class<?> candidateClass = candidate.loadClass();
					//创建这些Selector实例
					ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
							this.environment, this.resourceLoader, this.registry);
				    //查看是否有过滤器
					Predicate<String> selectorFilter = selector.getExclusionFilter();
					if (selectorFilter != null) {
						exclusionFilter = exclusionFilter.or(selectorFilter);
					}
					//如果是DeferredImportSelector,则用deferredImportSelectorHandler处理
					if (selector instanceof DeferredImportSelector) {
						this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
					}
					else {
					    //如果不是DeferredImportSelector,调用selectImports方法获取要加载的类全限定名称,递归调用本方法继续解析
						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
						processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
					}
				}
				else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
				    
					// 处理ImportBeanDefinitionRegistrar接口的实现类
					Class<?> candidateClass = candidate.loadClass();
					//同样的,创建这些ImportBeanDefinitionRegistrar实例
					ImportBeanDefinitionRegistrar registrar =
							ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
									this.environment, this.resourceLoader, this.registry);
					//放入importBeanDefinitionRegistrar,用于后面加载
				configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
				}
				else {
					//处理@Configuration注解类,或者是普通类(直接生成Bean)
					//在栈加上这个类
					this.importStack.registerImport(
							currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
					//递归回到doProcessConfigurationClass处理@Configuration注解类	processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
				}
			}
		}
		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();
		}
	}
}
复制代码

De esta manera, todas las anotaciones de @Import relacionadas con la clase @Conditional se cargan y analizan, lo cual es un gran proceso recursivo.

3. Convierta a BeanDefinition y regístrese en el contenedor.

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForConfigurationClass 方法:

private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

	if (trackedConditionEvaluator.shouldSkip(configClass)) {
		String beanName = configClass.getBeanName();
		if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
			this.registry.removeBeanDefinition(beanName);
		}
		this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
		return;
	}

    //对Import完成的,加载其Import的BeanDefinition
	if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}
	//加载@Bean注解的方法生成的Bean的Definition
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}
    //@ImportResource 注解加载的
	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	//加载ImportBeanDefinitionRegistrar加载的Bean的Definition
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
复制代码

Desde aquí se puede ver por qué el bean anotado por @Bean se cargará en prioridad al bean devuelto por ImportBeanDefinitionRegistrar.

Enlace original: https://juejin.cn/post/6917771718433964040

Si cree que este artículo es útil para usted, puede seguir mi cuenta oficial y responder a la palabra clave [Entrevista] para obtener una compilación de los puntos de conocimiento básicos de Java y un paquete de regalo para la entrevista. Hay más artículos técnicos de productos secos y materiales relacionados para compartir, ¡que todos aprendan y progresen juntos!

 

Supongo que te gusta

Origin blog.csdn.net/weixin_48182198/article/details/112668093
Recomendado
Clasificación