使用 Spring Boot + Javassist 实现运行时动态添加和删除 Controller

引言

在某些特殊场景下,我们可能需要在 Spring Boot 应用运行时动态地添加或删除 Controller。本文将详细介绍如何使用 Javassist 和 Spring Boot 的内部机制来实现这个功能。

技术栈

  • Spring Boot 2.7.0
  • Javassist 3.29.0-GA
  • Java 8+

Maven 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.0-GA</version>
</dependency>
</dependencies>

核心实现思路

实现动态 Controller 需要完成以下几个关键步骤:

  1. 使用 Javassist 动态生成 Controller 类
  2. 将生成的类注册到 Spring 容器
  3. 注册请求映射到 Spring MVC 的处理器映射中
  4. 实现删除功能时的清理工作

详细实现

管理类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@Component
public class DynamicControllerManager {
@Autowired
private GenericApplicationContext applicationContext;

@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;

private final Map<String, Class<?>> pathToControllerMap = new HashMap<>();
private final Map<String, Object> controllerInstanceMap = new HashMap<>();

// ... 实现方法
}

动态添加 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@GetMapping("/addController")
public String addController(@RequestParam String api, @RequestParam String returnMsg) {
try {
// 1. 使用 Javassist 创建新的 Controller 类
ClassPool pool = ClassPool.getDefault();
pool.appendClassPath(new ClassClassPath(this.getClass()));
CtClass ctClass = pool.makeClass("com.example.controller.Dynamic" + api + "Controller");

// 2. 添加类级别的 @RestController 注解
addRestControllerAnnotation(ctClass);

// 3. 创建处理方法并添加 @GetMapping 注解
addRequestMethod(ctClass, api, returnMsg);

// 4. 生成类并创建实例
Class<?> controllerClass = ctClass.toClass();
Object controller = controllerClass.getDeclaredConstructor().newInstance();

// 5. 注册到 Spring 容器
registerBean(api, controllerClass, controller);

// 6. 注册请求映射
registerRequestMapping(api, controllerClass, controller);

// 7. 保存映射关系
pathToControllerMap.put(api, controllerClass);
controllerInstanceMap.put(api, controller);

return "Successfully added controller for /" + api;
} catch (Exception e) {
return "Failed to add controller: " + e.getMessage();
}
}

删除 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@GetMapping("/delController")
public String deleteController(@RequestParam String api) {
try {
// 1. 获取 Controller 信息
Class<?> controllerClass = pathToControllerMap.get(api);
Object controller = controllerInstanceMap.get(api);

// 2. 注销请求映射
unregisterRequestMapping(api, controllerClass);

// 3. 从 Spring 容器中移除
String beanName = api + "Controller";
((GenericApplicationContext) applicationContext)
.removeBeanDefinition(beanName);

// 4. 清理缓存
pathToControllerMap.remove(api);
controllerInstanceMap.remove(api);

return "Successfully removed controller for /" + api;
} catch (Exception e) {
return "Failed to remove controller: " + e.getMessage();
}
}

关键技术点解析

使用 Javassist 生成类

Javassist 是一个强大的字节码操作库,我们使用它来:

  • 动态创建类
  • 添加注解
  • 创建方法
  • 设置方法体

Spring MVC 请求映射

注册请求映射的关键代码:

1
2
3
4
5
6
7
8
RequestMappingInfo mappingInfo = RequestMappingInfo
.paths("/" + api)
.build();
requestMappingHandlerMapping.registerMapping(
mappingInfo,
controller,
controllerMethod
);

Spring 容器注册

使用 GenericApplicationContext 注册 Bean:

1
2
3
4
5
applicationContext.registerBean(
beanName,
typedClass,
() -> controller
);

使用示例

  1. 添加新的 Controller:
1
GET http://localhost:8080/addController?api=test&returnMsg=Hello
  1. 访问新添加的接口:
1
2
GET http://localhost:8080/test
// 返回: Hello
  1. 删除 Controller:
1
GET http://localhost:8080/delController?api=test

注意事项

  1. 内存管理

    • 要注意清理所有相关的引用
    • 适当管理类加载器资源
  2. 线程安全

    • 注意并发操作的处理
    • 考虑添加适当的同步机制
  3. 错误处理

    • 完善的异常处理机制
    • 适当的日志记录
  4. 安全考虑

    • 添加适当的访问控制
    • 考虑添加验证机制

可能的扩展

  1. 支持更多 HTTP 方法(POST、PUT 等)
  2. 支持请求参数的动态配置
  3. 支持返回复杂对象
  4. 添加监控和统计功能
  5. 实现热重载功能

总结

通过结合使用 Javassist 和 Spring Boot 的内部机制,我们实现了一个灵活的动态 Controller 管理系统。这种方案可以在不重启应用的情况下,动态地添加和删除 Controller,为系统提供了更大的灵活性。

虽然这种方案在实际生产环境中使用需要慎重考虑,但它展示了 Spring Boot 的强大扩展性,也为我们提供了一种动态修改应用行为的思路。

参考资源