使用 CGLib 拦截 JDK class 的包私有方法

Interception of package-private method for JDK class with CGLib

我正在尝试使用如下代码为 java.net.SocketImpl class 创建 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() 有相同的行为。

我试图用用户 class 重现这个错误(根据 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 进行测试。

Cglib 通过创建一个 subclass 来拦截方法,该 subclass 覆盖其 superclass 的所有方法。要覆盖包私有方法,subclass 必须在同一个包中定义。

这对 SocketImpl 来说是不可能的,原因有二。 1. 只允许 bootstrap class 加载程序在内部命名空间中定义包。 2.为了覆盖一个方法,运行时包也需要相等。但是,无法将 class 注入 bootstrap class 加载程序。

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