JDK Proxy:不解析 Lambda,但最实用的工程方案
📅 2026-06-10 | 🏷️ JDK Proxy, DSL, 规则引擎, SQL | 📖 阅读约 20 分钟
前言
前四种方案都在想方设法解析 Lambda 的结构:
- SerializedLambda:从方法引用中提取方法名
- APT:编译期扫描字段
- ASM:读取编译后的字节码
- Javac Plugin:拦截编译器的 AST
第五种方案换个思路:不解析 Lambda,用代理对象录制 getter 调用路径。
这个方案实现入门门槛低,但工程价值反而更高,因为它的 API 设计天然支持链式 DSL,非常适合规则引擎、数据权限、工作流条件等动态场景。
核心原理
JDK 的 java.lang.reflect.Proxy 可以动态创建接口的代理对象。代理对象的所有方法调用都会被 InvocationHandler 拦截。
/**
* JDK Proxy 的核心机制:
*
* 1. Proxy.newProxyInstance() 创建一个实现了指定接口的代理对象
* 2. 代理对象的所有方法调用都会转发到 InvocationHandler.invoke()
* 3. 在 invoke() 中,我们可以获取方法名、参数等信息
* 4. 返回一个默认值(让调用方不报错)
*/创建代理对象
/**
* 创建实体接口的代理。
*
* 注意:JDK Proxy 只能代理接口,不能代理具体类。
* 所以实体类需要先提取一个接口(如 UserEntity)。
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> entityClass) {
return (T) Proxy.newProxyInstance(
entityClass.getClassLoader(), // 类加载器
new Class[]{entityClass}, // 要代理的接口
new RecordingHandler() // 调用处理器
);
}录制 getter 调用
/**
* 代理对象的方法调用处理器。
*
* 当代理对象的任何方法被调用时,invoke() 会被触发。
* 我们在这里记录方法名,然后返回一个占位值。
*/
private static class RecordingHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
// 如果当前正在录制,记录方法名
if (isRecording() && method.getParameterCount() == 0) {
recordCall(methodName);
// proxy.getAge() → 记录 "getAge"
}
// 返回对应类型的默认值(让调用方不报 NPE)
// 注意:这些返回值不会被使用,只是让 proxy.getAge() 调用能正常完成
Class<?> returnType = method.getReturnType();
if (returnType == int.class || returnType == Integer.class) return 0;
if (returnType == long.class || returnType == Long.class) return 0L;
if (returnType == boolean.class || returnType == Boolean.class) return false;
if (returnType == String.class) return "";
return null;
}
}录制流程
// 使用 ThreadLocal 保证线程安全
private static final ThreadLocal<List<String>> CALL_PATH =
new ThreadLocal<>();
// 开始录制
public static void startRecording() {
CALL_PATH.set(new ArrayList<>());
}
// 调用 getter
UserEntity proxy = createProxy(UserEntity.class);
proxy.getAge(); // InvocationHandler 拦截 → 记录 "getAge" → 返回 0
// 停止录制,获取路径
List<String> path = stopRecording(); // ["getAge"]
String propName = resolvePropertyName(path.get(0)); // "age"属性名解析
/**
* getter 方法名 → 属性名。
*
* 和 MyBatis Plus 的 PropertyResolver 规则一致:
* - getAge → age
* - isDeleted → deleted
* - getUserName → userName
*/
static String resolvePropertyName(String methodName) {
if (methodName.startsWith("get") && methodName.length() > 3) {
String rest = methodName.substring(3);
return Character.toLowerCase(rest.charAt(0))
+ rest.substring(1);
}
if (methodName.startsWith("is") && methodName.length() > 2) {
String rest = methodName.substring(2);
return Character.toLowerCase(rest.charAt(0))
+ rest.substring(1);
}
return methodName;
}链式 DSL
Proxy 路线上限的优势是天然支持链式 DSL。这是其他四条路线都不具备的。
ConditionBuilder 设计
/**
* 条件构建器 — 通过链式 DSL 构建 Expression 树。
*
* 使用方式:
* Expression expr = ConditionBuilder.create()
* .field(UserEntity.class, UserEntity::getAge).gt(18)
* .and()
* .field(UserEntity.class, UserEntity::getName).eq("Tom")
* .build();
*
* 每个 .field() 调用启动一次属性录制,
* 每个比较方法(gt/eq/lt...)生成一个 Expression,
* .and()/.or() 标记下一个条件的逻辑运算符,
* .build() 将所有条件组装为 LogicalExpression。
*/
public class ConditionBuilder {
// 已收集的条件(每个条件附带其前面的运算符)
private final List<ConditionEntry> entries = new ArrayList<>();
// 当前等待的逻辑运算符(默认 AND)
private LogicalOperator pendingOperator = LogicalOperator.AND;
/**
* 添加字段条件。
*
* @param entityClass 实体接口
* @param recorder 录制 Lambda(调用一次 getter)
* @return FieldStep(链式调用比较方法)
*/
public <T> FieldStep field(Class<T> entityClass,
PropertyRecorder<T> recorder) {
// 录制属性路径
startRecording();
T proxy = createProxy(entityClass);
recorder.record(proxy); // proxy.getAge() → 记录 "getAge"
List<String> path = stopRecording();
if (path == null || path.isEmpty()) {
throw new IllegalStateException("录制失败");
}
String propName = resolvePropertyName(path.get(0));
return new FieldStep(this, propName);
}
/**
* 标记下一个条件使用 AND 连接。
*/
public ConditionBuilder and() {
this.pendingOperator = LogicalOperator.AND;
return this;
}
/**
* 标记下一个条件使用 OR 连接。
*/
public ConditionBuilder or() {
this.pendingOperator = LogicalOperator.OR;
return this;
}
/**
* 构建最终的 Expression。
*/
public Expression build() {
if (entries.isEmpty()) {
throw new IllegalStateException("没有任何条件");
}
if (entries.size() == 1) {
return entries.get(0).expr;
}
// 多个条件按顺序组装(二叉树结构)
Expression result = entries.get(0).expr;
for (int i = 1; i < entries.size(); i++) {
ConditionEntry entry = entries.get(i);
if (entry.operator == LogicalOperator.OR) {
result = LogicalExpression.or(result, entry.expr);
} else {
result = LogicalExpression.and(result, entry.expr);
}
}
return result;
}
/**
* 字段步骤 — 提供比较方法(gt/eq/lt/ge/le/like/in)。
*/
public static class FieldStep {
private final ConditionBuilder builder;
private final String propertyName;
FieldStep(ConditionBuilder builder, String propertyName) {
this.builder = builder;
this.propertyName = propertyName;
}
/**
* 大于比较。
* .field(User::getAge).gt(18)
* → BinaryExpression(Property("age"), GT, Constant(18))
*/
public <T> ConditionBuilder gt(T value) {
builder.addCondition(new BinaryExpression(
new PropertyExpression(propertyName, value.getClass()),
Operator.GT,
new ConstantExpression(value, value.getClass())
));
return builder;
}
/**
* 等于比较。
* .field(User::getName).eq("Tom")
* → BinaryExpression(Property("name"), EQ, Constant("Tom"))
*/
public <T> ConditionBuilder eq(T value) {
builder.addCondition(new BinaryExpression(
new PropertyExpression(propertyName, value.getClass()),
Operator.EQ,
new ConstantExpression(value, value.getClass())
));
return builder;
}
/**
* LIKE 查询。
* .field(User::getName).like("%Tom%")
* → LikeExpression(Property("name"), "%Tom%")
*/
public ConditionBuilder like(String pattern) {
builder.addCondition(new LikeExpression(
new PropertyExpression(propertyName, String.class),
pattern
));
return builder;
}
}
// 条目:运算符 + 表达式
private record ConditionEntry(LogicalOperator operator, Expression expr) {}
}使用示例
// 构建: age > 18 AND name = "Tom"
Expression expr = ConditionBuilder.create()
.field(UserEntity.class, UserEntity::getAge).gt(18)
.and()
.field(UserEntity.class, UserEntity::getName).eq("Tom")
.build();
// 输出: (Property(age) > 18) AND (Property(name) = "Tom")SQL 生成器
所有路线产出的 Expression 都可以生成 SQL。用 Visitor 模式遍历表达式树:
/**
* SQL 生成器 — 用 Visitor 模式遍历 Expression 树,生成参数化 SQL。
*
* 支持 MySQL 和 PostgreSQL 方言。
* 支持驼峰转下划线列名映射。
*/
public class SqlGenerator implements ExpressionVisitor<Void> {
private final StringBuilder sql = new StringBuilder();
private final List<Object> params = new ArrayList<>();
private final SqlDialect dialect;
private final ColumnMapper columnMapper;
/**
* 生成 SQL 的入口方法。
*
* @param expression 表达式树
* @param dialect SQL 方言(MySQL / PostgreSQL)
* @return SqlResult(SQL 片段 + 参数列表)
*/
public static SqlResult generate(Expression expression,
SqlDialect dialect) {
SqlGenerator generator = new SqlGenerator(dialect);
expression.accept(generator);
return new SqlResult(generator.sql.toString(), generator.params);
}
/**
* 处理属性表达式 → 输出列名。
* Property("age") → "age"(或 "user_name" 如果配置了驼峰转下划线)
*/
@Override
public Void visitProperty(PropertyExpression expr) {
sql.append(columnMapper.mapColumn(expr.getPropertyName()));
return null;
}
/**
* 处理常量表达式 → 输出占位符 + 记录参数。
* Constant(18) → "?", params.add(18)
*
* 注意:参数化查询(? 占位符)防止 SQL 注入。
*/
@Override
public Void visitConstant(ConstantExpression expr) {
sql.append("?");
params.add(expr.getValue());
return null;
}
/**
* 处理二元比较 → 输出 "left op right"。
* GT(age, 18) → "age > ?"
*/
@Override
public Void visitBinary(BinaryExpression expr) {
expr.getLeft().accept(this); // age
sql.append(" ")
.append(expr.getOperator().getSymbol()) // >
.append(" ");
expr.getRight().accept(this); // ?
return null;
}
/**
* 处理逻辑组合 → 输出 "(a) AND (b)"。
* AND(GT(age,18), EQ(name,"Tom")) → "(age > ?) AND (name = ?)"
*/
@Override
public Void visitLogical(LogicalExpression expr) {
String op = " " + expr.getOperator() + " ";
List<Expression> operands = expr.getOperands();
for (int i = 0; i < operands.size(); i++) {
if (i > 0) sql.append(op);
sql.append("(");
operands.get(i).accept(this);
sql.append(")");
}
return null;
}
}使用示例
// 构建表达式
Expression expr = LogicalExpression.and(
new BinaryExpression(
new PropertyExpression("age", Integer.class),
Operator.GT,
ConstantExpression.of(18)
),
new BinaryExpression(
new PropertyExpression("name", String.class),
Operator.EQ,
ConstantExpression.of("Tom")
)
);
// 生成 SQL
SqlResult result = SqlGenerator.generate(expr, SqlDialect.MYSQL);
// result.getSql() → "(age > ?) AND (name = ?)"
// result.getParams() → [18, "Tom"]
// result.toWhereClause() → "WHERE (age > ?) AND (name = ?)"
// 驼峰转下划线
SqlResult result2 = SqlGenerator.generate(expr, SqlDialect.MYSQL,
SqlGenerator.ColumnMapper.CAMEL_TO_SNAKE);
// "user_name" 而不是 "userName"适用场景
Proxy 路线特别适合以下场景:
数据权限
// 按部门过滤数据
Expression permission = ConditionBuilder.create()
.field(UserEntity.class, UserEntity::getDeptId)
.eq(currentUser.getDeptId())
.and()
.field(UserEntity.class, UserEntity::getStatus).ne(0)
.build();
// 生成 SQL: WHERE dept_id = ? AND status != ?工作流条件
// 审批流程的分支条件
Expression condition = ConditionBuilder.create()
.field(TaskEntity.class, TaskEntity::getAssignee)
.eq(currentUser.getId())
.and()
.field(TaskEntity.class, TaskEntity::getStatus)
.in("PENDING", "IN_PROGRESS")
.build();规则引擎
// 业务规则:金额 > 1000 且创建时间在最近 7 天
Expression rule = ConditionBuilder.create()
.field(OrderEntity.class, OrderEntity::getAmount).gt(1000)
.and()
.field(OrderEntity.class, OrderEntity::getCreateTime)
.ge(sevenDaysAgo)
.build();性能特点
JDK Proxy 路线的性能非常稳定,不受 JDK 版本影响:
JDK 8 JDK 17 JDK 26
property_proxy 32.8 89.3 30.0 ns/op
compositeExpr_proxy 82.1 197.1 75.0 ns/op实际使用中的性能考量:
- 条件 DSL 通常只在构建查询时调用一次
- 构建完成后,SQL 会被缓存,后续查询直接用缓存的 SQL
- 首次构建的 ~80 ns 开销完全可以接受
- 不需要缓存属性解析结果(本身就是轻量操作)
下一篇:JDK 8 vs 17 vs 26 三版本性能实测,以及各路线的实际使用建议。