spring 中的模块装配

 Spring 中一直有类似于 @EnbaleXxx 这样注解一键开启 xxx 功能的支持,甚至连配置都不需要就可以使用,对于接入方来说实在是太爽了。最近在写内部中间件就接触到模块装配的知识点,因为对写过中间件的同学都明白,中间件有2点很重要:1.对于业务领域的高度抽象;2. 让接入方爽!重点来了,怎么让对方爽?说白一点,你把业务方的公用的、但又不属于中间件抽象出来的领域行为通过SDK封装好,让业务方几乎不用配置啥就可以用你的功能,这样他就爽了。下面就展示一下一般的流程:

1. 自定义装配模块的注解 @EnableXxx

 这个自定义注解很简单,唯一要注意的它比一般的注解多一个@Import({ExcelExportSelector.class}),这个很关键,后面会有用。

/**
 * @author weiguo.liu
 * @date 2020/11/17
 * @description excel导出支持的开启配置注解,开启后直接使用 {@link ExcelOptTemplate} 提供的方法即可更为简单的操作 推荐使用
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ExcelExportSelector.class})
public @interface EnableExcelExportSupport {

    /**
     * 导出的场景类型,全局,可多个
     */
    ExcelExportTaskBizType[] types();

}

2. 指明需要装配哪些配置

XxxSelector中说明需要加载哪些配置,这个类必须实现 ImportSelector 接口,在 selectImports 方法中指明所需要加载配置的全类名,返回对象是一个全类名数组。

下面就是加载2个配置:NewExcelExportDubboConfigExcelExportTemplateConfig

/**
 * @author weiguo.liu
 * @date 2020/11/17
 * @description 这里建议直接实现 DeferredImportSelector 接口,他会在所有的 @Bean 生效后再执行
 */
public class ExcelExportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {NewExcelExportDubboConfig.class.getName(), ExcelExportTemplateConfig.class.getName()};
    }
}

3. 提供2中需要导入的配置类

NewExcelExportDubboConfig主要是用来注册一个dubboService:newExcelExportService,这样有个好处,接入方不需要在application-consumer.xml去配置消费者就可以使用我们rpc,非常方便。

/**
 * @author weiguo.liu
 * @date 2020/11/17
 * @description template使用的基础dubbo配置
 */
public class NewExcelExportDubboConfig {

    @Value("${dubbo.reference.version}")
    private String dubboVersion;

    @Autowired
    private ApplicationConfig applicationConfig;
    @Autowired
    private RegistryConfig registry;

    @Bean(name = "newExcelExportService")
    @ConditionalOnBean(value = {ApplicationConfig.class, RegistryConfig.class})
    public NewExcelExportService getNewExcelExportService() {
        ReferenceConfig<NewExcelExportService> reference = new ReferenceConfig<>();
        reference.setApplication(applicationConfig);
        // 多个注册中心可以用setRegistries()
        reference.setRegistry(registry);
        reference.setInterface(NewExcelExportService.class);
        reference.setVersion(dubboVersion);
        reference.setCheck(false);

        return reference.get();
    }
}

再看一下ExcelExportTemplateConfig,这个配置类中主要是注册excelExportTemplate Bean 对象,这个对象中主要是封装了多个像newExcelExportService之类的dubboService,让一些复杂、组合的操作变成一个简单的方式来让接入放调用,所以在注入的时候创建是用的new ExcelExportTemplate(newExcelExportService)。甚至在SDK中我都帮客户端都写好了MQ的监听行为excelExportConsumer,接入想不爽都不行。

/**
 * @author weiguo.liu
 * @date 2021/2/23
 * @description
 */
public class ExcelExportTemplateConfig {
    @Resource
    private NewExcelExportService newExcelExportService;

    /**
     * excel操作模板
     */
    @Bean(name = "excelExportTemplate")
    public ExcelExportTemplate excelOptTemplate() {
        return new ExcelExportTemplate(newExcelExportService);
    }

    @Bean(name = "excelExportConsumer")
    @ConditionalOnBean(value = {ExcelExportTemplate.class, ExcelExportHandler.class})
    public ExcelExportConsumer excelExportConsumer() {
        return new ExcelExportConsumer();
    }
}

稍微瞄一下MQ的监听类

/**
 * @author weiguo.liu
 * @date 2020/11/24
 * @description excel业务校验的基础消息消费类
 */
