谈到Java当中的动态代理,主流的就两种:JDK内置的动态代理和cglib动态代理。
JDK方式的动态代理要求被代理类必须要实现于接口(因为生成的代理对象首先要继承于java.lang.reflect.Proxy
,不能再多继承了),而且执行代理对象方法时候是使用反射的方式调用,故而在频繁调用代理方法时候,会有一定的性能问题。JDK方式动态代理不是今天讨论的重点,有兴趣可以留言,我再写一篇。
今天要讨论的主角是cglib方式的动态代理。
首先,我们可以假设市面上没有cglib动态代理的这种解决方案,然后在实际开发过程中发现,一旦要用到动态代理(主要场景就是aop),就必须多写一个接口,虽然说面向接口编程是挺好的,但程序员不就是因为懒才能推动技术进步么,能不能只写一个类且还能实现我的需求呢?
// 常规的写法
public interface UserService {
void eat();
void wc();
}
public class UserServiceImpl implements UserService {
@Override
public void eat() {
System.out.println("吃饭");
}
@Override
public void wc() {
System.out.println("上厕所");
}
}
然我我就想啊想,应该是可以的,画一个简易的流程图
这样一来,确实是可以实现我们的目的,拦截器里边可以把被代理对象的Method
传进来,然后就可以为所欲为,什么前置、后置、环绕的处理都是可以进行拓展。
那就简单的用伪代码实现之
// 被代理类
public class MyService {
public void show() {
System.out.println("实现类show");
}
}
// 拦截器
public interface MyInterceptor {
/**
*
* @param var1 代理对象
* @param var2 被代理类的原方法
* @param var3 方法参数
* @param var4 方法代理
* @return
* @throws Throwable
*/
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
// 自定义代理逻辑
public class MyProxy implements MyInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("前置处理");
o = methodProxy.invokeSuper(o, args);
System.out.println("后置处理");
return o;
}
}
// 增强器 用来生成代理对象
public class MyEnhancer {
private Class<?> superClass; // 父类类型
private MyInterceptor interceptor; // 拦截器
public MyEnhancer(Class<?> superClass, MyInterceptor interceptor) {
this.superClass = superClass;
this.interceptor = interceptor;
}
// 创建代理对象
public Object create() {
// 使用字节码生成技术 将真实对象类的class文件加载进来,通过修改字节码生成其子类,覆盖父类相应的方法。
// ... 忽略
return new MyService();
}
}
public class Test {
public static void main(String[] args) {
// 创建增强器
MyEnhancer enhancer = new MyEnhancer(MyService.class, new MyProxy());
// 创建代理对象
MyService proxy = (MyService) enhancer.create();
// 执行代理方法
proxy.show();
}
}
根据流程图大致用伪代码实现出来了。
增强器里的创建代理对象,其实就是生成一个新的Class对象放到方法区中,JDK动态代理是使用Sun的ProxyGenerator来生成字节码,而cglib是使用ASM的字节码框架,后者效率更高,具体就不展开了,有兴趣可以去研究。
问题来了!
我现在生成了代理对象,那么该如何调用呢?cglib是使用继承并重写父类方法的方式来实现动态代理,对于private、final修饰的方法(也就是不能被代理的方法),又该如何正确的调用呢?
这就头大了。来分析一波,首先这些方法嘛,有没有唯一的id呢?对了!方法签名(方法名字+方法参数)。那我可不可以再搞个类出来,它就专门负责方法路由,根据方法签名,或者可以取签名的hash值,路由到指定方法上去执行。
cglib确实就是这么做的,而且它生成的路由类是两个,一个负责代理方法的路由,另一个负责未被代理的方法路由。
好了,扯了一堆,现在看看cglib实际使用吧
public class PeopleService {
public final int eat(){
System.out.println("吃饭");
wc(); // 此处调用wc()方法是会走被代理过的方法 因为会重新走路由的 JDK方式是反射执行 就不可以走路由
return 1;
}
public void wc(){
System.out.print("上厕所");
}
}
public class MyProxy implements MethodInterceptor {
public Object createProxy(Class<?> c){
//代理类class文件存入本地磁盘
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\Users\\www\\Desktop\\my_challenge\\mynote\\src\\main\\java\\proxy\\mytest\\cglib\\classes");
Enhancer e = new Enhancer();
e.setSuperclass(c);
e.setCallback(this);
return e.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if ("eat".equals(method.getName())){
System.out.print("先洗手再----->");
}
o = methodProxy.invokeSuper(o, objects);
if ("wc".equals(method.getName())){
System.out.print("---->之后要洗手");
}
return o;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyProxy myProxy = new MyProxy();
PeopleService proxy = (PeopleService) myProxy.createProxy(PeopleService.class);
System.out.println(proxy.eat());
}
}
生成了3份字节码
这样子的话,修改下流程图,应该长这样
总结一下cglib的特点:
- 在JDK动态代理中,调用目标对象的方法使用的是反射,而在cglib动态代理中使用的是FastClass机制。
- cglib生成字节码的底层原理是使用ASM字节码框架。
- cglib动态代理需创建3份字节码,所以在第一次使用时会比较耗性能,但是后续使用较JDK动态代理方式更高效。适合单例bean场景。
- cglib由于是采用动态创建子类的方法,对于final方法,无法进行代理。