代理:代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能的特点也为多人所用,在不改动源文件代码的前提下在代码前后方增加相应的逻辑,例如:日志记录,异常统一捕获打印。
动态代理: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()*方法,如果你的接口有多个方法,生成的代理类都会实现这些方法,换言之,接口的方法都会被代理
生成动态代理过程: