和我一起学 JDK 动态代理

181 阅读4分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

代理模式介绍

代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的非常不灵活,如果有另一个接口的实现类需要进行同样的代码增强,需要写一个新的代理类。

步骤:

  • 定义一个接口及其实现类;
  • 创建一个代理类同样实现这个接口;
  • 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以在目标方法执行前或执行后做一些自己想做的事情。

动态代理

相比于静态代理来说,动态代理更加灵活。 我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类,如 CGLIB 动态代理机制,下篇文章我们再一起学 CGLIB 动态代理,今天的主题是 JDK 动态代理。不管是 CGLIB 动态代理,还是 JDK 动态代理,代理类都是在程序运行时创建的。

JDK 动态代理

InvocationHandler 接口和 Proxy 类

Jdk 动态代理机制的核心是 InvocationHandler 接口和 Proxy 类。

调用 Proxy 类中的 newProxyInstance() 方法可以生成一个代理对象。这个方法有三个参数,一个是类加载器 loader,一个是被代理类实现的接口 interfaces,一个是实现了 InvocationHandler 接口的对象 handler

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

invocationHandler 是一个接口,每个代理实例都有一个与之关联的 InvocationHandler 实现类,这个实现类中的 invoke 方法定义了代理对象调用方法时希望执行的动作。

public interface invocationHandler{
    public Object invoke(Object proxy,Method method,Object[] args)
}

使用小例子

创建一个 Subject 接口,包含一个 request 方法。

public interface Subject {
    void request();
}

创建一个 RealSubject 类,实现 Subject 接口,request 方法是目标对象要实现的操作。接下来我们会通过创建一个代理对象,实现对 request 方法的增强。

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("real request");
    }
}

创建自定义处理器 Invocation 类。我们需要实现 InvocatioinHandler 接口来自定义处理逻辑。通过 Invocation 的构造器将目标对象注入进代理类。当我们的动态代理对象调用一个方法的时候,这个方法的调用就会被转发到实现 InvocationHandler 接口的类中的 invoke方法来调用。所以我们可以在这个 invoke 方法中实现我们需要的增强,本例在目标方法执行前和执行后都输出了信息。

public class Invocation implements InvocationHandler {
    private Object target;

    public Invocation(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("do sth before request");
        method.invoke(target,args);
        System.out.println("do sth after request");
        return null;
    }
}

Main 类:通过 ProxynewProxyInstance 方法,创建了实现 Subject 接口的代理对象,调用代理对象的方法时,调用处理器的 invoke 方法被调用,对 request 方法实现了增强。

public class Main {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        System.out.println("目标对象" + subject.getClass());
        InvocationHandler handler = new Invocation(subject);
        Subject proxy = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(),RealSubject.class.getInterfaces(),handler);
        System.out.println("代理对象" + proxy.getClass());

        proxy.request();
    }
}

输出结果

目标对象class proxy.RealSubject
代理对象class con.sun.proxy.$Proxy0
do sth before request
real request
do sth after request

代理对象的生成

我们创建一个代理对象时,是调用 newProxyInstance方法,在这个方法中,首先通过getProxyClass() 方法生成 class 文件、创建 class 对象,然后通过反射机制获取构造器,再通过 newInstance(new Object[]{h}) 方法实例化,并将自定义的InvocationHandler 实例传入,自此,动态代理对象生成。当通过代理对象调用方法时,就会调用代理对象传入的 InvocationHandler 实例中的 invoke()方法,可以增强原目标对象的方法。


public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
        final Class<?>[] intfs = interfaces.clone();
        //获取与指定装载器和一组接口相关的代理类类型对象
        Class<?> cl = getProxyClass0(loader, intfs);
        //通过反射获取构造函数
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //生成代理类实例
        return cons.newInstance(new Object[]{h});
}