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

如何避免反射绕过Groovy的invokeMethod拦截器实现懒加载?

Groovy懒加载代理捕获Java反射调用的解决方案

问题背景

通过Groovy的metaClass重写getProperty和invokeMethod实现对象懒加载:仅在调用属性getter时才加载数据。正常Groovy语法调用getter时代理工作正常,但使用Java反射(如Method.invoke)或Jackson序列化(内部用反射调用_accessorMethod.invoke(bean))时,拦截逻辑完全不触发,导致数据仍为null。

现有代理代码

static <T> T lazyProxyFor(T object) {
    Class clazz = object.class
    object.metaClass.getProperty = { String name ->
        println "getting property $name"

        def currentVal = clazz.metaClass.getMetaProperty(name).getProperty(delegate)
        if (!currentVal) {
          setProperty('testing') // 实际为从服务获取数据的逻辑
        }
        return currentVal
    }

    object.metaClass.invokeMethod = { String name, Object args ->
        println "calling $name"

        if (name ==~ /get[A-Z][a-zA-Z0-9_]*/ && args?.size() == 0) {
            object[name.drop(3).uncapitalize()] // 调用getProperty方法
        } else {
            clazz.metaClass.getMetaMethod(name, args)?.invoke(delegate, args) // 交给原始对象处理
        }
    }

    return object
}

测试复现代码

Address address = lazyProxyFor(new Address())

address.class.declaredMethods.each{ method ->
    if (method.name.startsWith('get')) {
        method.invoke(address)    // 反射调用:无拦截输出
        // address."$method.name"()  // Groovy语法调用:正常触发拦截
    }
}

原因分析

Groovy的metaClass仅覆盖Groovy语法层面的动态方法分派和属性访问,比如直接调用address.street或address.getStreet()(Groovy语法糖)。但Java反射调用Method.invoke时,会直接调用类的原始方法实现,完全绕过Groovy的元方法拦截机制,所以不会触发你的懒加载逻辑。

解决方案:使用动态代理拦截所有调用路径

要同时覆盖Groovy调用和Java反射调用,必须使用动态代理(Java原生Proxy或CGLIB类代理),这类代理会拦截所有通过代理对象的方法调用,包括反射方式。

方案1:Java原生动态代理(适用于实现接口的类)

如果目标类实现了接口,用Java原生Proxy创建代理对象,所有方法调用(包括反射)都会经过InvocationHandler的invoke方法:

import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy

static <T> T lazyProxyFor(T object) {
    Class<?> clazz = object.getClass()
    // 创建代理,需目标类实现至少一个接口
    return Proxy.newProxyInstance(
        clazz.getClassLoader(),
        clazz.getInterfaces(),
        new InvocationHandler() {
            @Override
            Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName()
                
                // 处理getter方法(含反射调用)
                if (methodName.startsWith("get") && (args == null || args.length == 0)) {
                    String propName = methodName.drop(3).uncapitalize()
                    println "calling getter via reflection: $methodName"
                    
                    def currentVal = clazz.metaClass.getMetaProperty(propName).getProperty(object)
                    if (!currentVal) {
                        // 替换为实际的数据加载逻辑
                        object.setProperty(propName, "loaded from service")
                    }
                    return clazz.metaClass.getMetaProperty(propName).getProperty(object)
                }
                // 处理Groovy的getProperty调用
                else if (methodName == "getProperty" && args.length == 1 && args[0] instanceof String) {
                    String propName = args[0]
                    println "getting property $propName"
                    
                    def currentVal = clazz.metaClass.getMetaProperty(propName).getProperty(object)
                    if (!currentVal) {
                        object.setProperty(propName, "loaded from service")
                    }
                    return currentVal
                }
                // 其他方法直接委托给原始对象
                else {
                    return method.invoke(object, args)
                }
            }
        }
    ) as T
}

方案2:CGLIB类代理(适用于无接口的类)

如果目标类没有实现接口,使用CGLIB创建类级别的代理(继承目标类并重写方法),同样能拦截反射调用:

首先确保项目引入CGLIB依赖(如Maven):

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

代理实现代码:

import net.sf.cglib.proxy.Enhancer
import net.sf.cglib.proxy.MethodInterceptor
import net.sf.cglib.proxy.MethodProxy

static <T> T lazyProxyFor(T object) {
    Class<?> clazz = object.getClass()
    Enhancer enhancer = new Enhancer()
    enhancer.setSuperclass(clazz)
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            String methodName = method.getName()
            
            // 处理getter方法(含反射调用)
            if (methodName.startsWith("get") && (args == null || args.length == 0)) {
                String propName = methodName.drop(3).uncapitalize()
                println "calling getter via reflection/CGLIB: $methodName"
                
                def currentVal = clazz.metaClass.getMetaProperty(propName).getProperty(object)
                if (!currentVal) {
                    object.setProperty(propName, "loaded from service")
                }
                return currentVal
            }
            // 处理Groovy的getProperty调用
            else if (methodName == "getProperty" && args.length == 1 && args[0] instanceof String) {
                String propName = args[0]
                println "getting property $propName"
                
                def currentVal = clazz.metaClass.getMetaProperty(propName).getProperty(object)
                if (!currentVal) {
                    object.setProperty(propName, "loaded from service")
                }
                return currentVal
            }
            // 其他方法调用父类(原始类)实现
            else {
                return methodProxy.invokeSuper(proxy, args)
            }
        }
    })
    return enhancer.create() as T
}

测试验证

修改测试代码后,无论是Groovy语法调用还是Java反射调用,都会触发懒加载拦截逻辑:

// 假设Address实现了AddressInterface(方案1)或无接口(方案2)
Address address = lazyProxyFor(new Address())

// 反射调用:现在会输出拦截日志,加载数据
address.class.declaredMethods.each { method ->
    if (method.name.startsWith('get')) {
        method.invoke(address)
    }
}

// Groovy调用:依然正常触发拦截
address.street

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 20:35:20