前提:本文假设你已经了解AOP切面编程的基础概念
AOP的逻辑
首先我们定义一个被代理类
@Bean
public class A interface I{
public void print() {
System.out.println("aaa");
}
}
复制代码
定义切点和切面
@Bean
@Aspect
public class AdviceSample {
@Before
@Pointcut(beanName = "A")
public void enhance2Before() {
System.out.println("before");
}
}
复制代码
后续我们使用A的时候,都会先打印切面的内容,也就是:
before
aaa
复制代码
如何将切面的内容添加到代理的方法里面去呢?
切面切入实现
JDK动态代理实现
提到代理,最先想到的是JDK提供的动态代理类InvocationHandler
,下面尝试使用JDK动态代理实现 ,切入的方式假设使用before
,而after
和around
看完就懂如何实现了。
public class JDKDynamicProxy implements InvocationHandler {
private Method before = null;
private Object beforeObj = null;
/**
* @param proxy 代理的真实对象
* @param method 要调用的真实对象的方法
* @param args 方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行before方法
if (before != null) {
before.invoke(beforeObj);
}
// 执行本体方法
Object invoke = method.invoke(target, args);
return invoke;
}
/**
* 获取动态代理对象
*
* @param obj 被代理类
* @return obj的父接口实现子类, 因为返回的是T父接口重写的类
*/
@SuppressWarnings("unchecked")
public <T> T getDynamicProxyImpl(T obj,Method before, Object beforeObj) {
assert obj != null;
this.before = before;
this.beforeObj = beforeObj;
// newProxyInstance() 会创建类$A,$A实现了obj的父接口,所有接口方法内容都为 {@link #invoke()} 。
return (T) newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
复制代码
统一调用getDynamicProxyImpl()
来获取新的代理类,参数里面传入before的Method对象,invoke()
方法定义了在执行本体方法之前执行before
方法,这样新的代理类都会先执行before
方法再执行本体方法了。
实现非常简单易懂,那么要实现after
方法就是可以传入after的Method
对象,然后在invoke()
的本体方法之后执行,around
的实现不用再增加新的逻辑了,只要同时传入before
和after
的Method
对象,这样新的代理类就会同时执行before()
和after()
了。
Cglib实现
JDK动态代理可以实现,但是有约束条件就是被代理类必须要有父接口,因为JDK动态实现的原理就是实现接口生成增强的子类。那么如果一个类没有父接口该如何进行切面增强呢?Spring
中使用了cglib
这个库来实现。
public class CglibProxy implements MethodInterceptor {
public static final Enhancer ENHANCER = new Enhancer();
private Method before = null;
private Object beforeObj = null;
private Method after = null;
private Object afterObj = null;
/**
* @param obj cglib动态代理生成的实例
* @param method 被调用的方法的引用
* @param params 参数列表
* @param methodProxy 代理类对方法的代理引用
*/
@Override
public Object intercept(Object obj, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
if (before != null) {
before.invoke(beforeObj);
}
Object result = methodProxy.invokeSuper(obj, params);
if (after != null) {
after.invoke(afterObj);
}
return result;
}
public void setBefore(Method before, Object beforeObj) {
this.before = before;
this.beforeObj = beforeObj;
}
public void setAfter(Method after, Object afterObj) {
this.after = after;
this.afterObj = afterObj;
}
@SuppressWarnings("unchecked")
public static <T> T getProxy(T target, Method before, Object beforeObj, Method after, Object afterObj) {
ENHANCER.setSuperclass(target.getClass());
CglibProxy cglibProxy = new CglibProxy();
cglibProxy.setBefore(before, beforeObj);
cglibProxy.setAfter(after, afterObj);
ENHANCER.setCallback(cglibProxy);
return (T) ENHANCER.create();
}
}
复制代码
实现的思路和JDK动态代理几乎一致,通过调用getProxy()
方法传入before()
或after()
方法就可以获得代理类了。
为什么cglib的实现不需要接口呢?cglib的实现原理是为被代理类创建一个子类,然后将切面通过ASM提供的字节码技术织入到目标方法中,这样子类的方法就包含了本体和切面的逻辑了。