什么是代理?
设计模式的一种,通过代理对象去访问目标对象,而外部只能访问到代理对象。为了增强目标类的逻辑。主要解决直接访问对象带来的问题,想在访问类之前做一些控制。代理模式前提是必须有接口。
代理对象:增强后的对象。
目标对象:被增强的对象。
代理技术:一个对象代替另一个对象去完成一些工作。
JAVA实现动态代理的两种方式
静态代理和动态代理
静态代理
继承
代理对象继承目标对象,重写需要增强的方法
public class UserDaoImpl {
public void query() {
System.out.println("查询数据库");
}
}
// 通过继承,增强query的功能
public class UserDaoLogImpl extends UserDaoImpl {
@Override
public void query() {
System.out.println("-----Logger-----");
super.query();
}
}
测试
UserDaoLogImpl userDaoLog = new UserDaoLogImpl();
userDaoLog.query();
结果 -----Logger----- 查询数据库
聚合
代理对象和目标对象实现同一个接口,代理对象中要包含目标对象(通过构造器传递)
实现
// 定义接口
public interface UserDao {
void query();
}
// 目标对象
public class UserDaoImpl implements UserDao {
public void query() {
System.out.println("查询数据库");
}
}
// 代理对象
public class UserDaoLog implements UserDao {
UserDao dao;
// 构造器,传递目标对象
public UserDaoLog (UserDao dao) {
this.dao = dao;
}
@Override
public void query() {
System.out.println("-----Logger-----");
dao.query();
}
}
测试
UserDaoImpl target = new UserDaoImpl();
UserDaoLog proxy = new UserDaoLog(target);
proxy.query();
结果 -----Logger----- 查询数据库
缺点
两个实现代理的方式都会产生类爆炸式增长。如果在不确定的情况下, 尽量不要去使用静态代理。因为一旦你写代码,就会产生类,一旦产生类就爆炸。
动态代理
既然静态代理会产生类爆炸,那么怎么样才能避免。
通过接口反射生成一个类文件(.java),然后调用第三方编译技术,动态编译类文件(.class),然后使用ClassLoader把编译后的.class文件加载到JVM中,最后通过反射把这个类实例化(因为动态产生的类不在工程中,需要使用到UrlClassLoader)。即反射得到java文件 ----> 动态编译为.class文件 ----> ClassLoader加载到JVM中 ----> 反射生成类对象
如何动态的执行我们的逻辑
自定义接口及实现类,然后传递接口,通过method.invoke()来调用我们的目标方法
这就是jdk动态代理的简单版本实现
JDK动态代理解析
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
newProxyInstance,通过指定的interfaces接口返回代理类对象。传入
InvocationHandler的实现类来执行我们自定义的逻辑。
通过JDK动态代理得到代理类
public class UserDaoImpl implements UserDao {
public void query() {
System.out.println("查询数据库");
}
}
public interface UserDao {
void query();
}
public class MyInvocationHandler implements InvocationHandler {
// 传入目标对象
Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* @param proxy 代理对象
* @param method 目标方法
* @param args 参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("MyInvocationHandler --- jdk");
return method.invoke(target, args);
}
}
测试
UserDao jdkUserDao = (UserDao) Proxy.newProxyInstance(Test.class.getClassLoader(),
new Class[] {UserDao.class}, new MyInvocationHandler(new UserDaoImpl()));
jdkUserDao.query();
结果 MyInvocationHandler --- jdk 查询数据库
查看JDK动态代理源码来验证上述过程
1.首先通过newProxyInstance的java.lang.reflect.Proxy#getProxyClass0 的方法我们得到了一个代理类。然后通过java反射得到代理对象。
// 根据类加载器和需要实现的接口得到代理类class
Class<?> cl = getProxyClass0(loader, intfs);
// 得到代理类的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 根据参数new一个代理类
return cons.newInstance(new Object[]{h});
2.我们发现通过java.lang.reflect.Proxy#getProxyClass0得到了代理类。getProxyClass0方法中对代理类实现了缓存机制。如果存在由实现了给定接口的给定加载器定义的代理类,则将仅返回缓存的副本;否则,它将通过ProxyClassFactory创建代理类。此处不再展示其代码
3.跳过缓存机制,我们可以发现得到代理类的方法主要在java.lang.reflect.Proxy.ProxyClassFactory#apply中。默认我们生成的代理的名字为com.sun.proxy.$Proxy + num.主要代码如下
public static final String PROXY_PACKAGE = "com.sun.proxy";
private static final String proxyClassNamePrefix = "$Proxy";
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
long num = nextUniqueNumber.getAndIncrement();
// 得到代理类的名字
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 得到代理类的二进制文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
// 根据二进制数组得到代理类的class
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
我们输出动态代理类产生的二进制文件。
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{UserDao.class});
FileOutputStream fileOutputStream = new FileOutputStream("f:\\$Proxy0.class");
fileOutputStream.write(bytes);
fileOutputStream.flush();
fileOutputStream.close();
代理类会实现equals、toString、hashCode方法,此处只列出重要方法。也可以运行测试代码查看生成的文件。
public final class $Proxy0 extends Proxy implements UserDao {
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void query() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException) {}
}
static {
try {
m3 = Class.forName("com.zjy.proxy1.UserDao").getMethod("query");
} catch (NoSuchMethodException var2) {}
}
}
发现当调用query方法时会调用传入的InvocationHandler中的invoke方法。同时传入生成的代理对象,目标对象的方法,和参数
defineClass0函数
private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
正常情况下,java会先调用classLoader去加载.class文件,然后调用loadClass函数去加载对应的类名,返回一个Class对象。而defineClass提供了另外一种方法,defineClass可以从byte[]还原出一个Class对象。
JDK动态代理
JDK中的代理是面向接口的代理,创建出来的代理都是java.lang.reflect.Proxy的子类。通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的invoke方法进行处理。
cglib动态代理
Cglib的代理是面向父类的代理,本质上它是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法),实际上为通过继承来实现的
区别
jdk动态代理只能对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,java不允许多重继承)。cglib能代理普通类
java的动态代理使用java原生的反射Api进行操作,在生成类上比较高效。cglib使用asm框架直接对字节码进行操作。在类的执行过程中比较高效。