JDK Proxy 原理

280 阅读4分钟

JDK Proxy 使用步骤

Proxy#newProxyInstance() 方法用于创建 JDK Proxy,JDK Proxy 只能基于接口创建代理。

ClassLoader loader:代理类的类加载器。loader – the class loader to define the proxy class.

Class<?>[] interfaces:代理类需要实现的接口。interfaces – the list of interfaces for the proxy class to implement.

InvocationHandler h:h – the invocation handler to dispatch method invocations to. 在 InvocationHandler#invoke() 方法中编写代理实现逻辑。

JDK Proxy 的使用注意事项见方法上的 JavaDoc。

public class Proxy implements java.io.Serializable {

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {

调用 Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler) 生成 userService 的代理对象,并将其强转为 UserService 类型。

public static void main(String[] args) {
    System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
    UserService userService = new UserServiceImpl();
    InvocationHandlerImpl invocationHandler = new InvocationHandlerImpl(userService);
    UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(), invocationHandler);
    User user = userService.getUserById(1L);
    System.out.println(user);
    user = userServiceProxy.getUserById(2L);
    System.out.println(user);
}

InvocationHandlerImpl 实现了 InvocationHandler 接口,在 invoke() 方法中编写代理逻辑,并通过反射调用 method.invoke(target, args) 方法。在 invoke() 方法中,你可以做任何想做的事情,你甚至可以不调用 target 类的方法。比如 MyBatis 中只需要定义 Mapper 接口, 而不需要定义任何 Mapper 接口的实现,自然也就不需要调用 target 类的方法。

@RequiredArgsConstructor
public class InvocationHandlerImpl implements InvocationHandler {

    private final Object target;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("preparing to execute method");
        // 如果使用 proxy 执行 invoke() 方法,会导致无限递归
        // Object retVal = method.invoke(proxy, args);
        Object retVal = method.invoke(target, args);
        System.out.println("execute method finished");
        return retVal;
    }

}

UserServiceImpl 类:

public class UserServiceImpl implements UserService {

    @Override
    public User getUserById(Long id) {
        System.out.println("querying user from db");
        return User.of(id, "oneby" + id, new Date());
    }

}

程序输出结果:userServiceProxy 执行前后输出了 invoke() 中的 sout 语句。

querying user from db
User(id=1, username=oneby1, createTime=Fri Apr 21 05:35:14 UTC 2023)
preparing to execute method
querying user from db
execute method finished
User(id=2, username=oneby2, createTime=Fri Apr 21 05:35:14 UTC 2023)

错误示范:千万不要写成 Object retVal = method.invoke(proxy, args)proxy 就是代理对象自己,自己调用自己的方法,会导致无限递归。

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("preparing to execute method");
        // 如果使用 proxy 执行 invoke() 方法,会导致无限递归
        Object retVal = method.invoke(proxy, args);
        // Object retVal = method.invoke(target, args);
        System.out.println("execute method finished");
        return retVal;
    }

程序输出结果:无限递归,但为啥会输出 NoClassDefFoundError,不是 StackOverflowError 呢?

// 超级多的 preparing to execute method 输出
preparing to execute method
preparing to execute method
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class java.lang.reflect.UndeclaredThrowableException
    at jdk.proxy1/jdk.proxy1.$Proxy0.getUserById(Unknown Source)
    at com.oneby.spring.aop.proxy.jdk.JdkProxyApplication.main(JdkProxyApplication.java:21)

InvocationHandlerImpl#invoke() 方法的 proxy 参数是代理对象。

InvocationHandlerImpl invoke() 方法的 proxy 参数.jpg

使用 JDK Proxy,UserServiceImpl 类的 this 就是 UserServiceImpl 实例,不是代理对象,等下一节讲 CGLib Proxy 的时候,this 可能会毁你三观。

JDK Proxy UserServiceImpl 类的 this.jpg

在生成代理对象的代码之前, 加上这句 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"),可以在工程的 jdk 目录下生成 JDK Proxy 对应的 Class 文件。

public final class $Proxy0 extends Proxy implements UserService {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;

    public $Proxy0(InvocationHandler param1) {
        super(var1);
    }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final User getUserById(Long var1) {
        try {
            return (User)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.oneby.spring.aop.proxy.jdk.UserService").getMethod("getUserById", Class.forName("java.lang.Long"));
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    private static Lookup proxyClassLookup(Lookup var0) throws IllegalAccessException {
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(var0.toString());
        }
    }
}

$Proxy0UserServiceImpl 的代理类,类中定义了四个静态字段,分别是 m0m1m2m3

public final class $Proxy0 extends Proxy implements UserService {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;

这四个静态字段分别对应四个方法,分别是:Object#hashCode()Object#equals()Object#toString()UserService#getUserById()

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.oneby.spring.aop.proxy.jdk.UserService").getMethod("getUserById", Class.forName("java.lang.Long"));
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

执行 m0m1m2m3 方法时,会执行 super.h.invoke() 方法,所以 Object#hashCode()Object#equals()Object#toString()UserService#getUserById() 方法都会被代理。

JDK Proxy 只能基于接口创建代理,但是官方大哥们考虑到了 Object 类中定义的 Object#hashCode()Object#equals()Object#toString() 方法也有可能需要被代理,也默认创建了这三个方法的代理,所以在使用 JDK Proxy 代理的时候可能需要特殊处理这三个方法。

JDK Proxy 的执行原理与效率:InvocationHandler#invoke() 方法的 prototype 为 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable,在 invoke() 方法中我们只能使用参数中的 Method method 使用反射来执行 target 对象中的方法(如果需要的话),所以 JDK Proxy 的效率总体上不如 CGLib Proxy,至于为什么,下一节讲了知道了。

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final User getUserById(Long var1) {
        try {
            return (User)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }