Spring AOP中包含两种AOP代理方式,分别是JDK动态代理和CGlib动态代理
首先看一下JDK动态代理。
JDK动态代理主要设计到java.lang.reflect包中的两个类:Proxy和InvocationHandler.其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
如下,通过模拟论坛删除Topic和删除Forum记录来举例,这个例子是通过操作执行的时间来监控性能的。在ForumServiceImpl中没有性能监视的横切代码:
ForumServiceImpl.java
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
// PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.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("com.baobaotao.proxy.ForumServiceImpl.removeForum");
System.out.println("模拟删除Forum记录:"+forumId);
try {
Thread.currentThread().sleep(40);
} catch (Exception e) {
throw new RuntimeException(e);
}
// PerformanceMonitor.end();
}
}
注释的部分就是性能监视的代码,我们将性能监视的代码整理如下,PerformanceHandler实现了InvocationHandler
PerformaceHandler.java
public class PerformaceHandler implements InvocationHandler {
private Object target;
public PerformaceHandler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName()); //①
Object obj = method.invoke(target, args); //②
PerformanceMonitor.end(); //③
return obj;
}
}
上面的代码中invoke()方法中①和的③部分就是性能监视的横切代码,②处的语句通过java的反射机制间接调用了目标对象的方法,这样就可以将横切逻辑代码和业务逻辑代码编织到一起。
上面的代码中,我们实现了InvocationHandler接口,该接口定义了一个invoke(Object proxy, Method method, Object[] args)方法,参数定义如下:
@param proxy the proxy instance that the method was invoked on
@param method the {@code Method} instance corresponding to the interface method invoked on the proxy instance. The declaring class of the {@code Method} object will be the interface that the method was declared in, which may be a superinterface of the proxy interface that the proxy class inherits the method through.
@param args an array of objects containing the values of the arguments passed in the method invocation on the proxy instance, or {@code null} if interface method takes no arguments. Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper class, such as {@code java.lang.Integer} or {@code java.lang.Boolean}.
proxy是最终生成的代理实例;method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用,args是传入实例某一个方法的入参数组,在方法反射调用时使用。
在上面的构造函数中通过target传入希望被代理的目标对象,在执行method.invoke()方法时调用目标实例的方法。
PerformanceMonitor.java
public class PerformanceMonitor {
private static ThreadLocal performaceRecord = new ThreadLocal();
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformace mp = performaceRecord.get();
if(mp == null){
mp = new MethodPerformace(method);
performaceRecord.set(mp);
}else{
mp.reset(method);
}
}
public static void end() {
System.out.println("end monitor...");
MethodPerformace mp = performaceRecord.get();
mp.printPerformace();
}
}
MethodPerformace.java
public class MethodPerformace {
private int random;
private long begin;
private long end;
private String serviceMethod;
public MethodPerformace(String serviceMethod){
reset(serviceMethod);
}
public void printPerformace(){
end = System.currentTimeMillis();
long elapse = end - begin;
System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
System.out.printf(this.toString());
}
public void reset(String serviceMethod){
// this.random = (int) Math.random();
Random rd = new Random();
this.random = rd.nextInt(10);
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();
}
@Override
public String toString() {
return "MethodPerformace{" +
"random=" + random +
", begin=" + begin +
", end=" + end +
", serviceMethod='" + serviceMethod + '\'' +
'}';
}
}
MethodPerformace是对每个方法执行时间进行记录和性能整理的类,使用ThreadLocal是因为ThreadLocal为每个使用该变量的线程提供独立的变量副本,这里考虑到PerformanceMonitor被当做工具类使用,有可能被多个线程调用,有关ThreadLocal的使用可以参考其他资料,这里也可以直接将performaceRecord变量声明为一个静态变量,此处仅为测试,所以只有一个线程来调用该类,修改如下:
public class PerformanceMonitor {
// private static ThreadLocal performaceRecord = new ThreadLocal();
private static MethodPerformace performaceRecord = null;
public static void begin(String method) {
System.out.println("begin monitor...");
// MethodPerformace mp = performaceRecord.get();
MethodPerformace mp = performaceRecord;
if(mp == null){
mp = new MethodPerformace(method);
performaceRecord = mp;
// performaceRecord.set(mp);
}else{
mp.reset(method);
}
}
public static void end() {
System.out.println("end monitor...");
// MethodPerformace mp = performaceRecord.get();
MethodPerformace mp = performaceRecord;
mp.printPerformace();
}
}
测试JDK代理的代码如下:
public class TestForumService {
public static void main(String[] args) {
// 业务类正常编码的测试
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1012);
// 使用JDK动态代理
ForumService target = new ForumServiceImpl();//①
PerformaceHandler handler = new PerformaceHandler(target);//②
ForumService proxy = (ForumService) Proxy.newProxyInstance(target
.getClass().getClassLoader(),
target.getClass().getInterfaces(), handler);//③
proxy.removeForum(10);//④
proxy.removeTopic(1012);//⑤
}
}
上面①处是希望被代理的目标业务类,②处将目标业务类和横切代码编织到一起,在③处根据目标业务类逻辑和性能监视横切逻辑的InvocationHandler实例创建代理实例,最后④⑤处调用代理实例。
③处使用了Proxy的newProxyInstance()静态方法为编织了业务逻辑和性能监视横切逻辑的handler创建了一个符合ForumServic接口的代理实例。该方法的第一个入参为类加载器,第二个为创建代理实例所需要实现的一组接口,第三个为整合业务逻辑和横切逻辑的编织器对象。
如下图,是通过创建代理对象进行业务方法调用的整体逻辑时序图:
接下来看一下CGlib动态代理
通过上面的JDK代理实现了横切逻辑和业务逻辑的分离,但是它有一个显示,即它只能为接口创建代理实例,Proxy接口的newProxyInstance方法的第二个参数就是需要代理实例实现的接口列表。对于没有通过接口定义业务方法的类,CGLib就可以完成动态代理。
CGLib是通过创建子类的方式实现的,并在子类中拦截所有父类方法的调用,顺势织入横切逻辑。
CglibProxy.java
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 obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());
Object result=proxy.invokeSuper(obj, args);
PerformanceMonitor.end();
return result;
}
}
上面代码中,用户可以通过getProxy(Class clazz)为一个类创建动态代理对象,该代理对象通过扩展(子类的方式)clazz创建代理对象。在对象中织入性能监视的横切逻辑。
Enhancer是用来通过字节码技术动态创建子类实例的。
其中,intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)是CGlib定义的接口方法,它拦截所有的目标类方法调用,var1为目标类实例,var2为目标类方法的反射对象,var3为方法的入参,var4为代理类实例,下面通过CGLib创建代理类:
public class TestForumService {
public static void main(String[] args) {
// 使用CGLib动态代理
CglibProxy proxy = new CglibProxy();
ForumService forumService = (ForumService)proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1023);
}
}
上面两种方式各有优缺点,首先JDK动态代理不能创建没有接口的类的代理,其次,其次两者的性能是有一定的区别的。CGlib创建的对象的性能JDK高,但是CGLib创建对象的时间话费却比JDK动态代理多,所以对于singleton的代理对象或者具有实例池的代理,由于不需要频繁的创建代理对象,所以比较适合采用CGlib动态代理技术,反之则JDK适合。