Spring AOP使用动态代理技术在运行期织入增强的代码,使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。
(1)带有横切逻辑的实例
一个论坛管理的业务类,业务代码前后都是重复性能监控功能的代码。
import org.testng.annotations.Test;
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
//开始性能监控
PerformanceMonitor.begin("removeTopic");
System.out.println("模拟删除Topic记录:" + topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
//结束对方法的性能监控
PerformanceMonitor.end();
}
public void removeForum(int forumId) {
//开始性能监控
PerformanceMonitor.begin("removeTopic");
System.out.println("模拟删除Topic记录:" + forumId);
try {
Thread.currentThread().sleep(40);
} catch (Exception e) {
throw new RuntimeException(e);
}
//结束对方法的性能监控
PerformanceMonitor.end();
}
@Test
public void TestForumService() {
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1013);
}
}
这些非业务代码破坏了业务逻辑的纯粹性,我们希望通过代理的方式将业务类方法中性能监控的横切代码完全移除,并通过JDK或CGLib动态代理技术将横切代码织入目标方法的相应位置。
(2)JDK动态代理
JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。
首先从业务类中溢出性能监视的横切代码:
public void removeTopic(int topicId) {
//PerformanceMonitor.begin("removeTopic");
System.out.println("模拟删除Topic记录:" + topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
//结束对方法的性能监控
//PerformanceMonitor.end();
}
public void removeForum(int forumId) {
//PerformanceMonitor.begin("removeTopic");
System.out.println("模拟删除Topic记录:" + forumId);
try {
Thread.currentThread().sleep(40);
} catch (Exception e) {
throw new RuntimeException(e);
}
//PerformanceMonitor.end();
}
将性能监视横切代码安置在PerformanceHandler中,如下:
public class PerformanceHandler implements InvocationHandler {
//target为目标业务类
private Object target;
public PerformanceHandler(Object target) {
this.target = target;
}
//method是被代理目标实例的某个具体方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
PerformanceMonitor.begin(method.getName());
Object obj = method.invoke(target, args);
PerformanceMonitor.end();
return obj;
}
}
最后,结合PerformanceHandler创建接口的代理实例,如下:
@Test
public void proxyTest() {
//希望被代理的目标业务类
ForumService target = new ForumServiceImpl();
//将目标业务类和横切代码编织在一起
PerformanceHandler handler = new PerformanceHandler(target);
//创建代理实例
ForumService proxy=(ForumService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
proxy.removeTopic(10);
proxy.removeForum(1012);
}
(3)CGLib动态代理
使用JDK创建动态代理有一个限制,即它只能为接口创建代理实例,这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader, Class[ ] interfaces, InvocationHandler h)看的很清楚,第二个入参interfaces就是需要代理实例实现的接口列表。
那么对于没有通过接口定义业务方法的类,如何 动态创建代理实例呢?这时候就需要使用CGLib。
CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//拦截父类所有方法的调用
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
PerformanceMonitor.begin(method.getName());
//通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(o, objects);
PerformanceMonitor.end();
return result;
}
}
测试方法如下:
@Test
public void CglibProxyTest() {
CglibProxy proxy = new CglibProxy();
//通过动态生成子类的方式创建代理类
ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1012);
}