← 返回

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 三版本性能实测,以及各路线的实际使用建议。