动态代理介绍
应用场景
假设现在已经存在一个children接口,其中一个方法是eat()你家有个小孩myChild类,你希望他讲卫生懂礼貌,以后成为新世纪的四好少年,所以要让他在吃饭前洗手,为了能够有时间打王者荣耀需要让他在吃饭后就睡觉。那这个时候的第一想法可能在children类中eat()方法的前后增加washHands和sleep的操作,或者增加方法washHand()和sleep(),然后在eat()方法中引用。这两种方法都可以解决这个问题,但是开闭原则告诉我们-对修改关闭╮(╯▽╰)╭。这样就堵死了这两种简单的方式。在你一筹莫展的时候,动态代理就站在了你的面前,拯救你于水火之中。
原理
我们在解决问题的时候往往想的就是简单直接的解决当前面对的问题,但是现实的情况往往不能如愿,既然不能够直接修改myChild类,那么就只能在你家小孩吃的之前增加一道洗手的程序,然后还是让你的小孩吃饭,吃饭之后再增加一道睡觉的程序。这个时候就相当于在吃饭这个操作的前后增加了不同的操作,但是对于吃饭本身来说并没有任何影响。
分类
动态代理分为两种:JDK提供的动态代理和第三方的CGLib
JDK动态代理示例
JDK动态代理简介
JDK动态代理顾名思义是由JDK提供的一种动态代理方式,简介结束。
类图
具体代码
撒话不说直接上代码
接口Children
public interface Children {
public void eat();
}
实现类MyChild
public class MyChild implements Children {
@Override
public void eat() {
System.out.println("eat Something");
}
}
处理类DynamicChildren
public class DynamicChildren implements InvocationHandler {
private Object children;
public DynamicChildren(Object object){
this.children = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Wash Hands!");
// 调用实际实例的eat方法
method.invoke(children, args);
System.out.println("Sleep!");
return null;
}
}
客户端Client
public class Client {
public static void main(String[] args) {
MyChild myChild = new MyChild();
InvocationHandler handler = new DynamicChildren(myChild);
Class> classType = handler.getClass();
/*classType.getClassLoader():获得DynamicChildren类加载器
myChild.getClass().getInterfaces():获得MyChild实现的所有接口类,当前的MyChild只实现了Children一类接口,所以这获得的只有这个
handler:DynamicChildren
children即为生成的动态代理类*/
Children children = (Children) Proxy.newProxyInstance(classType.getClassLoader(), myChild.getClass().getInterfaces(), handler);
// 这实际上是调用的DynamicChildren中的invoke方法
children.eat();
}
}
小结
以上就是我理解的JDK提供的动态代理相关的东西,完美是相对的,JDK动态代理也是一样有优缺点的。
优点
- 不依赖第三方jar包, 使用方便
- 随着JDK的升级,JDK动态代理的性能在稳步提升
缺点
- 只适用于实现了接口类
- 执行速度较慢
看了以上的内容,你的心中一定会冒出一个疑问,如果没有实现接口的类想要动态代理,是不是需要另外一种代理方式了呢?你猜的没错!这个时候就到了另一种动态代理-CGLibの出番だ!
CGLib简介
CGLIB(Code Generation Library)是一个开源项目!
是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。Hibernate> 支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成。
以上摘自百度百科
CGLib的原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截当前类的所有父类方法的调用,顺势织入横切逻辑。
使用的前置条件
需要在项目中依赖cglib相关的jar包和asm jar包。我使用的是cglib-2.2.jar、cglib-nodep-2.2.jar和asm-3.2.jar。
类图
具体代码
撒话不说直接上代码
需要代理的类MyChild
public class MyChild {
public void eat() {
System.out.println("eat something");
}
}
处理类CglibProxy
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
// 设置需要创建动态代理类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 通过字节码动态创建实例
return enhancer.create();
}
/**
* 实现 MethodInterceptor接口方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
System.out.println("Wash Hands!");
// 通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, arg);
System.out.println("Sleep!");
return result;
}
}
客户端类DoCGLib
public class DoCGLib {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
// 通过生成子类的方式来生成代理类
MyChild myChild = (MyChild) proxy.getProxy(MyChild.class);
myChild.eat();
}
}
小结
以上就是我理解的CGLib动态代理相关的东西,JDK动态代理有优缺点,那么CGLib动态代理也不能少。
优点
- 可以代理没有实现接口的对象
- 由于是动态生成字节码实现代理,因此代理对象的执行速度较快, 约为JDK动态代理的1.5 ~ 2倍
缺点
- 不能代理final类
- 动态生成字节码虽然执行较快,但是生成速度很慢,根据网上一些人的测试结果,CGLib创建代理对象的速度要比JDK慢10 ~ 15倍。
JDK动态代理和CGLib适用场景
- 由于JDK动态代理在创建代理对象上比CGLib快,所以如果你的程序需要频繁、反复地创建代理对象,请将JDK动态代理定位为备胎一号。
- 由于CGLib的执行速度比JDK快,所以如果不需要频繁创建代理对象的应用,如spring中默认的单例bean,只需要在容器启动时生成一次代理对象这种情况,请优先考虑转正CGLib。
以上です!