Java 代理学习笔记 —— 详解Java原生动态代理

代理模式

简述

代理模式,就是为一个真实的对象提供代理,从而能够控制这个对象的访问。代理对象和被代理的对象实现相同接口,在代理对象中,增加特定的控制逻辑。代理对象分为两种,静态代理和动态代理。
首先,我们有如下对象。
UserDao.java

package reflectionSample;

public interface UserDao {
    public void queryName();
}

UserDaoImpl.java

package reflectionSample;

public class UserDaoImpl implements UserDao{

    @Override
    public void queryName() {
        System.out.println("getUserName");
    }

}

如果我们需要在queryName之前做一些其它操作,比如记录由哪个用户发起的这个query操作,又或者是限制在某个特定条件下才可以执行query操作,显然直接修改UserDaoImpl会给后期维护带来困难,这个时候代理模式就能够起作用了。

静态代理模式

在静态代理模式之下,我们显示地创建代理类,代理类持有被代理类的对象,实现与被代理类的相同接口,并且增添相应的业务逻辑,以达到代理的效果。
StaticProxy.java

package reflectionSample;

public class StaticProxy implements UserDao{
    private UserDaoImpl userDaoImpl = new UserDaoImpl();
    @Override
    public void queryName() {
        System.out.println("proxy control");
        userDaoImpl.queryName();
    }

}

ProxyMain.java

package reflectionSample;

import java.lang.reflect.Proxy;

public class ProxyMain {
    public static void main(String args[]) {
        UserDao staticProxy = new StaticProxy();
        staticProxy.queryName(); 
        //out put: proxy control
        //         getUserName

    }
}

如上所示,我们完成了对queryName()这个接口的代理。
在静态代理的情况下,每代理一个对象,我们必须在编译时就生成好对应的类,而且需要对接口中所有方法进行代理,会造成不必要的代码冗余,但是这样做的好处就是,代理的调用时直接的方法调用,节省了时间。

动态代理模式

在动态代理的情况下,我们不再为每个类生成一个类来完成代理,而是通过Proxy.newProxyInstance(loader, interfaces, h)来生成代理对象。其三个参数的作用如下表所示

参数名作用
loader代理的class所属的classLoader(关于classLoader,可以参考这篇博客),代理和被代理的class必须在同一个classLoader之下才能够工作
interfaces被代理的接口。这个时候我们可以指定哪些接口是需要被代理的,传入的是接口的类的数组
hhandler,最终的回调方法,必须是一个实现了InvocationHandler的类的实例

InvocationHandler接口中的方法则为public Object invoke(Object proxy, Method method, Object[] args) throws Throwable,其参数作用如下表所示

参数名作用
proxy生成的代理对象
method被调用的方法对象
args调用方法时传入的参数

invoker方法中,method就是我们的代理方法,我们可以通过method.invoke(object, args)来直接调用具体对象的方法,由于这个函数的第一个参数是proxy对象,因此我们还需要让handler持有一个真正调用方法的对象,从而能够达到真正的代理效果。

DynamicProxyHandler.java

package reflectionSample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyHandler implements InvocationHandler{
    private Object realObj;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy control");
        return method.invoke(realObj, args); //通过检查后,调用真正实体的对应方法
    }

    public Object bind(Object realObj) {
        this.realObj = realObj;
        return Proxy.newProxyInstance(realObj.getClass().getClassLoader(), realObj.getClass().getInterfaces(), this);
    }

}

ProxyMain.java

package reflectionSample;

import java.lang.reflect.Proxy;

public class ProxyMain {
    public static void main(String args[]) {
        UserDao staticProxy = new StaticProxy();
        staticProxy.queryName();

        //输出: proxy control
        //      getUserName

        DynamicProxyHandler handler = new DynamicProxyHandler();
        UserDao dymicDao = (UserDao)handler.bind(new UserDaoImpl());
        dymicDao.queryName();
        //输出 : proxy control
        //       getUserName
    }
}

以上,我们就完成了一次Java原生的动态代理。在动态代理的方法里面,我们可以任意添加业务逻辑之外的控制逻辑,既不会让代码变的难以维护,又达到了效果,一举两得。

Java原生动态代理剖析

Proxy.newInstance源代码分析

既然原生代理有着这样强大的功能,那么它的具体实现又是什么呢?

