You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 21 JPMS模块下Micronaut动态注册/注销@Controller咨询

动态注册/注销Micronaut控制器及Groovy脚本热加载方案

一、JPMS模块下动态注册/注销@Controller类

动态注册可行,需适配JPMS规则

Micronaut的BeanContext支持手动注册Bean,包括带@Controller注解的类,只要满足JPMS模块的访问规则即可:

  1. JPMS模块配置:目标API模块需要在module-info.java中exports控制器所在包给Micronaut核心模块(如io.micronaut.core、io.micronaut.web),或者用opens包允许Micronaut进行反射操作(因为控制器依赖注解扫描和反射实例化)。
  2. 注册示例:
    BeanContext beanContext = BeanContext.current();
    // 从独立JPMS模块加载控制器类
    Class<?> controllerClass = Class.forName("com.example.user.api.UserController", true, getClass().getClassLoader());
    // 注册为Singleton Bean,Micronaut自动识别@Controller并生成路由
    beanContext.registerSingleton(controllerClass);
    

注销控制器的实现方式

Micronaut没有直接的注销API,但可以通过以下步骤完成:

  1. 销毁Bean实例:如果控制器实现了DisposableBean或Closeable接口,调用对应的销毁方法清理资源。
  2. 移除路由:通过MutableRouter过滤并移除目标控制器对应的路由:
    MutableRouter router = beanContext.getBean(MutableRouter.class);
    router.remove(route -> route.getTargetMethod().getDeclaringClass().equals(controllerClass));
    
  3. 移除Bean定义:调用beanContext.removeBeanDefinition(controllerClass),注意需确保没有其他Bean依赖该控制器,避免引发依赖错误。

多JPMS模块的协作

每个独立API模块依赖共同父层模块,父层可以提供统一的注册服务,负责加载子模块的控制器类并注册到全局BeanContext。子模块需在module-info.java中声明对父模块的依赖,并开放控制器包给Micronaut。

二、监听目录动态加载Groovy脚本@Controller

该场景完全可行,核心是结合WatchService监听目录变化,通过Groovy类加载器加载脚本并注册到Micronaut:

1. 监听目录文件变化

使用WatchService监听目标目录的创建/修改事件,触发脚本加载逻辑:

WatchService watchService = FileSystems.getDefault().newWatchService();
Path controllerDir = Paths.get("./dynamic-groovy-controllers");
// 监听文件创建和修改事件
controllerDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);

// 启动线程处理监听事件
new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        WatchKey key;
        try {
            key = watchService.take();
            for (WatchEvent<?> event : key.pollEvents()) {
                if (event.kind() == StandardWatchEventKinds.OVERFLOW) continue;
                Path fileName = (Path) event.context();
                if (fileName.toString().endsWith(".groovy")) {
                    loadGroovyController(controllerDir.resolve(fileName));
                }
            }
            key.reset();
        } catch (InterruptedException | IOException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
}).start();

2. 加载Groovy脚本并注册

使用GroovyClassLoader解析脚本类,先注销旧Bean(如果存在)再注册新实例:

private void loadGroovyController(Path filePath) throws IOException {
    GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader());
    Class<?> controllerClass = classLoader.parseClass(Files.newBufferedReader(filePath));

    BeanContext beanContext = BeanContext.current();
    // 先清理旧的Bean和路由
    if (beanContext.containsBean(controllerClass)) {
        beanContext.removeBeanDefinition(controllerClass);
        MutableRouter router = beanContext.getBean(MutableRouter.class);
        router.remove(route -> route.getTargetMethod().getDeclaringClass().equals(controllerClass));
    }
    // 注册新的控制器Bean
    beanContext.registerSingleton(controllerClass);
}

3. 注意事项

  • Groovy脚本必须包含@Controller及对应的路由注解(如@Get、@Post),符合Micronaut控制器规范。
  • 类加载器管理:频繁修改脚本时,建议为每个脚本创建独立类加载器,或使用Micronaut的ClassLoaderScope隔离动态类,避免版本冲突。
  • JPMS环境下需配置动态模块权限,确保Micronaut能加载外部目录的类。

内容的提问来源于stack exchange,提问作者mah454

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 06:35:58