Java的动态代理,你了解了么?

什么是代理

这个概念可以类比我们现实生活中的代购,或者中介之类的,好比我要去做一件事,然后让别人帮忙去做。

在Java中代理是什么

在Java中,代理可以分为静态代理和动态代理,这个都是JDK支持的,这个的区别主要就是在于能否在程序运行期间生成一个代理类,很明显,动态代理就可以做到这一点。

JDK是如何生成代理类的

在JDK的动态代理中,有两个比较关键的类和接口需要先了解,一个是InvocationHandler,另一个就是Proxy.先来看看这个InvocationHandler的接口描述。

Processes a method invocation on a proxy instance and returns

the result. This method will be invoked on an invocation handler

when a method is invoked on a proxy instance that it is associated with.

这里的大概意思就是说一个动态代理的实例在调用一个方法的时候,这个方法其实是被这个动态代理所关联的handler来调用的,这里的handler指的就是InvocationHandler。这里我们大概能够了解,如果我们需要自定义一个动态代理,那么需要两个要素,第一是需要生成一个代理对象,第二是需要定义一个InvocationHandler。那我们先定义一个handler。

public class ProxyHandler implements InvocationHandler {

  private Object target;

  public ProxyHandler(Object target) {
    this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("Pick a big one!");
    Object obj = method.invoke(target, args);
    return obj;
  }
}

这个ProxyHandler就是我们自定义的,这个handler定义的很简单,只需要实现InvocationHandler即可,那么这个invoke方法是什么?这些方法的参数都是代表什么意义?

原来实现一个handler如此简单,但是不知道里面的具体含义都是代表什么,只能再看InvocationHandler的描述了,这个是InvocationHandler接口的方法定义。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

这个方法定义的很简洁,现在先不需要过多的了解,我们只需要知道这些参数的含义即可:

  • proxy 代理对象(不是被代理的对象)
  • method 被代理的对象的方法对象
  • args 被代理的对象的方法参数

这里明白了,那我们现在ProxyHandler已经实现好了,根据接口的描述,好像还需要一个代理的对象,那如何生成一个代理的对象,在JDK中,需要Proxy这个类,这个类有一个newProxyInstance方法,我们需要的代理对象就是用这个方法来生成的。看一下这个方法描述。

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

这里也有三个参数,需要一个类加载器,需要一个接口对象数组,还有一个就是我们之前定义好的ProxyHandler,这里好像又能明白,如果我们被代理的对象是一个类,那么interfaces这个参数是无法获取的,对,那么被代理的一定是一个接口,那我们来试一试。我这里先定义一个接口。

public interface Fruit {

  void color();
}

反正都是吃的,你懂的!:) 再来一个这个接口的实现类。

public class Apple implements Fruit {

  @Override
  public void color() {
    System.out.println("This apple's color is red, It looks good!");
  }
}

很简单的,对不对!确实很简单,到这里,我们的动态代理就其实已经实现的差不多了,现在接口、接口的具体的实现、handler、代理对象都生成好了,那我们现在就来用一下,看看我们生成的代理怎么样。首先我们先明确一下,我们要做的是为我们的Fruit的这个color方法之前再添加一句话,就是这么简单,对不对?

Fruit fruit = new Apple();
ProxyHandler proxyHandler = new ProxyHandler(fruit);
Fruit f =
    (Fruit)
        Proxy.newProxyInstance(
            fruit.getClass().getClassLoader(), fruit.getClass().getInterfaces(), proxyHandler);
f.color();

然后程序运行一下,看看效果。

Pick a big one!
This apple's color is red, It looks good!

是不是,我们自己实现的动态代理成功了,但是有几个问题我们需要讨论一下,在上面代码中的f是什么?f.color()这个方法为什么就能实现我们想要的效果?这个动态代理具体体现在哪里了?

JDK的动态代理是如何体现的

根据上面我们的描述,只是感觉好像我们要实现的功能是实现了,但是有同学会说,你直接将要打印的语句放在color方法中不就行了么?没问题,这样做肯定没问题,但是如果有很多个方法那该如何处理?

但是上面我们提到的只是看到了实现的结果好像没有看到是如何体现的,这个就需要从Proxy的newProxyInstance方法说起了,我们先来看看这个方法具体是如何实现的。

Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
    checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

/*
 * Look up or generate the designated proxy class.
 */
Class<?> cl = getProxyClass0(loader, intfs);

