Springboot编程思想-@Enable模块驱动

引入@Enable模块驱动的意义在于,可以实现"按需实现",并且可以屏蔽组件集合装备的细节

本文基于小马哥-《springboot编程思想》总结,其中代码和结论均出自《springboot编程思想》

基于注解实现@Enable模块

  1. 声明一个@EnableHelloWorld注解类,并将需要注入的配置类通过@Import方式注入
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
  1. 其中,HelloWorldConfiguration类如下,是一个Configuration类
@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String helloWorld() { 
        return "Hello,World";
    }
}
  1. 最后基于Annotation的方式启动Spring容器,并通过@EnableHelloWorld注解成功注入helloWorld的实例对象
@EnableHelloWorld
@Configuration
public class EnableHelloWorldBootstrap {

    public static void main(String[] args) {
        // 构建 Annotation 配置驱动 Spring 上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册 当前引导类(被 @Configuration 标注) 到 Spring 上下文
        context.register(EnableHelloWorldBootstrap.class);
        // 启动上下文
        context.refresh();
        // 获取名称为 "helloWorld" Bean 对象
        String helloWorld = context.getBean("helloWorld", String.class);
        // 输出用户名称:"Hello,World"
        System.out.printf("helloWorld = %s \n", helloWorld);
        // 关闭上下文
        context.close();
    }
}
  1. 最终输出结果如下
helloWorld = Hello,World 

可以看到,基于注解方式实现@Enable模块驱动还是比较简单的,下面看看基于接口编程的方式来实现@Enable模块驱动

基于接口编程实现@Enable模块

接口编程需要实现ImportSelectorImportBeanDefinitionRegistar接口

  • ImportSeletor接口相对简单,使用Spring注解元信息抽象AnnotaionMetaData作为方法参数,该参数的内容为导入ImportSelector实现的@Configuration类元信息,进而动态地选择一个或者多个其他@Configuration类进行导入。
  • ImportBeanDefinitionRegistar相对于ImportSelector而言,编程复杂度更高,除注解源信息AnnotationData作为入参外,接口将Bean定义(BeanDefinition)的注册交给开发人员。

基于实现ImportSelector接口的实现方式(参考Spring中的@EnableCaching注解)

  1. 定义接口类Server,并且定义服务类型Server.Type
public interface Server {

    /**
     * 启动服务器
     */
    void start();

    /**
     * 关闭服务器
     */
    void stop();

    /**
     * 服务器类型
     */
    enum Type {

        HTTP, // HTTP 服务器
        FTP   // FTP  服务器
    }
}
  1. 定义Server接口的两个实现类,HttpServerFtpServer
@Component // 根据 ImportSelector 的契约,请确保是实现为 Spring 组件
public class HttpServer implements Server {

    @Override
    public void start() {
        System.out.println("HTTP 服务器启动中...");
    }

    @Override
    public void stop() {
        System.out.println("HTTP 服务器关闭中...");
    }
}

@Component // 根据 ImportSelector 的契约,请确保是实现为 Spring 组件
public class FtpServer implements Server {

    @Override
    public void start() {
        System.out.println("FTP 服务器启动中...");
    }

    @Override
    public void stop() {
        System.out.println("FTP 服务器关闭中...");
    }
}
  1. 实现@Enable注解驱动的Annotation—@EnableServer,同时将枚举类型Server.Type作为@EnableServer的属性值
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServerImportSelector.class) // 导入 ServerImportSelector
public @interface EnableServer {

    /**
     * 设置服务器类型
     * @return non-null
     */
    Server.Type type();
}
  1. 实现ImportSelector
public class ServerImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 读取 EnableServer 中的所有的属性方法,本例中仅有 type() 属性方法
        // 其中 key 为 属性方法的名称,value 为属性方法返回对象
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableServer.class.getName());
        // 获取名为"type" 的属相方法,并且强制转化成 Server.Type 类型
        Server.Type type = (Server.Type) annotationAttributes.get("type");
        // 导入的类名称数组
        String[] importClassNames = new String[0];
        switch (type) {
            case HTTP: // 当设置 HTTP 服务器类型时,返回 HttpServer 组件
                importClassNames = new String[]{HttpServer.class.getName()};
                break;
            case FTP: //  当设置 FTP  服务器类型时,返回 FtpServer  组件
                importClassNames = new String[]{FtpServer.class.getName()};
                break;
        }
        return importClassNames;
    }
}
  1. 创建基于Annotation的Spring启动引导类,并标注@EnableServer注解
@Configuration
@EnableServer(type = Server.Type.HTTP) // 设置 HTTP 服务器
public class EnableServerBootstrap {

    public static void main(String[] args) {
        // 构建 Annotation 配置驱动 Spring 上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册 当前引导类(被 @Configuration 标注) 到 Spring 上下文
        context.register(EnableServerBootstrap.class);
        // 启动上下文
        context.refresh();
        // 获取 Server Bean 对象,实际为 HttpServer
        Server server = context.getBean(Server.class);
        // 启动服务器
        server.start();
        // 关闭服务器
        server.stop();
        // 关闭上下文
        context.close();
    }
}

控制台结果如下:

HTTP 服务器启动中...
HTTP 服务器关闭中...

引导类启动后可以正常获取Server Bean,并且可以通过@EnableServer注解中的属性Server.Type来切换Server Bean的实际注入对象

基于实现ImportBeanDefinitionRegistar接口的实现方式

可以基于ImportSelector接口案例做调整即可

  1. 实现 ImportBeanDefinitionRegistar 接口:ServerImportBeanDefinitionRegistrar
public class ServerImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 复用 {@link ServerImportSelector} 实现,避免重复劳动
        ImportSelector importSelector = new ServerImportSelector();
        // 筛选 Class 名称集合
        String[] selectedClassNames = importSelector.selectImports(importingClassMetadata);
        // 创建 Bean 定义
        Stream.of(selectedClassNames)
                .map(BeanDefinitionBuilder::genericBeanDefinition) // 转化为 BeanDefinitionBuilder 对象
                .map(BeanDefinitionBuilder::getBeanDefinition)     // 转化为 BeanDefinition
                .forEach(beanDefinition ->
                        // 注册 BeanDefinition 到 BeanDefinitionRegistry
                        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry)
                );
    }
}
  1. 替换@EnableServer注解中元注解@Import的属性
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//@Import(ServerImportSelector.class) // 导入 ServerImportSelector
@Import(ServerImportBeanDefinitionRegistrar.class) // 替换 ServerImportSelector
public @interface EnableServer {

    /**
     * 设置服务器类型
     * @return non-null
     */
    Server.Type type();
}
  1. 重启引导类EnableServerBootstrap,观察控制台输出
HTTP 服务器启动中...
HTTP 服务器关闭中...

运行结果表明重构是成功的

你可能感兴趣的:(读后总结)