代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。
使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。
上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。
JDK动态代理
jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。先看下jdk动态代理的使用代码,再理解原理。首先有个“明星”接口类,有唱、跳两个功能:
public interface Star {
void sing();
void dance();
}
然后有明星实现类,“刘德华”
public class Liudehua implements Star { @Override public void sing() { System.out.println("唱歌"); }
@Override public void dance() { System.out.println("跳舞"); }
}
明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。
public class JdkProxy implements InvocationHandler {
private Object target; public void setTarget(Object target){ this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("JDK动态代理--收钱"); Object invoke = method.invoke(target, args); return invoke; } public Object createStarProxy(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); }
}
CGLIB动态代理
下面是CGLIB的代理类
public class CglibProxy implements MethodInterceptor {
public Object createCglibProxy(Class<?> tclass){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(tclass); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("CGLIB动态代理--收钱"); return methodProxy.invokeSuper(o,objects); }
}
测试类
public class TestProxy { public static void main(String[] args) { Liudehua liudehua = new Liudehua(); JdkProxy jdkProxy = new JdkProxy(); jdkProxy.setTarget(liudehua); Star star = (Star)jdkProxy.createStarProxy(); star.dance(); star.sing();
CglibProxy cglibProxy = new CglibProxy(); Liudehua cglibLiudehua =(Liudehua) cglibProxy.createCglibProxy(liudehua.getClass()); cglibLiudehua.dance(); cglibLiudehua.sing(); }
}
控制台输出
JDK动态代理--收钱
跳舞
JDK动态代理--收钱
唱歌
CGLIB动态代理--收钱
跳舞
CGLIB动态代理--收钱
唱歌
JDK动态代理和CGLIB的区别
DK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final, 对于final类或方法,是无法继承的
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类
在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐Spring如何选择用
JDK还是CGLIB?
当Bean实现接口时,Spring就会用JDK的动态代理。
当Bean没有实现接口时,Spring使用CGlib是实现。
为什么继承只能使用CGLib,因为JDK代理生成的代理类,默认会继承一个类,由于java是单继承,所以当原始类继承一个类的时候,只能使用CGLib动态代理
总结
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:
-
实现InvocationHandler
-
使用Proxy.newProxyInstance产生代理对象
-
被代理的对象必须要实现接口
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类。