/*
 * Invoke its constructor with the designated invocation handler.
 */
try {
    if (sm != null) {
        checkNewProxyPermission(Reflection.getCallerClass(), cl);
    }

    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    if (!Modifier.isPublic(cl.getModifiers())) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                cons.setAccessible(true);
                return null;
            }
        });
    }
    return cons.newInstance(new Object[]{h});

这里比较重要的是这么几行:

Class<?> cl = getProxyClass0(loader, intfs);
...
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
return cons.newInstance(new Object[]{h});

好像这么看起来实现的也很简单,没错,大师的代码都是这么简洁!首先根据我们传入的ClassLoader和接口对象就生成了一个代理类,然后获取一个构造器,这个构造器带有一个参数,类型是什么?就是InvocationHandler,大家自己可以看看就明白了,最后这个构造器根据我们自己定义的ProxyHandler,生成了一个对象,这个就是代理对象。

好的,那还是没有说明白这个代理对象是如何工作的,我们在看看getProxyClass0()这个方法,在IDE中,顺着这个方法点进去,然后就在Proxy这个类中,能够看到有一个ProxyClassFactory的内部类,这里有这么几个比较重要的。

// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";

//...
if (proxyPkg == null) {
    // if no non-public proxy interfaces, use com.sun.proxy package
    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
//...
/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);

这里好像又明白了,这个生成的代理类是什么,对,这个类的名称就是固定的,以$Proxy开始,然后后面再跟一个数字,然后这个类放在哪里,好像又明白了放在这个路径上,com.sun.proxy,然后ProxyGenerator.generateProxyClass()这个方法就是生成了代理类,即$Proxy.class。

然后我们再看看这个代理类具体是怎么样的?篇幅有限,只选取一些比较重要的。

public final class $Proxy0 extends Proxy implements Fruit {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
        }
    // ...
    public final void color() throws  {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}  
// ...
    static {
    try {
        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.jerry.proxy.interfaces.Fruit").getMethod("color");
        m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(var3.getMessage());
    }
}
  }

好的,说了这么多,这里最重要的出现了,这个生成的代理类实现了我们自定义的接口Fruit,实现了里面的方法,然后再看super.h.invoke(this,m3,(Object[])null),这里我认为就是整个动态代理最主要的地方,即代理的体现就在这里,可能还有同学不太明白,我们之前自定义的ProxyHandler的作用在哪里,还记得在生成代理类实例的时候,我们先生成了代理类的一个InvocationHandler类型参数的构造器,然后根据这个构造器传入我们定义的ProxyHandler,生成一个$Proxy的实例对象,看这里的代码。

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

将我们自定义的ProxyHandler传入之后,去更新了父类的InvocationHandler参数,可以在Proxy类中看到这个构造器。

/**
 * the invocation handler for this proxy instance.
 * @serial
 */
protected InvocationHandler h;
// ...
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

我看到网上有很多说,这里的操作是与新生成的代理实例绑定,也不能说不对,只是我个人认为是对InvocationHandler的赋值更为准确,这里就体现了面向接口的思想,到这里应该整个JDK的动态代理的过程就完成了。

我们再来看看刚开始的测试代码,修改一下。

Fruit fruit = new Apple();
ProxyHandler proxyHandler = new ProxyHandler(fruit);
Fruit f =
    (Fruit)
        Proxy.newProxyInstance(
            fruit.getClass().getClassLoader(), fruit.getClass().getInterfaces(), proxyHandler);
Fruit f = new $Proxy0(proxyHandler); // 如果修改成这样,是不是就看懂了?:)            
f.color();

说了这么多,不知道各位同学有没有看明白,如果哪里分析的不准确,欢迎提出来,一起讨论。顺便说一下,JDK的动态代理还有一个局限,就是只能对于接口类型进行代理,如果需要代理类,那就需要CDLib了。这个动态代理是一个基础,为什么是基础,不是说这个一定要掌握,因为在平常的开发工作中,可以说这种动态代理基本不会去用,因为都在使用大家熟悉的Spring,Spring AOP的基础就是JDK的动态代理和CGLib这两种技术。

OK,刚刚提到的Spring AOP,在这里我们就先不说了,如果有兴趣,可以来GitHub看看。 传送门!

关于Spring AOP的相关代码在jerry模块,不要问我这个模块的名字为什么叫jerry,我也不知道:).

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章