Каждый день — роль и использование аннотации Spring @Import

Spring

@Импортировать аннотацию

@ImportдаSpringОсновной компонент конфигурации на основе аннотаций Java.@ImportАннотации обеспечивают@BeanФункция аннотации, как и оригиналаSpringНа основе файла конфигурации xml<import>Функция тегов для организации нескольких разбросанных файлов xml, конечно, здесь заключается в организации нескольких разбросанных файлов xml.@Configurationтип.

Далее будет описано соответственно@ImportФункция аннотации.

1. Введите другие @Configuration

Предположим, у вас есть следующий интерфейс и два класса реализации:

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");
    }
}

два@ConfigurationConfigA``@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();
    }
}

пройти черезConfigAСоздайтеAnnotationConfigApplicationContext,ПолучатьServiceInterface, чтобы увидеть, какая реализация:

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

Результат:ServiceB.доказывать@ImportОпределение класса загружается вместо собственного.

2. Напрямую инициализировать bean-компоненты других классов

существуетSpring 4.2после,@ImportВы можете напрямую указать класс сущности и загрузить определение класса вcontextсередина. Например, в приведенном выше кодеConfigAиз@Importпревратиться в@Import(ServiceB.class), будет генерироватьServiceBизBeanв контекст контейнера, а затем запуститеmainметод, вывод:ServiceB.доказывать@Importопределения классов загружаются в первую очередь, чем их собственные.

3. Укажите класс, реализующий ImportSelector (и DeferredServiceImportSelector) для персонализированной загрузки.

указанная реализацияImportSelectorкласс, черезAnnotationMetadataсвойства внутри динамически загружаемых классов.AnnotationMetadataдаImportАтрибут класса аннотации (если класс является классом аннотации, он распространяется на класс, не являющийся аннотацией, к которому применяется этот класс аннотации).

необходимо реализоватьselectImportsметод, который возвращает@Configuationили конкретныйBeanполное имя классаStringмножество.

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();
    }
}

бежать сноваmainметод, вывод:ServiceB.доказывать@ImportОпределение класса загружается вместо собственного. Как правило, если структура основана наAnnotationMetadataПараметры класса динамической загрузки, вообще пишут доп.EnableАннотация, используемая вместе. Например:

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

После этого вConfigAдобавить заметки в@EnableService(name = "B")

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

бежать сноваmainметод, вывод:ServiceB.

также может быть достигнутоDeferredImportSelectorинтерфейс, такselectImportsВсе возвращенные классы загружаются последними, а не как@ImportКак уже говорилось, сначала загрузите. Например:

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

ИсправлятьEnableServiceаннотация:

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

такConfigAиметь приоритет надDefferredServiceImportSelectorвозвращениеConfigBзагрузить, выполнитьmainметод, вывод:ServiceA

4. Укажите класс, реализующий ImportBeanDefinitionRegistrar для персонализированной загрузки.

иImportSelectorИспользование похоже на Цель, но если мы хотим переопределитьBean, такие как динамическое внедрение свойств, изменениеBeanтип иScopeПодождите, это нужно реализовать, указавImportBeanDefinitionRegistrarреализация класса. Например:

определение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);
    }
}

определениеServiceImportBeanDefinitionRegistrarдинамическая регистрацияServiceC,Исправлять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());
    }
}

И, согласно следующему анализу исходного кода, мы можем знать,ImportBeanDefinitionRegistrarсуществует@BeanПосле того, как аннотация загружена, ее необходимо изменитьConfigAудалить@ConditionalOnMissingBeanаннотированныйBean, иначе он обязательно сгенерируетConfigAизServiceInterface

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

бежать заmain, вывод:TestServiceC

Анализ исходного кода, связанный с @Import

Разбор нагрузки@ImportАннотации расположены по адресуBeanFactoryPostProcessorПри обработке:

AbstractApplicationContextизrefreshметод

-> invokeBeanFactoryPostProcessors(beanFactory);

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

-> registryProcessor.postProcessBeanDefinitionRegistry(registry);

здесьregistryProcessor, мы ссылаемся наConfigurationClassPostProcessor

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)

-> processConfigBeanDefinitions(registry):

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());

	//省略后续清理逻辑
}

вparser.parse(candidates)Логика состоит в основном изorg.springframework.context.annotation.ConfigurationClassParserреализация, функция заключается в загрузке@ImportАннотация и непосредственно@Importаннотация.reader.loadBeanDefinitions(configClasses);Логика состоит в основном изorg.springframework.context.annotation.ConfigurationClassBeanDefinitionReaderизloadBeanDefinitionsForConfigurationClassМетод реализован, и функция состоит в том, чтобы преобразовать разобранную выше конфигурацию вBeanDefinitionэтоBeanопределение.

1. Загрузите аннотацию @Import

org.springframework.context.annotation.ConfigurationClassParser

прежде всегоparseметод

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`注解相关代码...
    //处理接口方法相关代码...
    //处理父类相关代码...
}

пройти черезgetImportsметод, собирая соответствующие@Importкласс внутри.

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"));
	}
}

После сбора его можно проанализировать.

2. Разберите аннотацию @Import

Способ разбора:processImports

//在解析时,入栈,解析结束后,出栈,通过检查栈中是否有当前类,判断是否有循环依赖
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();
		}
	}
}

Таким образом, все@Conditionalсвязанный с классом@ImportАннотация загружается и анализируется, что представляет собой большой рекурсивный процесс.

3. Преобразование в BeanDefinition и регистрация в контейнере

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());
}

Отсюда мы можем понять, почему мы сказали ранее@BeanаннотированныйBeanбудет иметь приоритет надImportBeanDefinitionRegistrarвозвращениеBeanнагрузка.