一、动态代理实现AOP的缺陷
在上一篇文章细说Spring——AOP详解(动态代理实现AOP)中讲解了如何使用动态代理实现AOP,虽然Java动态代理为我们提供了非常灵活的代理机制,但Java动态代理是基于接口的,如果目标对象没有实现接口我们该如何代理呢?这时候我们就需要使用CGLIB来实现AOP了。
二、CGLIB实现代理的原理
我们先创建一个目标对象
package demo1;
/**
* Created by Yifan Jia on 2018/6/9.
*/
public class SomeService {
public String doFirst() {
System.out.println("执行doFirst()方法");
return "abcde";
}
public void doSecond() {
System.out.println("doSecond()方法");
}
}
针对这个目标类,假如我们要使用动态代理实现AOP,那么我们只能在写一个增强的接口,然后让目标类实现增强接口,然后我们就可以使用动态代理实现目标类的增强,可是假如我们不想让目标类实现其他的接口,那么我们就只能使用CGLIB技术来实现目标类的增强了。
CGLIB实现目标类增强的原理是这样的:CGLIB会动态创建一个目标类的子类,然后返回该子类的对象,也就是增强对象,至于增强的逻辑则是在子类中完成的。我们知道子类要么和父类有一样的功能,要么就比父类功能强大,所以CGLIB是通过创建目标类的子类对象来实现增强的,所以:
目标子类 = 目标类 + 增强逻辑
至于CGLIB底层是如何动态的生成一个目标类的子类,它是使用动态字节码技术,我们知道我们编写的Java对象都是先编译为.class
文件,然后由类加载器加载到内存中变为一个Java对象的,动态字节码技术就是通过转换字节码生成新的类来实现改变一个类的内部逻辑的。至于更基础的部分,我也没有深入的研究,有兴趣的可以自己研究一下。
三、使用CGLIB实现AOP
这里需要注意,我们需要导入CGLIB的相关包才能使用CGLIB,这里需要导入两个包:
-
cglib-nodep-3.2.0.jar
:使用nodep包不需要关联asm的jar包,jar包内部包含asm的类. -
cglib-3.2.0.jar
:使用此jar包需要关联asm的jar包,否则运行时报错.
版本可以自行选择,我是有的Maven导入的jar包,依赖如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.0</version>
</dependency>
通过CGLIB代理实现如下:
- 首先实现一个
MethodInterceptor
,方法调用会被转发到该类的intercept()
方法。 - 然后在需要使用
SomeService
(目标对象)的时候,通过CGLIB动态代理获取代理对象。
我们仍然使用上面的SomeService
作为目标对象,然后我们实现一个MethodInterceptor
,在实现MethodInterceptor
之前,我们先看一下这个接口是什么:
public interface MethodInterceptor extends Callback {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}
这里解释一下各个参数的意思:
- Object为由CGLib动态生成的代理类实例
- Method为上文中实体类所调用的被代理的方法引用
- Object[]为参数值列表
- MethodProxy为生成的代理类对方法的代理引用。
下面是实现的一个MethodInterceptor
,同时写了创建增强对象的逻辑即myCglibCreator()
方法,该方法返回增强对象。
public class CglibFactory implements MethodInterceptor {
public SomeService myCglibCreator() {
Enhancer enhancer = new Enhancer();
//将目标类设置为父类,cglib动态代理增强的原理就是子类增强父类,cglib不能增强目标类为final的类
//因为final类不能有子类
enhancer.setSuperclass(SomeService.class);
//设置回调接口,这里的MethodInterceptor实现类回调接口,而我们又实现了MethodInterceptor,其实
//这里的回调接口就是本类对象,调用的方法其实就是intercept()方法
enhancer.setCallback(this);
//create()方法用于创建cglib动态代理对象
return (SomeService) enhancer.create();
}
//回调接口的方法
//回调接口的方法执行的条件是:代理对象执行目标方法时会调用回调接口的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invokeSuper(o, objects);
//这里实现将返回值字符串变为大写的逻辑
if(result != null) {
result = ((String) result).toUpperCase();
}
return result;
}
}
其中,Object result = methodProxy.invokeSuper(o, objects);
调用代理类实例上的methodProxy
方法的父类方法(即实体类SomeService
中对应的方法),然后返回目标方法的返回值result
,然后实现增强的逻辑,将返回的字符串变为大写的。下面是测试代码:
package demo1;
/**
* Created by Yifan Jia on 2018/6/9.
*/
public class Test {
public static void main(String[] args) {
SomeService target = new SomeService();
SomeService proxy = new CglibFactory(target).myCglibCreator();
String result = proxy.doFirst();
System.out.println(result);
proxy.doSecond();
}
}
结果截图:
上述代码中,我们通过CGLIB
的Enhancer
来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()
方法得到代理对象,对这个对象所有非final
方法的调用都会转发给MethodInterceptor.intercept()
方法,在intercept()
方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用MethodProxy.invokeSuper()
方法,我们将调用转发给原始对象,具体到本例,就是SomeService
的具体方法。CGLIG
中MethodInterceptor
的作用跟JDK动态代理代理中的InvocationHandler
很类似,都是方法调用的中转站。
四、总结
我们学习了通过CGLIB实现动态增强,但是CGLIB也有其缺陷,那就是必须目标类必须是可以继承的,如果目标类不可继承,那么我们就无法使用CGLIB来增强该类,现在我们已经学习完了Spring AOP中两种AOP的实现机制,我们可以称JDK动态代理实现的AOP为面向接口的动态增强
,将CGLIB实现的AOP称为面向子类的动态增强
。