代理在生活中随处可见,比如说我要买房,我一般不会直接和卖家对接,一般会和中介打交道,中介就是代理,卖家就是目标对象,我就是调用者,代理不仅实现了目标对象的行为(帮目标对象卖房),还可以添加上自己的动作(收保证金,签合同等)
用UML图来表示就是下面这样:
Client 是直接和 Proxy 打交道的,Proxy 是 Client 要真正调用的 RealSubject 的代理,它确实执行了 RealSubject 的 request 方法,不过在这个执行前后 Proxy 也加上了额外的 PreRequest(),afterRequest() 方法,注意 Proxy 和 RealSubject 都实现了 Subject 这个接口,这样在 Client 看起来调用谁是没有什么分别的(面向接口编程,对调用方无感,因为实现的接口方法是一样的),Proxy 通过其属性持有真正要代理的目标对象(RealSubject)以达到既能调用目标对象的方法也能在方法前后注入其它逻辑的目的。
代理主要分为两种类型:静态代理和动态代理,动态代理又有 JDK 代理和 CGLib 代理两种,我先解释下静态和动态的含义。
1. 静态代理
要理解静态和动态这两个含义,我们首先需要理解一下 Java 程序的运行机制
首先 Java 源代码经过编译生成字节码,然后再由 JVM 经过类加载,链接,初始化成 Java 类型,可以看到字节码是关键,静态和动态的区别就在于字节码生成的时机。
静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在编译时已经将接口,被代理类(委托类),代理类等确定下来,在程序运行前代理类的.class文件就已经存在了
动态代理:在程序运行后通过反射创建生成字节码再由 JVM 加载而成。
public interface Subject {public void request();}public class RealSubject implements Subject {@Overridepublic void request() {// 卖房System.out.println("卖房");}}public class Proxy implements Subject {private RealSubject realSubject;public Proxy(RealSubject subject) {this.realSubject = subject;}@Overridepublic void request() {// 执行代理逻辑System.out.println("卖房前");// 执行目标对象方法realSubject.request();// 执行代理逻辑System.out.println("卖房后");}public static void main(String[] args) {// 被代理对象RealSubject subject = new RealSubject();// 代理Proxy proxy = new Proxy(subject);// 代理请求proxy.request();}}
静态代理主要有两大劣势
- 代理类只代理一个委托类(其实可以代理多个,但不符合单一职责原则),也就意味着如果要代理多个委托类,就要写多个代理(别忘了静态代理在编译前必须确定)
- 第一点还不是致命的,再考虑这样一种场景:如果每个委托类的每个方法都要被织入同样的逻辑,比如说我要计算前文提到的每个委托类每个方法的耗时,就要在方法开始前,开始后分别织入计算时间的代码,那就算用代理类,它的方法也有无数这种重复的计算时间的代码
静态代理的这些劣势主要是是因为在编译前这些代理类是确定的,如果这些代理类是动态生成的呢,是不是可以省略一大堆代理的代码。
2. 动态代理
动态代理分为 JDK 提供的动态代理和 Spring AOP 用到的 CGLib 生成的代理,我们先看下 JDK 提供的动态代理该怎么写
JDK动态代理
代码
// 委托类public class RealSubject implements Subject {@Overridepublic void request() {// 卖房System.out.println("卖房");}}import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class ProxyFactory {private Object target;// 维护一个目标对象public ProxyFactory(Object target) {this.target = target;}// 为目标对象生成代理对象public Object getProxyInstance() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("计算开始时间");// 执行目标对象方法method.invoke(target, args);System.out.println("计算结束时间");return null;}});}public static void main(String[] args) {RealSubject realSubject = new RealSubject();System.out.println(realSubject.getClass());Subject subject = (Subject) new ProxyFactory(realSubject).getProxyInstance();System.out.println(subject.getClass());subject.request();}}
打印结果如下:
原始类:class com.example.demo.proxy.staticproxy.RealSubject代理类:class com.sun.proxy.$Proxy0计算开始时间卖房计算结束时间
我们注意到代理类的 class 为 com.sun.proxy.$Proxy0,它是如何生成的呢,注意到 Proxy 是在 java.lang.reflect 反射包下的,注意看看 Proxy 的 newProxyInstance 签名
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
- loader: 代理类的ClassLoader,最终读取动态生成的字节码,并转成 java.lang.Class 类的一个实例(即类),通过此实例的 newInstance() 方法就可以创建出代理的对象
- interfaces: 委托类实现的接口,JDK 动态代理要实现所有的委托类的接口
- InvocationHandler: 委托对象所有接口方法调用都会转发到 InvocationHandler.invoke(),在 invoke() 方法里我们可以加入任何需要增强的逻辑 主要是根据委托类的接口等通过反射生成的
基于JDK动态代理的好处?
由于动态代理是程序运行后才生成的,哪个委托类需要被代理到,只要生成动态代理即可,避免了静态代理那样的硬编码,另外所有委托类实现接口的方法都会在 Proxy 的 InvocationHandler.invoke() 中执行,这样如果要统计所有方法执行时间这样相同的逻辑,可以统一在 InvocationHandler 里写, 也就避免了静态代理那样需要在所有的方法中插入同样代码的问题,代码的可维护性极大的提高了。基于JDK动态代理的问题
注意第二个参数 Interfaces 是委托类的接口,是必传的, JDK 动态代理是通过与委托类实现同样的接口,然后在实现的接口方法里进行增强来实现的,这就意味着如果要用 JDK 代理,委托类必须实现接口,这样的实现方式看起来有点蠢,更好的方式是什么呢,直接继承自委托类不就行了,这样委托类的逻辑不需要做任何改动,CGlib 就是这么做的cglib动态代理
AOP 就是用的 CGLib 的形式来生成的,JDK 动态代理使用 Proxy 来创建代理类,增强逻辑写在 InvocationHandler.invoke() 里,CGlib 动态代理也提供了类似的 Enhance 类,增强逻辑写在 MethodInterceptor.intercept() 中,也就是说所有委托类的非 final 方法都会被方法拦截器拦截,在说它的原理之前首先来看看它怎么用的代码
```java public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
} }System.out.println("目标类增强前!!!");//注意这里的方法调用,不是用反射哦!!!Object object = proxy.invokeSuper(obj, args);System.out.println("目标类增强后!!!");return object;
public class CGlibProxy { public static void main(String[] args) { //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer = new Enhancer(); //设置目标类的字节码文件 enhancer.setSuperclass(RealSubject.class); //设置回调函数 enhancer.setCallback(new MyMethodInterceptor());
//这里的creat方法就是正式创建代理类RealSubject proxyDog = (RealSubject) enhancer.create();//调用代理类的eat方法proxyDog.request();
} }
打印如下```java代理类:class com.example.demo.proxy.staticproxy.RealSubject$$EnhancerByCGLIB$$889898c5目标类增强前!!!卖房目标类增强后!!!
可以看到主要就是利用 Enhancer 这个类来设置委托类与方法拦截器,这样委托类的所有非 final 方法就能被方法拦截器拦截,从而在拦截器里实现增强
底层实现原理
它是通过继承自委托类,重写委托类的非 final 方法(final 方法不能重载),并在方法里调用委托类的方法来实现代码增强的,它的实现大概是这样
public class RealSubject {@Overridepublic void request() {// 卖房System.out.println("卖房");}}/** 生成的动态代理类(简化版)**/public class RealSubject$$EnhancerByCGLIB$$889898c5 extends RealSubject {@Overridepublic void request() {System.out.println("增强前");super.request();System.out.println("增强后");}}
可以看到它并不要求委托类实现任何接口,而且 CGLIB 是高效的代码生成包,底层依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能比 JDK 强,所以 Spring AOP 最终使用了 CGlib 来生成动态代理
CGlib 动态代理使用上的限制
第一点之前已经已经说了,只能代理委托类中任意的非 final 的方法,另外它是通过继承自委托类来生成代理的,所以如果委托类是 final 的,就无法被代理了(final 类不能被继承)
相关问题
JDK 动态代理的拦截对象是通过反射的机制来调用被拦截方法的,CGlib 呢,它通过什么机制来提升了方法的调用效率。
于反射的效率比较低,所以 CGlib 采用了FastClass 的机制来实现对被拦截方法的调用。FastClass 机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,建议参考下https://www.cnblogs.com/cruze/p/3865180.html
首先 Java 源代码经过编译生成字节码,然后再由 JVM 经过类加载,链接,初始化成 Java 类型,可以看到字节码是关键,静态和动态的区别就在于字节码生成的时机。