【问题标题】:Interception of package-private method for JDK class with CGLib用CGLib截取JDK类的包私有方法
【发布时间】:2015-11-18 09:36:57
【问题描述】:

我正在尝试使用以下代码为 java.net.SocketImpl 类创建 CGLib 代理:

Enhancer e = new Enhancer();
e.setSuperclass(SocketImpl.class);
e.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object socketImplInstance, Method method, Object[] arguments, MethodProxy methodProxy) throws Throwable {
        System.out.println("Got call to " + method.getName());
        return methodProxy.invokeSuper(socketImplInstance, arguments);
    }
});
SocketImpl socketImpl = (SocketImpl)e.create();

Method m = SocketImpl.class.getDeclaredMethod("getSocket");
m.setAccessible(true);
System.out.println("getSocket: " + m.invoke(socketImpl));

m = SocketImpl.class.getDeclaredMethod("getLocalPort");
m.setAccessible(true);
System.out.println("getLocalPort: " + m.invoke(socketImpl));

由于这段代码,我得到了一个输出:

getSocket: null
Got call to getLocalPort
getLocalPort: 0

我们没有“Got call to getSocket”,所以 SocketImpl#getSocket() 没有被拦截。此方法与 SocketImpl#getLocalPort() 的不同之处仅在于访问级别 - SocketImpl#getLocalPort() 是受保护的,而 SocketImpl#getSocket() 是包私有的。我与其他包私有方法 SocketImpl#getServerSocket() 有相同的行为。

我尝试使用用户类(根据 SocketImpl 是抽象的)重现此错误,但如果我们有,一切都按预期工作:

package userpackage;

public abstract class Abstract {
    void testMethod() {}
}

package somethingother;

Enhancer e = new Enhancer();
e.setSuperclass(Abstract.class);
e.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object abstractInstance, Method method, Object[] arguments, MethodProxy methodProxy) throws Throwable {
        System.out.println("Got call to " + method.getName());
        return methodProxy.invokeSuper(abstractInstance, arguments);
    }
});
Abstract abstrct = (Abstract)e.create();

Method m = Abstract.class.getDeclaredMethod("testMethod");
m.setAccessible(true);
System.out.println("testMethod: " + m.invoke(abstrct));

我们得到输出:

Got call to testMethod
testMethod: null

没关系,这个包私有方法被拦截了。

请您帮我理解一下这个例子发生了什么以及为什么我们有不同的行为。我只有一个猜测,它可能与 SecurityManager 有关,但在那种情况下,你能指出它为什么不起作用的具体情况吗?

我使用 CGLib 3.1 和 3.2.0 进行测试。

【问题讨论】:

    标签: java cglib


    【解决方案1】:

    Cglib 通过创建一个覆盖其超类的所有方法的子类来拦截方法。对于覆盖包私有方法,子类必须定义在同一个包中。

    这对于 SocketImpl 是不可能的,原因有两个。 1. 仅允许引导类加载器在内部名称空间中定义包。 2. 为了覆盖一个方法,运行时包也需要相等。但是,无法将类注入到引导类加载器中。

    使用 cglib,这是不可能的。但是,您可以查看 Byte Buddy,它允许通过检测创建引导类代理。

    【讨论】:

    • 感谢您的回答拉斐尔,您是对的。如果做增强型SocketImpl.getClass(),我得到$java.net.SocketImpl$$EnhancerByCGLIB$$f41d9022,同时SocketImpl.class是java.net.SocketImpl,所以CGLib在“$java.net”包中创建子类,而不是“java.net”。
    • Rafael,我看了一下 ByteBuddy,但看起来用自己的内部变量和来自外部上下文的值编写 CGLib 样式的 MethodInterceptor 是不可能或更难的。因此,需要重新考虑 ByteBuddy 抽象中的实现。但我会在空闲时间仔细研究一下。感谢您的建议。
    • 好的,发现使用 InvocationHandlerAdapter 和 java.lang.reflect.InvocationHandler 实现或多或少是一样的。
    • 嗨,Dimitry,您应该能够使用 Byte Buddy 完成所有可以使用 cglib 且代码更少的事情。 Byte Buddy 允许您使用模仿 cglib 的 InvocationHandler 的注释。使用注释而不是接口很重要,因为引导类加载器无法看到由 cglib 定义的接口,这是导致NoClassDefFoundErrors 的原因。
    猜你喜欢
    • 1970-01-01
    • 2013-11-13
    • 1970-01-01
    • 1970-01-01
    • 2019-01-26
    • 2014-12-31
    • 1970-01-01
    • 2011-02-22
    • 1970-01-01
    相关资源
    最近更新 更多