public class ExcelExportConsumer extends BaseConsumer implements ApplicationContextAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelExportConsumer.class);
    /**
     * 业务方需要监听的mq
     */
    private static final TraceTopic EXCEL_EXPORT_TOPIC = TraceTopic.EXCEL_EXPORT_TASK_CREATE;
    private static ApplicationContext springApplicationContext;
    private Map<Integer, ExcelExportHandler> bizTypeContainer = new HashMap<>(8);

    @Resource
    private ExcelExportTemplate excelExportTemplate;

    @Value("${public.rocketmq.nameserver.name}")
    @Override
    public void setNameServer(String nameServer) {
        this.nameServer = nameServer;
    }

    /**
     * 特殊场景需要,重新定义
     */
    @Value("${rocketmq.consumer.group}")
    @Override
    public void setGroup(String group) {
        this.group = group;
    }

    @PostConstruct
    public void initMethod() throws Exception {
        super.init();

        if (CollectionUtils.isEmpty(bizTypeContainer)) {
            fillBizTypeContainer();
        }
    }

    @PreDestroy
    public void destroyMethod() {
        super.destroy();
    }

    @Override
    public String getTopic() {
        return EXCEL_EXPORT_TOPIC.getTopic();
    }

    @Override
    public String getTags() {
        return EXCEL_EXPORT_TOPIC.getTags();
    }

    @Override
    public boolean doConsumeMessage(String msgId, int reconsumeTimes, Serializable message) {
        LOGGER.info("ExcelExportConsumer receive topic:{} tag:{} message:{}", EXCEL_EXPORT_TOPIC.getTopic(),
            EXCEL_EXPORT_TOPIC.getTags(), JSON.toJSONString(message));
        if (!(message instanceof ExcelExportAddNotifyDTO)) {
            LOGGER.warn(">> useless message,msgId={}, message={}", msgId, JSON.toJSONString(message));
            return true;
        }

        ExcelExportAddNotifyDTO excelTaskNotifyDTO = (ExcelExportAddNotifyDTO)message;
        if (!acceptable(msgId, excelTaskNotifyDTO)) {
            return true;
        }

        // 如果需要自定义检查消息体内容,需要覆盖此方法
        // true 数据合法,业务后端会接着处理业务 | false 数据非法,请求终止,业务方逻辑不会继续处理,直接失败,任务超时
        if (!customCheck()) {
            LOGGER.warn(">> customCheck failed.");
            return true;
        }

        ExcelExportHandler processHandler = bizTypeContainer.get(excelTaskNotifyDTO.getBizType());
        if (processHandler == null) {
            LOGGER.warn(">> found no handler -> can not found excel export handler, type:{}",
                excelTaskNotifyDTO.getBizType());
            return true;
        }

        return processHandler.process(excelTaskNotifyDTO);
    }

    /**
     * 如果需要自定义检查消息体内容,需要覆盖此方法
     * 
     * @return true 数据合法,业务后端会接着处理业务 | false 数据非法,请求终止,业务方逻辑不会继续处理,直接失败
     */
    protected boolean customCheck() {
        return true;
    }

    /**
     * 简单的数据预处理
     *
     * @param excelTaskNotifyDTO
     * @return
     */
    private boolean acceptable(String msgId, ExcelExportAddNotifyDTO excelTaskNotifyDTO) {
        if (excelTaskNotifyDTO == null) {
            LOGGER.warn(">> excelTaskNotifyDTO is null, msgId:{}", msgId);
            return false;
        }

        int bizType = excelTaskNotifyDTO.getBizType();
        if (!CollectionUtils.isEmpty(bizTypeContainer) && !bizTypeContainer.containsKey(bizType)) {
            LOGGER.warn(">> not your msg -> your type is {}, msgId:{}, msg's bizType is {}.",
                JSON.toJSONString(bizTypeContainer), msgId, excelTaskNotifyDTO.getBizCondition());
            return false;
        }

        ExcelExportTaskDO task = excelExportTemplate.getTask(excelTaskNotifyDTO.getId());
        if (task == null) {
            return false;
        }

        if (ExcelExportTaskStatus.isTerminalStatus(task.getStatus())) {
            LOGGER.warn(">> unexpect result -> task's status arrive terminal status, task:{}", JSON.toJSONString(task));
            return false;
        }

        return true;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        springApplicationContext = applicationContext;
    }

    private void fillBizTypeContainer() {
        Map<String, Object> excelExportSupportAnnotation =
            springApplicationContext.getBeansWithAnnotation(EnableExcelExportSupport.class);
        if (CollectionUtils.isEmpty(excelExportSupportAnnotation)) {
            return;
        }

        Map<String, Object> excelExportHandlerAnnotation =
            springApplicationContext.getBeansWithAnnotation(CustomExcelExportHandler.class);
        Map<Integer, ExcelExportHandler> map = new HashMap<>(8);
        excelExportHandlerAnnotation.forEach((k, v) -> {
            Class<?> clazz = v.getClass();
            CustomExcelExportHandler annotation =
                AnnotationUtils.findAnnotation(clazz, CustomExcelExportHandler.class);
            if (annotation != null) {
                ExcelExportTaskBizType type = annotation.type();
                map.put(type.getCode(), (ExcelExportHandler)v);
            }
        });

        excelExportSupportAnnotation.forEach((key, value) -> {
            Class<?> aClass = value.getClass();
            EnableExcelExportSupport annotation =
                AnnotationUtils.findAnnotation(aClass, EnableExcelExportSupport.class);
            if (annotation != null && annotation.types().length > 0) {
                Arrays.asList(annotation.types()).forEach(o -> bizTypeContainer.put(o.getCode(), map.get(o.getCode())));
            }
        });

    }
}

上面的操作完成后,一旦业务方在启动类上添加了注解 @EnableExcelExportSupport 注解后,业务方就会自动注入我们中间件的 dubboSrvice newExcelExportService、封装好的dubboService的操作模板excelExportTemplate以及MQ的消费者。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页