Spring擴展——@Import注解


引言

在Spring中有許多Enable開頭的注解,比如以下常見注解
@EnableTransactionManagement
@EanbleAsync
@EnableCache
@EnableAspectJAutoProxy
@EnableSchedulin
這些注解是在什么時候,什么地方被處理的呢?
我們在另一篇博客里面可以找到相應的答案——Spring源碼——ConfigurationClassPostProcessor類
ConfigurationClassPostProcessor類繼承關系圖

@Import原理

@Import源碼

/**
 * Indicates one or more <em>component classes</em> to import &mdash; typically
 * {@link Configuration @Configuration} classes.
 *
 * <p>Provides functionality equivalent to the {@code <import/>} 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.
 * 此注解,如果被掃描到,ConfigurationClassPostProcessor在處理的時候,會掃描其value字段,會根據Value字段的類型不同,分別調用其不同的處理方法。
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
 * @see Configuration
 * @see ImportSelector
 * @see ImportBeanDefinitionRegistrar
 * @see ImportResource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * 可以引入普通Bean 配置Bean ImportSelector指定的名字的對象 ImportBeanDefinitionRegistrar手動向Register里面注冊BennDefinition
	 * ImportSelector  ImportBeanDefinitionRegistrar 本身的實現類,也是作為一個Bean的,只不過它還能引入其它類。
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();

}

ConfigurationClassPostProcessor屬於BDRPP,所以在執行InvokeBeanFactoryPostProcessor的時候,會先執行其postProcessBeanDefinitionRegistry方法,通過這個方法可以向容器里面添加更多的BeanDefinition信息。
在執行其postProcessBeanDefinitionRegistry的時候,會遍歷當前容器里BeanDefinitionsMap里面所有的Bean信息,如果是Configuration配置類,則會處理其@Import注解,在處理的時候,是遞歸進行處理的。
ConfigurationClassPostProcessor在處理Import注解的時候,會掃描其value字段,會根據Value字段的類型不同,分別調用其不同的處理方法。

// 遍歷每一個@Import注解的類
				for (SourceClass candidate : importCandidates) {
					// 檢驗配置類Import引入的類是否是ImportSelector子類
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						// 候選類是一個導入選擇器->委托來確定是否進行導入
						Class<?> candidateClass = candidate.loadClass();
						// 通過反射生成一個ImportSelect對象
						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接口的實例
						// 如果是則應用選擇器將會在所有的配置類都加載完畢后加載
						if (selector instanceof DeferredImportSelector) {
							// 將選擇器添加到deferredImportSelectorHandler實例中,預留到所有的配置類加載完成后統一處理自動化配置類
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
							// 獲取引入的類,然后使用遞歸方式將這些類中同樣添加了@Import注解引用的類
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
							// 遞歸處理,被Import進來的類也有可能@Import注解
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
						}
					}
					// 如果是實現了ImportBeanDefinitionRegistrar接口的bd
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						// 候選類是ImportBeanDefinitionRegistrar  -> 委托給當前注冊器注冊其他bean
 						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						/**
						 * 放到當前configClass的importBeanDefinitionRegistrars中
						 * 在ConfigurationClassPostProcessor處理configClass時會隨之一起處理
						 */
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						// 候選類既不是ImportSelector也不是ImportBeanDefinitionRegistrar-->將其作為@Configuration配置類處理
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						/**
						 * 如果Import的類型是普通類,則將其當作帶有@Configuration的類一樣處理
						 * 將candidate構造為ConfigurationClass,標注為importedBy,意味着它是通過被@Import進來的
						 * 后面處理會用到這個判斷將這個普通類注冊進DefaultListableBeanFactory
						 */
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}

@Import在處理的時候,根據它的value值的類型來選擇不同的處理流程

@Import引入Bean

1. ImportSelector

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 * @return the class names, or an empty array if none
	 * 選擇並返回應基於哪個類導入的名稱
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	/**
	 * Return a predicate for excluding classes from the import candidates, to be
	 * transitively applied to all classes found through this selector's imports.
	 * <p>If this predicate returns {@code true} for a given fully-qualified
	 * class name, said class will not be considered as an imported configuration
	 * class, bypassing class file loading as well as metadata introspection.
	 * @return the filter predicate for fully-qualified candidate class names
	 * of transitively imported configuration classes, or {@code null} if none
	 * @since 5.2.4
	 * 需要排除那項類的名稱
	 */
	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}
}

如果@Import引入的是ImportSelector的子類,將它添加到deferredImportSelectorHandler實例中,預留到所有的配置類加載完成后統一處理自動化配置類
此類的主要功能是:根據給定的條件(通常是一個或多個注解屬性)判斷要導入哪個配置類

