AOP是具有特定的應用場合的,它只適合那些具有橫切邏輯的應用場合,如性能檢測、訪問控制、事務管理及日志紀錄。
Spring AOP使用動態代理技術在運行期織入增強的代碼,Spring AOP使用了兩種代理機制:一種是基于JDK的動態代理;另一種是基于CGLib的動態代理。
下面我們通過一個帶有橫切邏輯的實例來實現這兩種代理方式:
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeTopic");
System.out.println("模擬刪除Topic記錄:"+topicId);
try {
Thread.sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
PerformanceMonitor.end();
}
public void removeForum(int forumId) {
PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeForum");
System.out.println("模擬刪除Forum記錄:"+forumId);
try {
Thread.sleep(40);
} catch (Exception e) {
throw new RuntimeException(e);
}
PerformanceMonitor.end();
}
}
測試代碼:
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1012);
上面代碼每個方法執行前后都加入了性能檢測代碼,這是完美的橫切邏輯。
JDK動態代理
Java1.3后,Java提供了動態代理技術,運行開發者在運行期間創建接口的代理實例。JDK動態代理主要涉及java.lang.reflect包中的兩個類:Proxy和InvocationHandler。InvocationHandler是一個接口,可以通過實現該接口定義橫切邏輯,并通過反射機制調用目標類的代碼,動態地將橫切邏輯和業務邏輯編織在一起。
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;
}
}
測試代碼:
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);
CGLib動態代理
CGLib采用底層的字節碼技術,可以為一個類創建子類,在子類中采用方法攔截的技術攔截所有父類方法的調用并順勢織入橫切邏輯。
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;
}
}
測試代碼:
CglibProxy cglibProxy = new CglibProxy();
ForumService forumService = (ForumService)cglibProxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1023);
需要注意的是,由于CGLib采用動態創建子類的方式生成代理對象,所以不能對目標類中的final或private方法進行代理。
為什么會有兩種代理機制
- JDK創建代理有一個限制,即它只能為接口創建代理實例,雖然面向接口編程是好的編程習慣,但有時候并不是必須的,這是JDK動態代理的局限性。
- 就性能來說,CGLib所創建的動態代理對象的性能比JDK所創建的動態代理對象的性能高差不多10倍,CGLib在創建代理對象時所話費的時間卻比JDK動態代理大概多8倍,但是對于singleton的代理對象或者具有實例池的代理,因為無需頻繁創建代理對象,所以比較合適采用CGLib動態代理技術,反之則適合采用JDK動態代理技術。