Java动态代理的两种实现方法:JDK动态代理和CGLIB动态代理
代理模式JDK动态代理CGLIB动态代理代理模式
代理模式是23种设计模式的一种,指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但B是真正的实现类,A则比较“虚”,A借用了B的方法去实现接口的方法。A虽然为“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态植入。使用动态代理的好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,JDK动态代理和CGLIB动态代理。JDK动态代理
JDK动态代理是JRE提供的类库,可以直接使用,不依赖第三方。先看一下JDK动态代理的使用代码,在理解原理。
首先,有个明星接口,有唱和跳两个方法。
package proxy;public interface Star {String sing(String name);String dance(String name);}
再有一个明星实现类-刘德华。
package proxy;public class LiuDeHua implements Star {public String sing(String name) {System.out.println("给我一杯忘情水!");return "唱完";}public String dance(String name) {System.out.println("开心的马骝");return "跳完";}}
明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。
package proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class StarProxy implements InvocationHandler {//目标类,被代理对象private Object target;public void setTarget(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//这里可以做增强System.out.println("收钱");Object result = method.invoke(target, args);return result;}//生成代理类public Object CreateProxyedObj() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}}
上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。需要注意的是这个CreatProxyedObj方法不一定非得在我们的StarProxy类中,往往放在一个工厂类中。上述代理的代码使用过程一般如下:
1、new一个目标对象
2、new一个InvocationHandler,将目标对象set进去
3、通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。
package proxy;public class Client {public static void main(String[] args) {Star ldh = new LiuDeHua();StarProxy proxy = new StarProxy();proxy.setTarget(ldh);Star proxyedObj = (Star) proxy.CreateProxyedObj();proxyedObj.sing("刘德华");}}
Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。
所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,上述例子中就是StarProxy, 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。
看下我们的实现,我们在InvocationHandler里调用了对象B(target)的方法,调用之前增强了B的方法。
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{// 这里增强System.out.println("收钱");Object result = method.invoke(target, args);return result;}
所以可以这么认为C代理了InvocationHandler,InvocationHandler代理了我们的类B,两级代理。
整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。
CGLIB动态代理
代理的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有包装对象这一方式。
通过继承可以继承父类所有得公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是CGLIB的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以CGLIB实现的代理也是可以被正常使用的。
package proxy;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class CglibProxy implements MethodInterceptor{// 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中public Object CreatProxyedObj(Class<?> clazz){Enhancer enhancer = new Enhancer();enhancer.setSuperclass(clazz);enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable{// 这里增强System.out.println("收钱");return arg3.invokeSuper(arg0, arg2);} }
从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。
cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“父类方法名父类方法名父类方法名”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。
这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。
C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。