文章目錄
AOP引入:動態代理處理公共代碼
重複代碼問題
拿我上一個博客那個例子來說,需要事務處理的不僅僅只有一個方法,如果每個方法都使用同樣的流程進行事務處理,大量的重複代碼就會出現。
就像下面這樣:
public boolean transfer(String fromUsername, String toUsername, double money){
transactionManager.openTranscation();
try {
User from = userDao.getUserByUsername(fromUsername);
User to = userDao.getUserByUsername(toUsername);
from.setAccount(from.getAccount() - money);
to.setAccount(to.getAccount() + money);
userDao.updateUser(from);
userDao.updateUser(to);
transactionManager.commit();
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
return true;
}
如果需要事務處理,就會出現和上面代碼流程及其類似的代碼,不同的地方只有業務部分的代碼。
所以,可以提取出如下的公共代碼
public Object function(...){
transactionManager.openTranscation();
try {
/******業務代碼******/
transactionManager.commit();
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
}
到這裏,可以想到Java的動態代理。使用動態代理對象調用方法時,每個方法都會調用InvocationHandler.invoke()方法,因此,公共代碼可以放到invoke()方法中
在這裏,使用代理模式,對於需要進行事務處理的方法時,通過代理對象進行調用。對於不需要事務處理的方法,就可以使用被代理對象直接調用。先看一下類圖:
思路是這樣的,Servcice
通過一個工廠方法創建一個動態代理對象,如果使用該代理方法調用,則會加入事務處理,如果使用被代理對象本身的方法,則不會加入事務處理。
實現代碼如下:
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
private UserDaoImpl userDao;
@Autowired
private TransactionManager transactionManager;
/**
* 沒有實現事務處理,使用代理模式對該方法進行增強
*/
@Override
public boolean transfer(String fromUsername, String toUsername, double money) {
User from = userDao.getUserByUsername(fromUsername);
User to = userDao.getUserByUsername(toUsername);
if(from.getAccount() < money)
return false;
from.setAccount(from.getAccount() - money);
to.setAccount(to.getAccount() + money);
userDao.updateUser(from);
userDao.updateUser(to);
return true;
}
//創建一個有事務處理的動態代理對象
@Override
public IUserService getTransactionProxy() {
class InvocationHandlerImpl implements InvocationHandler {
IUserService userService;
public InvocationHandlerImpl(IUserService userService) {
this.userService = userService;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
transactionManager.openTranscation();
try{
Object retvalue = method.invoke(userService, objects);
transactionManager.commit();
return retvalue;
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
}
}
return (IUserService) Proxy.newProxyInstance(this.getClass().getClassLoader(),
this.getClass().getInterfaces(),
new InvocationHandlerImpl(this));
}
}
AOP相關概念
AOP(Aspect Orientend Programming)
AOP,面向切面編程。通過預編譯或運行期間動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續。
定義中提到的統一維護,指的就是對公共代碼進行維護。
連接點(JoinPoint)
連接點是指被代理對象的方法。這些方法可以通過AOP進行功能性的增強。
切入點(PointCut)
被AOP進行功能性增強的連接點,被稱作切入點。
目標對象(Target)
被代理的對象,上面例子中的Servcie
對象,他需要被代理,實現功能性的增強。
通知(Advice)
對切入點進行功能性增強的部分。
通知的分類:
- 前置通知
- 後置通知
- 異常通知
- 最終通知
- 環繞通知
舉例:
public Object function(...){
transactionManager.openTranscation();
try {
/******切入點方法調用******/
transactionManager.commit();
}catch (Exception e){
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
transactionManager.closeTransaction();
}
}
在切入點方法調用之前的通知transactionManager.openTransaction()
,就稱爲前置通知。
在切入點方法調用之後的通知trasactionManager.commit()
,稱爲後置通知。
在異常處理中執行的通知trasactionManager.rollback()
,稱爲異常通知。
進行資源回收等操作的通知transactionManager.closeTransaction()
,稱爲最終通知(在finally代碼塊中)。
對於整個function()
方法,被稱作環繞通知。對於環繞通知的處理,可以像上面一樣,當攔截切入點方法後,對切入點方法進行功能性增強;也可以不調用切入點方法,這樣就相當於使用環繞通知修改切入點功能。
可以看到,在實現時,需要一個“通知對象”(trasactionManager),對被代理對象的方法進行功能性增強
織入(Weaving)
把功能上的增強應用到被代理對象的過程。就像上面的例子,使用各種通知對切入點進行功能增強的過程。
代理(Proxy)
一個類被AOP植入後,產生一個代理類。通過代理對象調用切入點方法,會執行通知對象的通知及被代理對象的切入點方法(如果使用環繞通知,可能不會執行切入點方法)。(在上面的例子中,採用動態代理的方式,代理的類型和被代理對象的類型是相同的)
切面(Aspect)
切面 = 切入點 + 通知。
對一個切面進行定義時:定義切入點&&定義通知