image

SpringBoot多模块项目,模块开启和注册的一种思路实现

  • WORDS 6213

SpringBoot多模块项目中使用@EnableXXX注解和模块注册配置来启用某个模块

SpringBoot多模块项目中(以我的聚合支付项目为例),定义了一个核心包和众多的模块子包。在核心包中定义整个项目的自动配置以及模块注册器、异常捕获、顶级接口等核心类,在模块子包中完成各模块的功能然后向核心包注册,并通过 @EnableXXX@AutoConfigure等注解可以非常方便的完成模块和项目的自动配置。

定义核心包中的模块注册类和项目自动配置类

先是模块注册器,传入模块的名称创建一个注册器对象

/**
 * @Author: lisang
 * @DateTime: 2023/7/9 下午11:07
 * @Description: 支付模块注册器
 */
public class ModuleRegistration {
    private final String moduleName;

    public ModuleRegistration (String moduleName) {
        Assert.notNull(moduleName, "支付模块名称不能为空");
        this.moduleName = moduleName;
    }

    public String getModuleName() {
        return moduleName;
    }
}

再是注册中心,调用 addModule方法后,会将每个模块的注册器对象保存到 registrationList集合中

/**
 * @Author: lisang
 * @DateTime: 
 * @Description: 支付Module注册中心
 */
public class ModuleRegistry {
    private final List<ModuleRegistration> registrationList = new ArrayList<>();

    public ModuleRegistry(){

    }

    /**
     * 添加模块
     * @param moduleName 模块名称
     */
    public void addModule(String moduleName) {
        registrationList.add(new ModuleRegistration(moduleName));
    }

    /**
     * 获取模块列表
     * @return 返回模块名称列表
     */
    public List<String> getModuleNames() {
        return registrationList.stream().map(ModuleRegistration::getModuleName).toList();
    }
}

然后是 PayModuleConfigurer接口,每个模块需要实现该接口并重写里面的方法

/**
 * @Author: lisang
 * @DateTime: 
 * @Description: 支付模块配置接口
 */
public interface PayModuleConfigurer {
    // 子模块调用 registry.addModule()方法注册模块
    default void addModule(ModuleRegistry registry) {};
}

最后是核心包中项目自动配置类,生成一个 ModuleRegistry对象,确保在项目中注册器始终是单例的,然后拿到所有 PayModuleConfigurer接口的实现类,并传入生成的 ModuleRegistry对象,那么所有模块都会注册到这个对象中。这里需要确保所有模块的自动配置类在项目自动配置类之前运行

/**
 * @Author: lisang
 * @DateTime: 
 * @Description: 支付项目核心配置类
 */
@Configuration
public class PayAutoConfiguration {
    private final ModuleRegistry moduleRegistry;

    public PayAutoConfiguration(List<PayModuleConfigurer> configurers) {
        moduleRegistry = new ModuleRegistry();
        if(!CollectionUtils.isEmpty(configurers)){
            configurers.forEach(module -> module.addModule(moduleRegistry));
        }
    }

    @Bean
    public ModuleRegistry moduleRegistry() {
        return moduleRegistry;
    }
}

子模块中的实现

实现 PayModuleConfigurer支付模块配置接口

/**
 * @Author: lisang
 * @DateTime: 
 * @Description:
 */
public class AlipayModuleConfig implements PayModuleConfigurer {
    @Override
    public void addModule(ModuleRegistry registry) {
        // 传入模块名字 注册模块
        registry.addModule("alipay");
    }
}

开启子模块的 @EnableAlipayPay注解

/**
 * @Author: lisang
 * @DateTime: 
 * @Description: 开启支付宝支付的启动注解
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 条件注入 项目中必须有这个类才注入当前类
@ConditionalOnClass(AlipayClient.class)
// 导入支付宝模块的自动配置类
@Import(AlipayPayAutoConfiguration.class)
public @interface EnableAlipayPay {
}

支付宝子模块的自动配置类

/**
 * @Author: lisang
 * @DateTime: 
 * @Description: 支付宝支付自动配置类
 */

