要理解动态代理之前,我们先来聊聊代理模式。
1.静态代理
有一天,接到产品经理的需求,在这类的所有方法前后都打上日志。
如何能在不修改这个类(假设类设计都满足依赖倒转原则,即都有对应的接口)的前提下实现这个需求呢?
我们首先想到的就是代理模式,代理模式的定义是,给某一个类提供一个代理类,并由代理类来控制远对象的引用,通俗来讲就是中介。
假设我们原来有一个
public interface IPerson {
void shopping();
}
public class Person implements IPerson {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public void shopping() {
System.out.println(name + "买了个表e");
}
}
我们需要在shopping前后打log,于是我们需要一个代理类
public class PersonProxy implements IPerson {
private IPerson source;
public PersonProxy(IPerson source) {
this.source = source;
}
@Override
public void shopping() {
System.out.println("方法执行前打印");
source.shopping();
System.out.println("方法执行后打印");
}
}
然后加上代理类
public static void main(String[] args) {
//原有类
IPerson zhangsan=new Person("张三");
//代理类
IPerson proxy=new PersonProxy(zhangsan);
//由代理类执行
proxy.shopping();
}
打印
方法执行前打印
张三买了个表
方法执行后打印
可以看到这样就已经实现了产品经理的需求了~~~
这就是代理模式中的一种,也可以叫做静态代理
静态代理的优缺点:
- 优点:客户端不需要知道实现类怎么做的,只需要知道代理类即可,主需要专注本身的逻辑,而不需要关注太多外部的影响。
- 缺点:静态代理的缺点也很明显, 每个代理类只服务一个类,如果需要服务更多的类的时候,势必要为每一个类进行代理。
2.动态代理
有一天产品经理又说,上次的功能做的不错,现在又有一个新的需求,把所有类的所有方法前后,都打上日志。
哦豁~~~
如果还是按照静态代理的做法,我们需要为每一个类都实现一个代理类,这成百上千个类,是要搞死人的说。
有没有一些更加方便的方法呢,答案是有的,就是使用动态代理
与静态代理的区别,静态代理是需要我们手动去实现一个代理类的,而相反,动态代理则是可以让系统帮我们动态生成一个代理类。这么神奇?下面我们就来了解一下。
Java的java.lang.reflect包下提供了一个Proxy类和InvocationHandler接口,通过这个类和接口,可以动态生成JDK动态代理类和动态代理对象
创建动态代理的步骤:
//1.创建于代理对象相关联的InvocationHandler
InvocationHandler handler=new MyInvocationHandler<>(zhangsan);
//2.使用Proxy的getProxyClass获得代理类
Class<?> proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), Person.class.getInterfaces());
//3.获得生成的代理类的 参数问InvocationHandler.class的构造函数
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
//4.通过构造函数新建一个实例
Person proxyObj = (Person) constructor.newInstance(handler);
当然以上步骤Proxy集合在一起提供了一个更加方便调用的方法
//1.创建于代理对象相关联的InvocationHandler
InvocationHandler handler=new MyInvocationHandler<>(zhangsan);
//2.通过Proxy的newProxyInstance创建一个代理对象,来代理zhangsan,代理对象的每个执行方法都会替换执行InvocationHandler中的invoke方法
IPerson proxy = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), IPerson.class.getInterfaces(), handler);
先来看具体的代码
Person类省略,参考上面静态代理的代码
InvocationHandler类
public class MyInvocationHandler<T> implements InvocationHandler {
/**
* 被代理的类
*/
T target;
public MyInvocationHandler(T target) {
this.target = target;
}
/**
*
* @param proxy 代表动态代理对象
* @param method 正在执行的方法
* @param args 代表调用目标方法时传入的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行"+method.getName()+"方法");
//代码插入日志,这里只是为了方便,就直接打印了
System.out.println(method.getName()+"方法执行前打印");
Object invoke = method.invoke(target, args);
System.out.println(method.getName()+"方法执行后打印");
return invoke;
}
}
动态代理实现
//创建被代理对象
IPerson zhangsan=new Person("张三");
//创建于代理对象相关联的InvocationHandler
InvocationHandler handler=new MyInvocationHandler<>(zhangsan);
//创建一个代理类,来代理zhangsan
IPerson proxy = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), IPerson.class.getInterfaces(), handler);
//使用代理类执行方法
proxy.shopping();
看看上面的打印
打印
可以看到,使用动态代理创建的代理类执行方法,正常的完成了我们的打日志功能。
看到这里,我们不禁地有些疑问,为什么系统可以帮我们动态地实现代理类,他是如何做到的呢?带着疑问,我们来探究一下动态代理的原理
3.动态代理的原理初探
先要谈及原理,必然需要先从源码开始,实现动态代理很简单,核心代码只有一句,Proxy.newProxyInstance,我们先来看看这个方法里面的实现。
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
//1.先克隆需要代理的接口类信息
final Class<?>[] intfs = interfaces.clone();
//2.通过getProxyClass0得到代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
//3.获取代理类的构造函数(参数为InvocationHandler.class)
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
cons.setAccessible(true);
}
//4.获取代理类的实例
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方法,得到一个代理类,这里是获取代理类的关键,由于是动态生产了文件,具体代码就不想去分析了,这个类暂存在jvm虚拟机中的,我们可以通过下面的方法获取字节码文件
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Person.class.getInterfaces());
String path = "E:/PersonProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("代理类class文件写入成功");
} catch (Exception e) {
System.out.println("写文件错误");
}
ProxyGenerator在jdk的tl.jar包上,android studio无法直接访问到,于是使用了
IntelliJ IDEA 打印出来。
下面是系统生成的代理类的大概代码
/**
* 可以看到系统帮我们生产了一个$Proxy0类,并且实现了IPerson接口,也就是Person的代理类
*/
public final class $Proxy0 extends Proxy implements IPerson {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
//其实看到这个构造函数的时候,就已经瞬间明白了,InvocationHandler 相当于被代理类
//传入到代理类中,让代理类去处理具体事情,跟上面所描述的静态代理一样
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//在来看看这个静态代码块,主要关注m3,通过反射获取IPerson.shopping()这个方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("cn.lgh.mydemo.IPerson").getMethod("shopping");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
...
public final void shopping() throws {
try {
//super.h也就是我们通过构造函数传进来的InvocationHandler
//然后执行了invoke方法,并且把m3(上面反射得到的shopping方法)传过去,
//然后我们就可以在InvocationHandler中的invoke得到被代理类的真实方法了
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
...
}
看到这里,我们是不是已经明白了动态代理原理了。
jdk生成了$Proxy0(这里0的意思是,如果有多个代理类,就递增显示,默认从0开始)的代理类,这个类存放在内存中,当我们创建代理对象是,就是通过反射找到这个类的构造函数,然后创建这个类实例。通过查看这个类的源码,我们不难了解动态代理的原理。
4.总结
1.动态代理是对代理模式的实现
2.代理模式包括静态代理和动态代理
3.动态代理原理是通过Proxy这个类,让系统生成了一个代理类,并且把中介对象InvocationHandler传入代理列中,中介这对象持有被代理对象,当Proxy执行具体方法时,调用中介者(InvocationHandler)的invoke方法,然后我们在InvocationHandler的invoke方法中,调用被代理在的具体方法,达到代理的目的。
网友评论