参考链接:https://mp.weixin.qq.com/s/Lx11nFqIJJC8d2aX3PmCPQ
业务处理中,使用代理的情况:
(1)设计模式中有一个设计原则是开闭原则,是说对修改关闭对扩展开放,我们在工作中有时会接手很多前人的代码,里面代码逻辑让人摸不着头脑(sometimes the code is really like shit),这时就很难去下手修改代码,那么这时我们就可以通过代理对类进行增强。
(2)我们在使用RPC框架的时候,框架本身并不能提前知道各个业务方要调用哪些接口的哪些方法 。那么这个时候,就可用通过动态代理的方式来建立一个中间人给客户端使用,也方便框架进行搭建逻辑,某种程度上也是客户端代码和框架松耦合的一种表现。
(3)Spring的AOP机制就是采用动态代理的机制来实现切面编程。
动态代理和静态代理的区别:
静态代理是在编译时就引入代理类,而动态代理则是在运行时才动态生成代理类。简单来说就是通过动态代理我们不再需要手动的去写一个一个的代理类了,而是在运行时动态代理技术自动帮我们生成这个代理类。
有两种方法实现动态代理:一是通过接口;一是通过继承。这两种方法也衍生出了Java中实现动态代理的两种方案:JDK动态代理和Cglib动态代理。
JDK动态代理
JDK动态代理的实现是在运行时,根据一组接口定义,使用Proxy、InvocationHandler等工具类去生成一个代理类和代理类实例。
下面动手写个JDK动态代理的代码样例:
/**
* @Description 代理测试接口
*/
//第一步,定义一个接口。这个接口里面定义一个方法helloWorld()。
public interface MyIntf {
void helloWorld();
}
/**
* @Description 目标类
*/
//第二步,编写一个我们自己的调用处理类,这个类需要实现InvocationHandler接口
//InvocationHandler接口只有一个待实现的invoke方法。
//这个方法有三个参数,proxy表示动态代理类实例,method表示调用的方法,args表示调用方法的参数。
//在实际应用中,invoke方法就是我们实现业务逻辑的入口。
//这里我们的实现逻辑就一行代码,打印当前调用的方法
//(在实际应用中这么做是没有意义的,不过这里我们只想解释JDK动态代理的原理,所以越简单越清晰
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
return null;
}
}
/**
* @Description JDK动态代理测试类
*/
//第三步,直接使用Proxy提供的方法创建一个动态代理类实例。
//并调用代理类实例的helloWorld方法,检测运行结果
public class ProxyTest {
public static void main(String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
MyIntf proxyObj = (MyIntf)Proxy.newProxyInstance(MyIntf.class.getClassLoader(),new Class[]{MyIntf.class},new MyInvocationHandler());
proxyObj.helloWorld();
}
}
第8行代码是设置系统属性,把生成的代理类写入到文件。这里再强调一下,JDK动态代理技术是在运行时直接生成类的字节码,并载入到虚拟机执行的。这里不存在class文件的,所以我们通过设置系统属性,把生成的字节码保存到文件,用于后面进一步分析。
第9行代码就是调用Proxy.newProxyInstance方法创建一个动态代理类实例,这个方法需要传入三个参数,第一个参数是类加载器,用于加载这个代理类。第二个参数是Class数组,里面存放的是待实现的接口信息。第三个参数是InvocationHandler实例。
第10行调用代理类的helloWorld方法,运行结果:
public abstract void com.tuniu.distribute.openapi.common.annotation.MyIntf.helloWorld()
分析运行结果,就可以发现,方法的最终调用是分派到了MyInvocationHandler.invoke方法,打印出了调用的方法信息。
到这里,对于JDK动态代理的基本使用就算讲完了。我们做的事情很少,只是编写了接口MyIntf和调用处理类MyInvocationHandler。其他大部分的工作都是Proxy工具类帮我们完成的。Proxy帮我们创建了动态代理类和代理类实例。
Cglib动态代理
和JDK动态代理不同,Cglib动态代理是基于Java的继承机制实现的。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。简单的实现举例:
/这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理
public class SayHello {
public void say(){
System.out.println("hello everyone");
}
}
intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。
public class CglibProxy implements MethodInterceptor{
//实现MethodInterceptor接口方法
@override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置代理");
return result;
}
}
具体实现类:
public class DoCGLib {
public static void main(String[] args) {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(SayHello.class);
//设置回调函数
enhancer.setCallback(new CglibProxy ());
//这里的create是正式创建代理类
SayHello proxyImp = (SayHello)enhancer.create();
//调用代理类的say方法
proxyImp.say();
}
}
输出结果:
前置代理
hello everyone
后置代理
总结
CgLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CgLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CgLib合适,反之,使用JDK方式要更为合适一些。同时,由于CgLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
JDK动态代理是在运行时JDK根据class文件的格式动态拼装class文件,并加载到jvm中生成代理对象的。而Cglib动态代理是通过ASM库来操作class文件动态生成代理类的。同时你应该了解到:JDK动态代理是基于java中的接口实现的,Cglib是基于java中的继承实现的。