最好的方法当然是直接查看源代码了。首先我们研究一下Proxy.newProxyInstance这个静态方法做了哪些事情
Proxy.java

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

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs); //检查是否拥有对这个类的访问权限,java安全相关,会在后面文章补充
        }

        //检查内存中是否存在已经生成好的类,如果没有,就生成一个并返回

        Class<?> cl = getProxyClass0(loader, intfs);

        //调用生成的代理类的构造方法创建新的类,并返回
        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});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

从上面的代码,我们能够发现,其实动态代理也会新建一个类,同时也会实例化这个新类的对象,然后返回。那么具体,这个新的类,又是怎么被构建出来的呢?

getProxyClass0这个方法中,首先会从内部的proxyClassCache中去取缓存的类,如果没有的话,就会通过ProxyClassFactory生产一个。

proxyClassCache的声明为private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

接下来我们继续研究ProxyFactory中的方法,我们只需要重点关注ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
因为这个静态方法直接以字节码的形式生成新的类。

public static byte[] generateProxyClass(final String name,
                                            Class<?>[] interfaces,
                                            int accessFlags)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
        final byte[] classFile = gen.generateClassFile();

        .....
        ...

        return classFile;
    }

ProxyGenerator.java

private byte[] generateClassFile() {
        //第一步,生成最终类所需要的所有方法

        //首先,为这个新的类生成hashCode, equals, toString这三个java.lang.Object中带有的默认方法
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);

        //遍历提供的接口,并且增加所有接口中的方法
        for (Class<?> intf : interfaces) {
            for (Method m : intf.getMethods()) {
                addProxyMethod(m, intf);
            }
        }

        //检查已经实现的方法,是否有可能出现冲突
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            checkReturnTypes(sigmethods);
        }

        //第二步,生成构造函数,并且为刚刚生成的每一个方法生成一个私有静态变量。
        try {
            methods.add(generateConstructor());

            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                for (ProxyMethod pm : sigmethods) {

                    // add static field for method's Method object
                    fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;",
                         ACC_PRIVATE | ACC_STATIC));

                    // generate code for proxy method and add it
                    methods.add(pm.generateMethod());
                }
            }

            methods.add(generateStaticInitializer());

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }
        //判断方法/字段的数量是否超过java限制
        if (methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        }
        if (fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        }

        //第三步,将生成好的方法,字段,以二进制的形式输出(之前生成的方法,字段,都是在内存中的变量,并没有输出到文件)


        cp.getClass(dotToSlash(className));
        cp.getClass(superclassName);
        for (Class<?> intf: interfaces) {
            cp.getClass(dotToSlash(intf.getName()));
        }

        /*
         * Disallow new constant pool additions beyond this point, since
         * we are about to write the final constant pool table.
         */
        cp.setReadOnly();

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);

        ... 

        return bout.toByteArray();
    }

由上面我们可以知道,其实动态代理,就是根据传入的参数(class名称,需要代理的接口数组,访问控制符)来动态生成一个新的类,这个类会被JVM动态加载到内存中,然后实例化,最终完成代理的功能。

ProxyGenerator.java中省略部分主要包含的是对.class二进制文件的输出,如果想要进一步了解.class文件的结构以及如何输出一个.class文件,可以参阅这个系列的博客

生成的新类代码分析

既然动态代理的原理是生成一个新的java class,那么它的结构是什么呢?

通过在Main函数中增加如下声明,可以让生成的class文件保存到本地
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
得到本地文件之后,我们可以通过一些class 二进制文件反编译器,如Java Decompiler,来反编译从而得到可读性更好的java文件。

下面附上生成的proxy类
$Proxy0.java

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import reflectionSample.UserDao;

public final class $Proxy0
  extends Proxy
  implements UserDao //实现了指定的UserDao接口
{
//每个方法,都维护了一个私有静态变量
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

  //保存了对最后方法调用类的引用
  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
  {
    try
    {
      //调用方法时,都是通过方法调用类的invoke,而不是调用实际的方法。
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void queryName()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      //在类初始化的时候,就对私有静态变量进行赋值,以获得自己需要实现的接口的方法
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("reflectionSample.UserDao").getMethod("queryName", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

以上,就是Java原生代理的所有学习笔记了。从上面的分析我们不难得出几个结论。

  • 代理也是基于类的,静态代理通过编译/手动在编译时写入代理类,以及相应的代理方法,从而完成代理。动态代理在运行时通过指定需要实现的接口,动态拼接生成新的类,并且指定所有方法的回调处理类,从而达到实现一个接口,代理所有方法的效果。
  • 动态代理需要通过method进行反射调用。

那么,Java原生动态代理就到这里结束了,后面的文章会介绍另外一种动态代理,基于ASM的cglib

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值