// 确保模块的自动配置在项目自动配置类之前执行
@AutoConfigureBefore(PayAutoConfiguration.class)
// 为类开启配置文件绑定
@EnableConfigurationProperties(AlipayPayProperties.class)
public class AlipayPayAutoConfiguration {
    // 注入模块中需要的Bean
  
    @Bean
    public AlipayModuleConfig alipayModuleConfig() {
        return new AlipayModuleConfig();
    }
    @Bean
    @ConditionalOnClass(AlipayPayProperties.class)
    public AlipayClient alipayClient(@NotNull AlipayPayProperties alipayPayProperties) throws AlipayApiException {
        AlipayConfig config = new AlipayConfig();
        config.setServerUrl(alipayPayProperties.getServerUrl());
        config.setAppId(alipayPayProperties.getAppId());
        config.setPrivateKey(alipayPayProperties.getPrivateKey());
        config.setAlipayPublicKey(alipayPayProperties.getPublicKey());
        config.setSignType(alipayPayProperties.getSignType());
        config.setCharset(alipayPayProperties.getCharSet());
        return new DefaultAlipayClient(config);
    }
    @Bean
    public AlipayPayBusiness alipayPayBusiness(AlipayClient alipayClient, AlipayPayProperties alipayPayProperties){
        return new AlipayPayBusiness(alipayClient, alipayPayProperties);
    }
    @Bean
    public PayTemplate alipayPayTemplate(AlipayPayBusiness alipayPayBusiness){
        return new AlipayPayTemplate(alipayPayBusiness);
    }
}

修改启动类和配置文件

首先在启动类上标注 @EnableAlipayPay注解,表示要开启支付宝支付模块,然后去配置文件中补全支付宝模块所需要的配置信息即可完成自动配置。

@SpringBootApplication
@EnableUnionPay
@EnablePaypalPay
public class PayApplication {
    public static void main(String[] args) {
        SpringApplication.run(PayApplication.class);
    }
}
pay:
  alipay:
    app-id:
    seller-id:
    sign-type:
    char-set:
    public-key:
    private-key:
    server-url:
    notify-url:
    format: 

模块注册的应用

我的项目中需要根据实现的模块来动态注入 RabbitMQQueueKafkaTopic,这样就可以很方便的实现

@Override
public void run(ApplicationArguments args) throws Exception {
    logger.info("开始运行Rabbit的自动配置...");
    // 拿到所有注册的模块
    List<String> moduleNames = moduleRegistry.getModuleNames();
    if (CollectionUtils.isEmpty(moduleNames)){
        logger.warn("自动配置结束,没有已注册的支付模块...");
        return;
    }
    logger.info("获取支付模板成功,注册模块数量:{}", moduleNames.size());
    // 通过模块名进行自动配置,并对Queue进行统一管理
    moduleNames.forEach(moduleName -> {
        queueManager.addQueue(moduleName);
        String successName = queueManager.getSuccessName(moduleName);
        String refundName = queueManager.getRefundName(moduleName);
        amqpAdmin.declareQueue(new Queue(successName));
        amqpAdmin.declareQueue(new Queue(refundName));
        String successKey = queueManager.getSuccessKey(moduleName);
        String refundKey = queueManager.getRefundKey(moduleName);
        amqpAdmin.declareBinding(
            new Binding(successName, Binding.DestinationType.QUEUE, exchangeName, successKey, null)
        );
        logger.info("{}绑定Queue到Exchange成功,queueName:{},bindingKey:{}", moduleName, successName, successKey);
        amqpAdmin.declareBinding(
            new Binding(refundName, Binding.DestinationType.QUEUE, exchangeName, refundKey, null)
        );
        logger.info("{}绑定Queue到Exchange成功,queueName:{},bindingKey:{}", moduleName, refundName, refundKey);
    });
    logger.info("Rabbit的自动配置运行结束...");
}

关联文章

0 条评论