JDK动态代理分析

版权声明:本文为博主原创文章,转载请注明原帖地址,谢谢 https://blog.csdn.net/AooMiao/article/details/82151187

代理:代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能的特点也为多人所用,在不改动源文件代码的前提下在代码前后方增加相应的逻辑,例如:日志记录,异常统一捕获打印。
动态代理:JDK提供相应API来实现动态代理功能,其中包括InvocationHandler(代理接口)、Proxy(生成动态代理类)
下面来看动态代理的例子:
需要在下面的业务方法添加时间耗时记录(不改动业务方法代码的前提下)

/**
 * 业务接口
 */
public interface BaseDao {
    Object findById(Long id);
}
/**
 * 业务接口实现类
 */
public class BaseDaoImpl implements BaseDao {
    @Override
    public Object findById(Long id) {
        System.out.println("查询ID:" + id);
        return new Object();
    }
}

接下来编写需要在业务方法前后执行的逻辑代码,首先继承InvocationHandler,重写方法

/**
 * 生成的代理类最终会执行invoke方法
 */
public class TimeInvokeHandler implements InvocationHandler {

    /**
     * 需要注入调用方法的对象,例子这里target为BaseDao的实现类
     */
    private Object target;

    public TimeInvokeHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("*************************调用之前********************************");
        System.out.println("happened in class: " + proxy.getClass().getName());
        System.out.println("happened in method: " + method.getName());
        for (int i = 0; i < args.length; i++) {
            System.out.println("arg[" + i + "]: " + args[i]);
        }
        Object res = method.invoke(target, args);//业务bean的方法,例子这里指findById()
        System.out.println("*************************调用之后********************************");
        return res;
    }
}

编写测试类:

public class Test {
    public static void main(String[] args) throws ParseException {
        BaseDao target = new BaseDaoImpl();//生成bean
        TimeInvokeHandler handler = new TimeInvokeHandler(target);//注入业务bean
        /**
         * 被代理类:BaseDaoImpl
         * Proxy.newProxyInstance()返回代理类
         * @loader   加载被代理类的"类加载器"
         * @interfaces   被代理类实现的接口
         * @h       执行代理代码的类(InvocationHandler)
         */
        BaseDao dao = (BaseDao)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
        dao.findById(8L);//调用返回的代理类的方法
    }
}

执行结果:

*************************调用之前********************************
happened in class: com.sun.proxy.$Proxy0
happened in method: findById
arg[0]: 8
查询ID:8
*************************调用之后********************************

看到的现象:调用了InvocationHandler的invoke()方法,而不是直接调用findById()方法,实现了不改动代码的前提下为业务逻辑增加一些判断(记录统计,日志等)
原因:注意上面的打印日志happened in class: com.sun.proxy.$Proxy0,这里的Proxy0就是main方法中的局部变量dao,就是newProxyInstance返回的代理对象,这个代理对象是newProxyInstance()方法中动态生成的的类的实例,这个动态类实现了BaseDao接口,所以可以转型为BaseDao。
具体newProxyInstance()代码(只显示主要的)如下:

/**
 * @param loader  加载被代理类的"类加载器"
 * @param interfaces  被代理类实现的接口
 * @param h      执行代理代码的类(InvocationHandler)
 */
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        throws IllegalArgumentException {
    Objects.requireNonNull(h);//参数h判空

    final Class<?>[] intfs = interfaces.clone();

    /*
     * 动态生成代理类的Class
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    try {
        //获取代理类的构造方法
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        //调用构造方法(需要传入参数h)返回代理类的实例(就是上面例子的com.sun.proxy.$Proxy0)
        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(loader, intfs)这行代码,生成了代理类的Class,该方法进去就是直接调用WeakCache的get(K key, P parameter)方法,先从缓存中查询是否已经生成了该代理类,没有则调用WeakCache的内部类Factory的get()方法,在get方法中最终调用Proxy的内部类ProxyClassFactory的apply(ClassLoader loader, Class

    /**
     * 生成代理类的Class
     * @param loader 类加载器
     * @param interfaces 代理类要实现的接口
     */
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {


        String proxyPkg = null; // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        /*
         * Record the package of a non-public proxy interface so that the
         * proxy class will be defined in the same package.  Verify that
         * all non-public proxy interfaces are in the same package.
         * 验证所有的不公共修饰的接口都要在同一包下,不然抛异常
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";//包名定义(use com.sun.proxy package)
        }

        /*
         * Choose a name for the proxy class to generate.
         */
        long num = nextUniqueNumber.getAndIncrement();//获取已生成代理类数量,并且+1返回
        String proxyName = proxyPkg + proxyClassNamePrefix + num;//包名+类名("$Proxy"+num)
        //最终proxyName = "com.sun.proxy.$Proxy0";代理类名就是$Proxy0

        /*
         * Generate the specified proxy class.
         * 重要:生成代理类字节码的字节数组!
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
        try {
            //根据字节数组返回代理类的Class
            /**
             * 返回对象实例的Class
             * @param ClassLoader loader 类加载器
             * @param String name 类名
             * @param byte[] b 生成Class所需要的字节数组(对象实例字节数组,这里为代理类实例)
             * @param int off  字节数组起始位置
             * @param int len  长度
             */
            return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the proxy
             * class generation code) there was some other invalid aspect of the
             * arguments supplied to the proxy class creation (such as virtual
             * machine limitations exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }

不难看出,关键的一行代码就是ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);,这行代码调用的java底层的方法,直接就返回了代理类的字节数组,根据字节数组就可以生成一个文件(class字节码文件),下面就来实验一下,把字节素质写入本地的一个文件class,然后反编译为java文件,就能一探代理类的究竟:

import sun.misc.ProxyGenerator;//导入ProxyGenerator

public class Test {
    public static void main(String[] args) throws FileNotFoundException, IOException {
        byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class<?>[]{BaseDao.class});
        new FileOutputStream(new File("d:/$Proxy0.class")).write(data);//在D盘新建一个文本文件,重命名为$Proxy0.class
    }
}

然后反编译$Proxy0.class得到以下代码:

//可以看到,代理类继承了Proxy实现了传入参数的接口
public final class $Proxy0 extends Proxy implements BaseDao {
    //实现接口的方法都是被定义在全局变量中,例Method m1,m2......
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    //构造方法,需要传入InvocationHandler参数,参数存放在Proxy中的全局变量h
    public $Proxy0(InvocationHandler arg0) throws  {
      super(arg0);
   }

    public final boolean equals(Object arg0) throws  {
      try {
         return ((Boolean)super.h.invoke(this, m1, new Object[]{arg0})).booleanValue();
      } catch (RuntimeException | Error arg2) {
         throw arg2;
      } catch (Throwable arg3) {
         throw new UndeclaredThrowableException(arg3);
      }
   }

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

    public final Object findById(Long arg0) throws  {
      try {
         return (Object)super.h.invoke(this, m3, new Object[]{arg0});
      } catch (RuntimeException | Error arg2) {
         throw arg2;
      } catch (Throwable arg3) {
         throw new UndeclaredThrowableException(arg3);
      }
   }

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

    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("com.aoomiao.test.BaseDao").getMethod("findById",
                    new Class[]{Class.forName("java.lang.Long")});
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException arg1) {
            throw new NoSuchMethodError(arg1.getMessage());
        } catch (ClassNotFoundException arg2) {
            throw new NoClassDefFoundError(arg2.getMessage());
        }
    }
}

猜你喜欢

转载自blog.csdn.net/AooMiao/article/details/82151187
今日推荐