# 动态加载 Spring 工具包
**Repository Path**: wb04307201/dynamo-spring
## Basic Information
- **Project Name**: 动态加载 Spring 工具包
- **Description**: 一个用于动态加载和管理 Java 类的工具库,支持动态编译、AOP 代理、Spring Bean 管理。会话模型 ClassLoader、ByteBuddy 代理、子类化 Spring MVC、自动配置。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 59
- **Forks**: 26
- **Created**: 2022-11-21
- **Last Updated**: 2026-06-25
## Categories & Tags
**Categories**: utils
**Tags**: 编译, 动态编译
## README
# dynamo-spring 动态加载 Spring 工具包
> 一个用于动态加载和管理 Java 类的工具库,支持动态编译、AOP 代理、Spring Bean 管理。会话模型 ClassLoader、ByteBuddy 代理、子类化 Spring MVC、自动配置。

[](https://gitee.com/wb04307201/dynamo-spring)
[](https://gitee.com/wb04307201/dynamo-spring)
[](https://github.com/wb04307201/dynamo-spring)
[](https://github.com/wb04307201/dynamo-spring)


## 快速开始
### Maven 依赖
库分两个模块,按需选择:
```xml
io.github.wb04307201
dynamo-spring-spring-boot-starter
1.3.0
```
| 模块 | 内容 | 依赖 |
| --- | --- | --- |
| `dynamo-spring-core` | `compiler` + `aspect` + `exception` | javaparser + byte-buddy + spring-core + slf4j |
| `dynamo-spring-spring-boot-starter` | 上述 + `DynamoRuntime` + `bean` 包 + 自动配置 | 上述 + spring-webmvc + spring-boot-autoconfigure |
### 5 行跑通
```java
import cn.wubo.dynamo.spring.DynamoRuntime;
try (DynamoRuntime runtime = DynamoRuntime.create()) {
Class> clazz = runtime.compileAndLoad("public class A { public String hi() { return \"hi\"; } }");
Object o = clazz.getDeclaredConstructor().newInstance();
System.out.println(o.getClass().getMethod("hi").invoke(o));
}
```
### 包 ↔ 模块
| Java 包 | 所在模块 |
| --- | --- |
| `cn.wubo.dynamo.spring.compiler` | `core` |
| `cn.wubo.dynamo.spring.aspect` | `core` |
| `cn.wubo.dynamo.spring.exception` | `core` |
| `cn.wubo.dynamo.spring`(`DynamoRuntime`) | `spring-boot-starter` |
| `cn.wubo.dynamo.spring.bean` | `spring-boot-starter` |
## 三大能力
### 1. 动态编译(`compiler` 包)
`DynamoClassLoader` 会话模型 ClassLoader——每个实例独立持有 classpath、字节码缓存、文件管理器。
```java
import cn.wubo.dynamo.spring.compiler.CompilationResult;
import cn.wubo.dynamo.spring.compiler.CompilerOptions;
import cn.wubo.dynamo.spring.compiler.DynamoClassLoader;
try (DynamoClassLoader loader = DynamoClassLoader.create()) {
CompilationResult result = loader.compile(sourceCode);
if (result.isSuccess()) {
Class> clazz = result.getCompiledClass();
// ...
} else {
result.getDiagnostics().forEach(System.err::println);
}
}
```
新增选项:
```java
loader.compile(source, CompilerOptions.create()
.sourceVersion("17")
.targetVersion("17")
.classpath("/path/to/lib.jar")
.enablePreview());
```
更多 overload:
```java
// 指定父 ClassLoader(用于在隔离环境加载 JDK 自带类之外的类)
ClassLoader parent = Thread.currentThread().getContextClassLoader();
DynamoClassLoader loader = DynamoClassLoader.create(parent);
// 直接喂字节码(不经过源码编译)
Class> clazz = loader.defineClass("com.example.Precompiled", bytes);
// 批量添加 JAR
loader.addJarPaths("/path/a.jar", "/path/b.jar");
// 编译失败抛 CompilationException(携带 CompilationResult)
try {
loader.compileAndLoad(badSource);
} catch (CompilationException ex) {
CompilationResult r = ex.getResult(); // 结构化诊断
}
```
### 2. AOP 代理(`aspect` 包)
基于 ByteBuddy 的代理。`IAdvice` 三方法切面接口。
```java
import cn.wubo.dynamo.spring.aspect.DynamoProxy;
import cn.wubo.dynamo.spring.aspect.IAdvice;
import java.lang.reflect.Method;
IAdvice advice = new IAdvice() {
public void before(Object t, Method m, Object[] a) { /* ... */ }
public void after(Object t, Method m, Object[] a, Object r) { /* ... */ }
public void afterThrow(Object t, Method m, Object[] a, Throwable c) { /* ... */ }
};
MyService proxy = DynamoProxy.proxy(target, advice);
```
或用内置 `SimpleAdvice`(线程安全,基于 `ThreadLocal`):
```java
import cn.wubo.dynamo.spring.aspect.SimpleAdvice;
MyService proxy = DynamoProxy.proxy(MyService.class, new SimpleAdvice());
```
> **已知限制**:`SimpleAdvice` 不支持同线程的 re-entrant 调用——同线程在 `before` 与 `after` 之间再次进入任何用 `SimpleAdvice` 代理的方法时,内层会覆盖外层的计时器。
### 3. Spring Bean 管理(`bean` 包)
不反射 Spring 私有 API——通过子类化 `RequestMappingHandlerMapping` 实现。
```java
import cn.wubo.dynamo.spring.bean.DynamoBean;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) ctx.getBeanFactory();
// 注册 controller(bean 定义 + 路由)
DynamoBean.registerController(bf, "myCtrl", MyController.class);
// 注销 controller(只移除路由;bean 定义保留在容器中)
DynamoBean.unregisterController(bf, "myCtrl");
// 真正"替换":先注销旧路由,再注册新类型
DynamoBean.unregisterController(bf, "myCtrl");
DynamoBean.registerController(bf, "myCtrl", NewType.class);
// 纯单例 Bean 注册(不涉及路由)
DynamoBean.registerSingleton(bf, "myBean", MyBean.class);
DynamoBean.unregisterSingleton(bf, "myBean");
```
> **关于 `refreshController`**:`refreshController(bf, name, type)` 等价于 `registerController`——
> 它会注册新类型的路由,但**不清理**旧路由。如需真正替换,请先 `unregisterController` 再注册。
### 4. DynamoRuntime 高级门面
把三件套粘到一个生命周期里:
```java
import cn.wubo.dynamo.spring.DynamoRuntime;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) ctx.getBeanFactory();
try (DynamoRuntime runtime = DynamoRuntime.withBeanFactory(bf)) {
Class> controller = runtime.compileAndLoad(sourceCode);
runtime.registerController("dynamicCtrl", controller);
// 路由已生效
}
// ClassLoader 关闭后,Bean 仍在容器中(由 Spring 生命周期管理)
// runtime 还支持:registerBean / unregisterBean / unregisterController /
// refreshController / addJarPath / compile / compileAndLoad(与 DynamoClassLoader 同语义)
// 不带 BeanFactory 的轻量用法:
try (DynamoRuntime runtime = DynamoRuntime.create()) {
Class> c = runtime.compileAndLoad(source);
}
// 或指定父 ClassLoader:
try (DynamoRuntime runtime = DynamoRuntime.create(customParent)) { ... }
```
## Spring Boot 自动配置
引入依赖后自动激活。无需 `@EnableXxx`、无需 `@Import`:
- `WebMvcRegistrations` 替换默认 `RequestMappingHandlerMapping` 为 `DynamoRequestMappingHandlerMapping`
- `spring.mvc.*` 所有属性继续生效(WebMvcRegistrations 透明替换 mapping,setter 全部继承到子类)
如果需要禁用:
```properties
spring.autoconfigure.exclude=cn.wubo.dynamo.spring.bean.DynamoBeanAutoConfiguration
```
## 生产环境 classpath 配置
因为本地和服务器的 classpath 路径差异,服务上动态编译可能找不到 import 的类。
请在消费方项目的 `pom.xml` 加:
```xml
org.apache.maven.plugins
maven-jar-plugin
true
lib/
org.apache.maven.plugins
maven-dependency-plugin
copy-dependencies
package
copy-dependencies
${project.build.directory}/lib
false
runtime
```
启动时:
```shell
java -jar -Dloader.path=lib/ your-app.jar
```
## 构建、测试、CI
仓库根目录是 parent pom,所有命令在根目录跑:
```bash
mvn -B test # 全部模块的单元测试(113 个,分布在 core + starter)
mvn -B verify # 单元 + 集成测试(共 123 个 = 113 unit + 10 IT,JaCoCo 报告生成到各模块 target/site/jacoco/)
# 单独跑某个模块
mvn -B test -pl dynamo-spring-core -am
mvn -B verify -pl dynamo-spring-test -am
```
CI:GitHub Actions 跑在 `ubuntu-latest` + JDK 17 上,push 与 PR 都触发。
## 演示应用
`dynamo-spring-test` 模块自带一个可启动的 Spring Boot Web 应用,把三大能力 + DynamoRuntime 高级门面拼到 4 个 tab 里,浏览器里直接玩:
```bash
# 安装所有模块到本地仓库(首次需要;spring-boot:run 依赖 starter 模块的 jar)
mvn -B install -DskipTests
# 启动 demo(默认 http://localhost:8080)
mvn -B -pl dynamo-spring-test spring-boot:run \
-Dspring-boot.run.jvmArguments="-Dnet.bytebuddy.experimental=true"
```
> `-Dnet.bytebuddy.experimental=true` 是为了在 JDK 21+ 上启用 ByteBuddy 实验性支持;不传也能跑,但 JDK 17 下没必要。
### 四个 tab 的能力
| Tab | 路径前缀 | 演示 |
| --- | --- | --- |
| 1. 动态编译 | `/api/compile/*` | 源码 → 编译 → 加载 → 实例化 → 反射调用;列已加载实例,删除可释放 ClassLoader |
| 2. AOP 拦截 | `/api/aspect/*` | 源码 → 编译 + 创建 ByteBuddy 代理;advice 选 `log` / `timing` / `throw`;调用时返回累积的 `before / after / afterThrow` 日志 |
| 3. 动态 Controller | `/api/bean/*` | 源码(`@RestController`)→ 编译 → `DynamoBean.registerController` → 浏览器立刻可路由;注销后访问 404;列表显示当前注册的 controller 及其路由 |
| 4. DynamoRuntime | `/api/runtime/*` | 开 session → session 内 compile / instantiate / invoke / registerController / unregisterController;关 session 后 Bean 仍在容器中 |
### 关键文件
```
dynamo-spring-test/src/main/
├── java/cn/wubo/dynamic/loader/utility/demo/
│ ├── DemoApp.java @SpringBootApplication 启动器
│ ├── InstanceRegistry.java 集中管理实例/代理/session 状态
│ ├── CompileController.java Tab 1
│ ├── AspectController.java Tab 2
│ ├── BeanController.java Tab 3
│ ├── RuntimeController.java Tab 4
│ ├── GlobalExceptionHandler.java 把 4xx/5xx 翻译成统一 JSON
│ ├── ArgsHelper.java 反射调用辅助(findMethod / coerceArgs)
│ └── advice/
│ ├── LoggingAdvice.java log:记录全部三阶段
│ ├── TimingAdvice.java timing:ThreadLocal StopWatch
│ └── ThrowAdvice.java throw:被动记录,让源方法自己抛以触发 afterThrow
└── resources/
├── application.yml server.port=8080
└── static/
├── index.html 单页 4 tab
├── app.js 原生 JS(fetch)
└── styles.css
```
### 端到端示例(curl)
```bash
# Tab 1:编译 Greeter.greet("alice") → "hi alice"
curl -X POST http://localhost:8080/api/compile/load \
-H 'Content-Type: application/json' \
-d '{"source":"public class G { public String greet(String n){return \"hi \"+n;} }"}'
# 返回 {"id":"...","className":"G","methods":[...]}
curl -X POST http://localhost:8080/api/compile/invoke \
-H 'Content-Type: application/json' \
-d '{"id":"...","methodName":"greet","args":["alice"]}'
# 返回 {"result":"hi alice","resultType":"java.lang.String"}
# Tab 3:动态注册 controller
curl -X POST http://localhost:8080/api/bean/register \
-H 'Content-Type: application/json' \
-d '{"beanName":"dynCtrl","source":"@org.springframework.web.bind.annotation.RestController public class DynCtrl { @org.springframework.web.bind.annotation.GetMapping(\"/api/dyn\") public String dyn(){return \"dynamic\";} }"}'
curl http://localhost:8080/api/dyn # → "dynamic"
curl -X POST http://localhost:8080/api/bean/unregister \
-H 'Content-Type: application/json' -d '{"beanName":"dynCtrl"}'
curl -i http://localhost:8080/api/dyn # → HTTP/1.1 404
```
### 与 `test-jar` 的关系
`dynamo-spring-test` 同时生成 default-jar(demo 应用,可直接 `java -jar`)和 test-jar(IT 设施,给消费方复用)。两套产物互不影响:
- 普通 `mvn install` 把 default-jar 入本地仓库;
- IT 用 `mvn verify` 走 failsafe,作用在 test-jar 之外的另一个分类下。
DemoApp 与 TestApp 是两个独立的 `@SpringBootApplication`:`DemoApp` 在 `cn.wubo.dynamo.spring.demo` 包下(main classpath,供 `spring-boot:run`),`TestApp` 在 `cn.wubo.dynamo.spring` 包下(test classpath,IT 启动器)。
## 测试覆盖维度
| 维度 | 说明 |
| --- | --- |
| **核心 API** | `DynamoRuntime` / `DynamoClassLoader` / `DynamoProxy` 主路径 |
| **并发** | 8 线程 × 20 编译并行加载;多实例隔离;compile-vs-close race |
| **生命周期** | close 幂等;close 后操作;Bean 跨 close 保留 |
| **边界 / 错误恢复** | 空源码 / 注释 / Unicode 类名 / 接口 / 枚举 / 重复注册 / 不存在注销 |
| **AOP 多场景** | afterThrow 触发;advice 隔离;ThreadLocal 跨线程 |
| **Spring 集成** | registerController 真实写入 mapping;unregister + register 替换 |
## 许可证
Apache License 2.0