SpringBoot-重要注解(1)

ConfigurationProperties 注解

https://blog.csdn.net/skh2015java/article/details/120141409

@Import、@ImportAutoConfiguration

https://www.cnblogs.com/imyjy/p/16092825.html

当我们需要在一个配置类中导入另一个 Configuration 配置类时,可以使用 @Import@ImportAutoConfiguration 注解:

@Configuration
@Import(MyOtherAutoConfiguration.class)
public class MyAutoConfiguration implements ImportAware {

}

@Configuration
@ImportAutoConfiguration(MyOtherAutoConfiguration.class)
public class MyAutoConfiguration implements ImportAware{

}

查看源码可以发现 @ImportAutoConfiguration 继承了 @Import 注解,所以核心是这个注解:

@Import 注解主要是用来替代 Spring XML 中的 <import/> 标签的,这个标签主要是用来解决配置混乱的,它支持我们将要声明的 bean 分类写入多个 XML 配置文件中。通过 @Import 能够将多个配置类集中整合到一起。但是 @Import 的使用场景十分局限,比如 (1)它导入的配置类集合是固定的无法根据实际情况选择;(2)Import导入的类只能在当前模块所依赖的下游模块中,无法导入一个上游或者第三方配置类。

但是 ImportSelector 接口可以解决上面两个缺陷,它会根据导入的 ImportSelector 实现类所返回的值作为需要导入的配置类集合,并对这些类加载。实例如下,重写 selectImports() 方法,通过获取环境变量中的配置,选择导入哪个类。

// 自定义类选择导入
@Configuration
@Import({MyImportSelector.class})
public class MyAutoConfiguration implements ImportAware {

}

public class MyImportSelector implements ImportSelector, EnvironmentAware {
    private Environment environment;

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        boolean use1 = environment.getProperty("use1", Boolean.class, false);
        String configClass = use1 ? "com.xxx.demo.extension.MyOtherConfiguration1" : "com.xxx.demo.extension.MyOtherConfiguration2";
        return new String[]{configClass};
    }

    @Override
    public void setEnvironment(Environment env) {
        environment = env;
    }
}

同样还有另外一种方式,那就是 ImportBeanDefinitionRegistrar,它的接口中直接提供了 BeanDefinitionRegistry 参数,也就意味着可以直接自主地进行 Bean注册,而不是返回一个类名或者类对象。

@Configuration
@Import({MyImportBeanDefinitionRegistry.class})
public class MyAutoConfiguratoin implements ImportAware {

}

public class MyImportBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean use1 = environment.getProperty("use1", Boolean.class, false);
        String configClass = use1 ? "com.xxx.demo.extension.MyOtherConfiguration1" : "com.xxx.demo.extension.MyOtherConfiguration2";
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(configClass);
        registry.registryBeanDefinition("myOtherConfiguration", beanDefinition);
    }
}

细看 @ImportAutoConfiguration注解

查看 ImportAutoConfiguration 注解,我们可以发现它继承了 ImportAutoConfigurationImportSelector.class 类,进入这个类源码可以发现它继承了 AutoConfigurationImportSelector 类。

AutoConfigurationImportSelector 类内部实现了 selectImports() 方法,该方法返回的全限定类名会被加载进 Bean对象池。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

            // 核心通过这个方法获取候选类名
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
}

getCandidateConfigurations() 方法在 ImportAutoConfigurationImportSelector 类中重新实现了,代码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> candidates = new ArrayList();
    Map<Class<?>, List<Annotation>> annotations = this.getAnnotations(metadata);
    annotations.forEach((source, sourceAnnotations) -> {
        this.collectCandidateConfigurations(source, sourceAnnotations, candidates);
    });
    return candidates;
}

private void collectCandidateConfigurations(Class<?> source, List<Annotation> annotations, List<String> candidates) {
    Iterator var4 = annotations.iterator();

    while(var4.hasNext()) {
        Annotation annotation = (Annotation)var4.next();
        candidates.addAll(this.getConfigurationsForAnnotation(source, annotation));
    }
}

private Collection<String> getConfigurationsForAnnotation(Class<?> source, Annotation annotation) {
    String[] classes = (String[])AnnotationUtils.getAnnotationAttributes(annotation, true).get("classes");
    // 如果ImportAutoConfiguration 注解指定了属性值,那么直接返回 classes的值;如果没有配置属性值,就从 spring.factories 文件中读取 key 为当前配置类名的结果
    return (Collection)(classes.length > 0 ? Arrays.asList(classes) : this.loadFactoryNames(source));
}

所以总结如下:

  1. 先获取当前配置类上的 ImportAutoConfiguration 注解信息;
  2. 如果 ImportAutoConfiguration 注解中指定了classes(或value) 属性值,那么直接返回 classes 的值;
  3. 如果未配置 classes(或value) 属性值,从 spring.factories 文件中读取key为当前配置类名的结果;

@ImportAutoConfiguration 注解使用实例

该注解的使用有以下两种方式:

方式一:直接在注解中绑定

方式二:注解中不指定,在 spring.factories 中指定

posted @ 2024-03-07 19:22  Stitches  阅读(5)  评论(0编辑  收藏  举报