2. ImportBeanDefinitionRegistrar

ublic interface ImportBeanDefinitionRegistrar {
	/**
	 * @since 5.2
	 * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
	 * @see ConfigurationClassPostProcessor#setBeanNameGenerator
	 */
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {
		registerBeanDefinitions(importingClassMetadata, registry);
	}
	/**
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * <p>The default implementation is empty.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}
}

如果@Import引入的是ImportBeanDefinitionRegistrar的子類,將它添加到ConfigurationClass對象的importBeanDefinitionRegistrars集合中,待后面統一進行處理
它的功能主要是:可以非常靈活地手動注冊BeanDefinitions信息

3. 引入普通Bean對象

如果是引入的普通Bean對象,則將其看作是Configuration配置類進行處理,遞歸處理

@Import擴展

Import引入普通Bean對象

在需要的時候,通過注解的形式,在項目中引入SpringUtil工具類

1.自定義注解@EnableSpringUtil

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SpringUtil.class)
public @interface EnableSpringUtil {

}

2.SpringUtil工具類

public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    /**
     * 注入ApplicationContext
     *
     * @param applicationContext the ApplicationContext object to be used by this object
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

在獲取到ApplicationContext后,就可以在代碼里面獲取任意指定的Bean對象了。
@EnableSpringUtil注解作為一個第三方包的注解,我們在需要的項目中,只需要在主類上面寫上此注解,就可以優雅地使用SpringUtil工具類了

Import之ImportSelector擴展

通過自定義注解上的參數,選擇引入不同的Bean組件

1.自定義注解@EnableXXX

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(XXXSelector.class)
public @interface EnableXXX {
    PersonRole value() default PersonRole.TEACHER;
}

2.PersonRole枚舉類

public enum  PersonRole {
    TEACHER,
    STUDENT;
}

3.實現XXXSelector

public class XXXSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        MergedAnnotations annotations = importingClassMetadata.getAnnotations();
        PersonRole xxValue = annotations.get(EnableXXX.class).getEnum("value", PersonRole.class);

        switch (xxValue) {
            case STUDENT:
                return new String[]{StudentService.class.getName()};
            case TEACHER:
                return new String[]{TeacherService.class.getName()};
            default:
                return new String[]{TeacherService.class.getName()};
        }
    }
}

根據@EnableXXX注解的參數值引入不同的Bean對象

4.使用@EnableXXX

@EnableAspectJAutoProxy
@Configuration
@EnableXXX(PersonRole.TEACHER)
public class XXXConfiguration {

}

在需要使用的地方,寫上@EnableXXX,並寫上指定的參數就可以使用了

Import之ImportBeanDefinitionRegistrar擴展

通過@EnableCustomComponent注解,將我們指定包里面的,同時被標注了指定注解的類,引入到容器中作為Bean對象

1.自定義注解@EnableCustomComponent

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CustomImportBeanDefinitionRegistrar.class)
public @interface EnableCustomComponent {
    //需要掃描的包路徑
    String[] basePackage();
    //需要掃描的注解
    Class<? extends Annotation>[]  annotations();

}

2.自定義一個需要被掃描到的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomComponent {

}

如果指定包里面的類,被標注了此注解,這個類就會被掃描到,加入到容器中作為Bean組件

3.實現CustomImportBeanDefinitionRegistrar

public class CustomImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("registerBeanDefinitions2");
        ClassPathBeanDefinitionScanner scanner
                = new ClassPathBeanDefinitionScanner(registry, false);
        MergedAnnotation<EnableCustomComponent> enableCustomComponentMergedAnnotation
                = importingClassMetadata.getAnnotations().get(EnableCustomComponent.class);
        String[] basePackages = enableCustomComponentMergedAnnotation.getStringArray("basePackage");
        Class<?>[] annotations = enableCustomComponentMergedAnnotation.getClassArray("annotations");
        for (String basePackage : basePackages) {
            System.out.println(basePackage);
        }
        for (Class<?> annotation : annotations) {
            scanner.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) annotation));
        }
        int scan = scanner.scan(basePackages);
        System.out.println("scan result " + scan);
    }
}

4.使用@EnableCustomComponent注解

@Configuration
@EnableCustomComponent(
        basePackage = "com.mashibing.ycb.importtest.test3.service",
        annotations = {CustomComponent.class, CustomComponent2.class})
public class KKKConfiguration {
}

這樣就實現了,通過一個注解,將某一個包里面的被指定注解標注了的類,掃描到Bean容器中來


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM