在代理模式中我们介绍的例子其实是一种静态代理,静态代理通常只代理一个类,且需要实现这个类的接口,也就是需要知道它到底代理了什么,很显然这种方式在某些特定的小场景下可以适用,但是要是到了大的层面,例如我希望给所有的控制层加一层日志打印,那么如果一个个实现代理或者实现相同的接口显然是不可能,这时候就需要了动态代理,它利用反射机制,能够事先不需要知道代理的是什么,在运行时再确定。现在大家会很经常听到一个名词叫面向横切面编程,也就是AOP(Aspect Oriented Programming)。
JDK提供了java.lang.reflect.InvocationHandler
接口和 java.lang.reflect.Proxy
类,借助这两个类我们可以完成我们的动态代理,它的入口是Proxy
类。
打开Proxy
源码,我们可以发现这个类暴露的公共方法其实就4个getProxyClass()
, getInvocationHandler()
,isProxyClass()
,newProxyInstance()
而且都是静态方法,我们一般只关心newProxyInstance
方法
Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
:传入类加载器,一组接口再加上动态代理的Handler就能自动为你生成一个经过代理的Bean!接下去我们再聊聊InvocationHandler
。
我们简单介绍一下原理:
动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,也就是在运行中生成了一个类似被代理类的拷贝类,拥有所有的类结构信息,但是默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过InvocationHandler
接口,所有方法都由该Handler
来进行处理,即所有被代理的方法都由InvocationHandler
接管实际的处理任务,所以invoke
方法是接口InvocationHandler
定义必须实现的,它完成对真实方法的调用!
例子
定义一个接口:
public interface Foo {
void doSomething();
}
定义一个实现类:
public class FooImpl implements Foo{
@Override
public void doSomething() {
System.out.println("这是一个实现类");
}
}
为了实现一个代理的前置通知,我们包装了一下Proxy类的接口:
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
//寻找JoinPoint连接点,AOP框架使用元数据定义
if (true) {
//执行一个前置通知
(new BeforeAdvice()).exec();
}
//执行目标,并返回结果
return (T) Proxy.newProxyInstance(loader, interfaces, h);
}
}
通知类
public interface IAdvice {
void exec();
}
public class BeforeAdvice implements IAdvice {
@Override
public void exec() {
System.out.println("我是前置通知,我被执行了!");
}
}
动态代理的Handler类:
public class MyInvocationHandler implements InvocationHandler {
private Object target=null;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这里可以打印一些必要日志");
return method.invoke(target, args);
}
}
最后就是客户端类:
public class Client {
public static void main(String[] args) {
Foo foo = new FooImpl();
InvocationHandler invocationHandler = new MyInvocationHandler(foo);
Foo foo1 = DynamicProxy.newProxyInstance(foo.getClass().getClassLoader(), foo.getClass().getInterfaces(), invocationHandler);
foo1.doSomething();
}
}
输出:
我是前置通知,我被执行了!
这里可以打印一些必要日志
这是一个实现类
小结
要实现动态代理的首要条件是:被代理类必须实现一个接口!当然动态代理不仅仅只有JDK一家,CGLIB可以实现不需要接口也可以实现动态代理的方式。调试时,只要看到类似$Proxy0
这样的结构,大家就可以判断这是一个动态代理!
辨析:CGLib与JDK动态代理
JDK动态代理
利用拦截器(拦截器必须实现InvocationHanlder
)加上反射机制生成一个实现代理接口的匿名类;
在调用具体方法前调用InvokeHandler
来处理。
CGLib动态代理
利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
关于两者之间的性能:JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:
1、CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;
2、但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;
更多详情参见:https://blog.csdn.net/yhl_jxy/article/details/80635012
参考
《设计模式之禅》