JDK动态代理分析

代理:代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能的特点也为多人所用,在不改动源文件代码的前提下在代码前后方增加相应的逻辑,例如:日志记录,异常统一捕获打印。
动态代理: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<>[] interfaces)*方法,接下来进去该方法的源码(显示主要的):

	/**
     * 生成代理类的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
	//上面最开始的Test类中我传入了TimeInvokeHandler
	public $Proxy0(InvocationHandler arg0) throws  {
      super(arg0);
   }
	//重写equals方法,调用了代理方法invoke
	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);
      }
   }
	//重写toString方法,调用了代理方法invoke
	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);
      }
   }
	//BaseDao的接口实现,调用了h.invoke()方法,就是最开始编写的TimeInvokeHandler的invoke方法
	//方法中的this就是代理类实例本身
	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);
      }
   }
	//重写hashCode方法,调用了代理方法invoke
	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());
		}
	}
}

到达这里之后代理就不难理解了,值得注意的是代理类上面所出现的四个方法equals()、toString()、findById()、hashCode()这四个方法都是被代理了,调用这些方法都是去执行我们生成代理时传入参数InvocationHandler实例的*invoke()*方法,如果你的接口有多个方法,生成的代理类都会实现这些方法,换言之,接口的方法都会被代理
生成动态代理过程:

  • 继承InvocationHandler接口,重写invoke()方法,编写需要植入的代码
  • newProxyInstance(ClassLoader loader,Class<>[] interfaces,InvocationHandler h)生成代理类
  • getProxyClass0(loader, intfs)生成代理类的Class
  • Class获取构造器,传入参数h,实体化代理类对象并返回
  • 返回的代理类可以被转型为实现的接口,然后调用接口的方法就是调用代理的方法h.invoke()

你可能感兴趣的:(JDK动态代理分析)