乐趣区

关于springboot:Spring-与自定义注解外部配置化的结合使用

Spring 与自定义注解、内部配置化的联合应用

一、Java 注解的简略介绍

注解,也叫Annotation、标注,是 Java 5 带来的新个性。

  1. 可应用范畴

    类、字段、办法、参数、构造函数、包等,具体可参阅枚举类 java.lang.annotation.ElementType

  2. 生命周期(摘自 刘大飞的博客)

    • RetentionPolicy.SOURCE 注解只保留在源文件,当 Java 文件编译成 class 文件的时候,注解被遗弃
    • RetentionPolicy.CLASS 注解被保留到 class 文件,但 jvm 加载 class 文件时候被遗弃,这是默认的生命周期
    • RetentionPolicy.RUNTIME 注解不仅被保留到 class 文件中,jvm 加载 class 文件之后,依然存在
  3. 应用形式

    能够应用反射获取注解的内容,具体如何应用请本人百度,可参考这篇 Java 注解齐全解析,这里不是重点,不多做介绍

二、Spring 的 @Import注解

@Import 注解是 Spring 用来注入 Spring Bean 的一种形式,能够用来润饰别的注解,也能够间接在 Springboot 配置类上应用。

它只有一个 value 属性须要设置,来看一下源码

public @interface Import {Class<?>[] value();}

这里的 value 属性只承受三种类型的 Class:

  • @Configuration 润饰的配置类
  • 接口 org.springframework.context.annotation.ImportBeanDefinitionRegistrar 的实现类
  • 接口 org.springframework.context.annotation.ImportSelector 的实现类

上面针对三种类型的 Class 别离做简略介绍,两头交叉自定义注解与内部配置的联合应用形式。

三、被@Configuration 润饰的配置类

像 Springboot 中的配置类一样失常应用,须要留神的是,如果该类的包门路已在 Springboot 启动类上配置的扫描门路下,则不须要再从新应用 @Import 导入了,因为 @Import 的目标是注入 bean,然而 Springboot 启动类主动扫描曾经能够注入你想通过 @Import 导入的 bean 了。

这种 Class 能够进行如下拓展

  • 继承各种 Aware 接口,获取对应的信息(如果不分明 Aware 接口在 Spring 当中的作用,请自行百度),如,继承 EnviromentAware,能够拿到 Spring 的环境配置信息,进而从中拿到 @Value 所须要的值,如 environment.getProperty("user.username")
  • 应用 @Autowire@Resource@Value 注入各种所需 Spring 资源
  • 像一般 Spring Bean 一样应用该类

更多应用形式,请自行百度。

四、接口 org.springframework.context.annotation.ImportBeanDefinitionRegistrar 的实现类

@Import 润饰自定义注解时候,通常会导入这品种。

来看一下接口定义

public interface ImportBeanDefinitionRegistrar {
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
            BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);
    }

    /**
    * importingClassMetadata 被 @Import 润饰的自定义注解的元信息,能够取得属性汇合
    * registry Spring bean 注册核心
    **/
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { }

通过这种形式,咱们能够依据自定义注解配置的属性值来注入 Spring Bean 信息。

来看如下案例,咱们通过一个注解,启动 RocketMq 的音讯发送器:

@SpringBootApplication
@EnableMqProducer(group="xxx")
public class App {public static void main(String[] args) {SpringApplication.run(App.class);
    }
}

这是一个服务项目的启动类,这个服务开启了 RocketMq 的一个发送器,并且分到 xxx 组里。

来下一下 @EnableMqProducer 注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({XXXRegistrar.class,XXXConfig.class})
public @interface EnableMqProducer {String group() default "DEFAULT_PRODUCER_GROUP";

    String instanceName() default "defaultProducer";

    boolean retryAnotherBrokerWhenNotStoreOK() default true;}

这里应用 @Import 导入了两个配置类,第一个是接口 org.springframework.context.annotation.ImportBeanDefinitionRegistrar 的实现类,第二个是被@Configuration 润饰的配置类

咱们看第一个类,这个类注入了一个 DefaultMQProducer 到 Spring 容器中,使业务方能够间接通过 @Autowired 注入应用

public class XXXRegistrar implements ImportBeanDefinitionRegistrar {  
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableMqProducer.class.getName()));
        registerBeanDefinitions(attributes, registry);
    }

    private void registerBeanDefinitions(AnnotationAttributes attributes, BeanDefinitionRegistry registry) {
        // 获取配置
        String group = attributes.getString("group");
        // 省略局部代码...
        // 增加要注入的类的字段值
        Map<String, Object> values = new HashMap<>();
        // 这里有的同学可能不分明为什么 key 是这个
        // 这里的 key 就是 DefaultMQProducer 的字段名
        values.put("producerGroup", group);
        // 省略局部代码

        // 注册到 Spring 中
        BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, DefaultMQProducer.class.getName(), DefaultMQProducer.class, values);
    }

到这里,咱们曾经注入了一个 DefaultMQProducer 的实例到 Spring 容器中,然而这个实例,还不残缺,比方,还没有启动,nameServer 地址还没有配置,可内部配置的属性还没有笼罩实例已有的值(nameServer 地址倡议内部配置)。好消息是,咱们曾经能够通过注入来应用这个实例了。

下面遗留的问题,就是第二个类接下来要做的事。

来看第二个配置类

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@EnableConfigurationProperties(XxxProperties.class)  //Spring 提供的配置主动映射性能,配置后可间接注入 
public class XXXConfig {

    @Resource // 间接注入
    private XxxProperties XxxProperties;

    @Autowired // 注入上一步生成的实例
    private DefaultMQProducer producer;

    @PostConstruct
    public void init() {
        // 省略局部代码
        // 获取内部配置的值
        String nameServer = XxxProperties.getNameServer();
        // 批改实例
        producer.setNamesrvAddr(nameServer);
        // 启动实例
        try {this.producer.start();
        } catch (MQClientException e) {throw new RocketMqException("mq 音讯发送实例启动失败", e);
        }
    }

    @PreDestroy
    public void destroy() {producer.shutdown();
    }

到这里,通过自定义注解和内部配置的联合,一个残缺的音讯发送器就能够应用了,但形式有取巧之嫌,因为在音讯发送器启动之前,不晓得还有没有别的类应用了这个实例,这是不平安的。

五、接口 org.springframework.context.annotation.ImportSelector 的实现类

首先看一下接口

public interface ImportSelector {

    /**
     * importingClassMetadata 注解元信息,可获取自定义注解的属性汇合
     * 依据自定义注解的属性,或者没有属性,返回要注入 Spring 的 Class 全限定类名汇合
     如:XXX.class.getName(),Spring 会主动注入 XXX 的一个实例
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

    @Nullable
    default Predicate<String> getExclusionFilter() {return null;}

}

这个接口的实现类如果没有进行 @Aware 拓展,性能比拟繁多,因为咱们无奈参加 Spring Bean 的构建过程,只是通知 Spring 要注入的 Bean 的名字。不再详述。

六、总结

通过接口和配置类的灵便联合,能够实现自定义注解的配置化设计,归根到底是 Spring Bean 的灵便构建,如果你有更好更优雅的形式,欢送留言指教。

退出移动版