文章目錄
- 一、介紹
- 二、 AOP術語
- 三、AOP介紹
-
- 3.1 什麼是AOP ?
- 3.2 Spring通知類型
- 四、面向切面程式設計
-
- 4.1 面向切面AspectJ
- 4.2 注解式的Aspect
一、介紹
在傳統的業務處理代碼中,通常都會進行事務處理、日志記錄等操作。雖然使用OOP可以通過組合或者繼承的方式來達到代碼的重用,但如果要實作某個功能(如日志記錄),同樣的代碼仍然會分散到各個方法中。這樣,如果想要關閉某個功能,或者對其進行修改,就必須要修改所有的相關方法。這不但增加了開發人員的工作量,而且提高了代碼的出錯率
AOP采取橫向抽取機制,将分散在各個方法中的重複代碼提取出來,然後在程式編譯或運作時,再将這些提取出來的代碼應用到需要執行的地方。這種采用橫向抽取機制的方式,采用傳統的OOP思想顯然是無法辦到的,因為OOP隻能實作父子關系的縱向的重用。雖然AOP是一種新的程式設計思想,但卻不是OOP的替代品,它隻是OOP的延伸和補充
二、 AOP術語
- Advice(通知/增強處理):AOP架構在特定的切入點執行的增強處理,即在定義好的切入點處所要執行的程式代碼。可以将其了解為切面類中的方法
- Target Object(目标對象):指所有被通知的對象,也被稱為被增強對象。如果AOP架構采用的是動态的AOP實作,那麼該對象就是一個被代理對象
- Proxy(代理):将通知應用到目标對象之後,被動态建立的對象
- Weaving(織入):将切面代碼插入到目标對象上,進而生成代理對象的過程
三、AOP介紹
3.1 什麼是AOP ?
- AOP采取橫向抽取機制,将分散在各個方法中的重複代碼提取出來
- 在程式編譯或運作時,再将這些提取出來的代碼應用到需要執行的地方
3.2 Spring通知類型
前置通知、後置通知、環繞通知、異常抛出通知、引介通知
-
org.springframework.aop.MethodBeforeAdvice(前置通知)
在目标方法執行前實施增強,可以應用于權限管理等功能
-
org.springframework.aop.AfterReturningAdvice(後置通知)
在目标方法執行後實施增強,可以應用于關閉流、上傳檔案、删除臨時檔案等功能
-
org.aopalliance.intercept.MethodInterceptor(環繞通知)
在目标方法執行前後實施增強,可以應用于日志、事務管理等功能
-
org.springframework.aop.ThrowsAdvice(異常抛出通知)
在方法抛出異常後實施增強,可以應用于處理異常記錄日志等功能
-
org.springframework.aop.IntroductionInterceptor(引介通知)
在目标類中添加一些新的方法和屬性,可以應用于修改老版本程式
四、面向切面程式設計
4.1 面向切面AspectJ
- 自定義的切面
public class MyAspect {
// 前置通知
public void beforeProxy(JoinPoint joinPoint) {
System.out.print("前置通知: 模拟執行權限檢查, ");
System.out.print("目标類是: " + joinPoint.getTarget());
System.out.println(", 被織入的方法: " + joinPoint.getSignature().getName());
}
// 後置通知
public void afterReturningProxy(JoinPoint joinPoint) {
System.out.print("後置通知:模拟記錄日志...");
System.out.print("目标類是:" + joinPoint.getTarget());
System.out.println(", 被織入的目标方法: " + joinPoint.getSignature().getName());
}
// ProceedingJoinPoint是 JoinPoint的子接口, 表示可以執行的目标方法
// 使用proceedingJoinPoint為參數,必須傳回一個對象 Object
// 必須接收一個參數,類型為 ProceedingJoinPoint
// 必須抛出異常 throws Throwable
public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("環繞通知開始執行, 模拟開啟事務: ");
Object obj = proceedingJoinPoint.proceed();
System.out.println("環繞通知結束, 模拟關閉事務...");
return obj;
}
public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
System.out.println("抛出異常: " + exception.getMessage());
}
// 最終通知
public void afterProxy() {
System.out.println("最終通知,結束後釋放資源... ");
}
}
- 編寫XML檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1 目标類 -->
<bean id="userDAO" class="com.hlq.AspectJ.UserDAOImpl" />
<!-- 2 切面 -->
<bean id="myAspect" class="com.hlq.AspectJ.MyAspect" />
<!-- 3 aop程式設計 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入點,通知最後增強哪些方法 -->
<aop:pointcut
expression="execution(* com.hlq.AspectJ.*.add*(..))"
id="addPointCut" />
<!-- 配置另一個切入點 -->
<aop:pointcut expression="execution(* com.hlq.AspectJ.*.delete*(..))" id="deletePointCut"/>
<!-- 3.2 關聯通知Advice和切入點pointCut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="beforeProxy" pointcut-ref="addPointCut" />
<!-- 3.2.2 後置通知,在方法傳回之後執行,就可以獲得傳回值 returning屬性:用于設定後置通知的第二個參數的名稱,類型是Object -->
<aop:after-returning method="afterReturningProxy"
pointcut-ref="deletePointCut" returning="returnVal" />
<!-- 3.2.3 環繞通知 -->
<aop:around method="aroundProxy" pointcut-ref="addPointCut" />
<!-- 3.2.4 抛出通知:用于處理程式發生異常-->
<!-- * 注意:如果程式沒有異常,将不會執行增強 -->
<!-- * throwing屬性:用于設定通知第二個參數的名稱,類型Throwable -->
<aop:after-throwing method="afterThrowingProxy"
pointcut-ref="addPointCut" throwing="exception" />
<!-- 3.2.5 最終通知:無論程式發生任何事情,都将執行 -->
<aop:after method="afterProxy" pointcut-ref="addPointCut" />
</aop:aspect>
</aop:config>
</beans>
- 測試
public class UserDAOImpl implements UserDAO {
public void addUser() {
System.out.println("UserDAO addUser is called... ");
}
public void deleteUser() {
System.out.println("UserDAO deleteUser is called...");
}
public UserDAOImpl() {
super();
}
}
public class AspectTest {
public static void main(String[] args) {
String xmlPath = "com/hlq/AspectJ/aspectConf.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
userDAO.addUser();
userDAO.deleteUser();
}
}
前置通知: 模拟執行權限檢查, 目标類是: [email protected], 被織入的方法: addUser
環繞通知開始執行, 模拟開啟事務:
UserDAO addUser is called...
最終通知,結束後釋放資源...
環繞通知結束, 模拟關閉事務...
UserDAO deleteUser is called...
後置通知:模拟記錄日志...目标類是:[email protected], 被織入的目标方法: deleteUser
4.2 注解式的Aspect
- 定義注解切面
@Aspect
@Component
public class AspectJ {
// 定義切入點表達式
@Pointcut("execution(* com.hlq.annotationAspect.*.add*(..))")
// 使用一個傳回值為void、方法體為空的方法來命名切入點
private void addPointCut() {
}
@Pointcut("execution(* com.hlq.annotationAspect.*.delete*(..))")
public void deletePointCut() {
}
// 前置通知
@Before("addPointCut()")
public void beforeProxy(JoinPoint joinPoint) {
System.out.print("前置通知: 模拟執行權限檢查, ");
System.out.print("目标類是: " + joinPoint.getTarget());
System.out.println(", 被織入的方法: " + joinPoint.getSignature().getName());
}
// 後置通知
@AfterReturning("deletePointCut()")
public void afterReturningProxy(JoinPoint joinPoint) {
System.out.print("後置通知:模拟記錄日志...");
System.out.print("目标類是:" + joinPoint.getTarget());
System.out.println(", 被織入的目标方法: " + joinPoint.getSignature().getName());
}
// // ProceedingJoinPoint是 JoinPoint的子接口, 表示可以執行的目标方法
// // 使用proceedingJoinPoint為參數,必須傳回一個對象 Object
// // 必須接收一個參數,類型為 ProceedingJoinPoint
// // 必須抛出異常 throws Throwable
// @Around("myPointCut()")
// public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//
// System.out.println("環繞通知開始執行, 模拟開啟事務: ");
//
// Object obj = proceedingJoinPoint.proceed();
//
// System.out.println("環繞通知結束, 模拟關閉事務...");
// return obj;
// }
//
// @AfterThrowing(value="myPointCut()", throwing="exception")
// public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
// System.out.println("抛出異常: " + exception.getMessage());
// }
//
// // 最終通知
// @After("myPointCut()")
// public void afterProxy() {
// System.out.println("最終通知,結束後釋放資源... ");
// }
}
- 配置XML檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 指定需要掃描的包,使注解生效 -->
<context:component-scan base-package="com.hlq.annotationAspect" />
<!-- 啟動基于注解的聲明式AspectJ支援 -->
<aop:aspectj-autoproxy />
</beans>
- 編寫任意測試類
@Repository("userDAO")
public class UserDAOImpl implements UserDAO {
public void addUser() {
System.out.println("UserDAO addUser is called... ");
}
public void deleteUser() {
System.out.println("UserDAO deleteUser is called...");
}
}
public class AspectTest {
public static void main(String[] args) {
String xmlPath = "com/hlq/annotationAspect/aspectConf.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
userDAO.addUser();
userDAO.deleteUser();
}
}
前置通知: 模拟執行權限檢查, 目标類是: [email protected], 被織入的方法: addUser
UserDAO addUser is called...
UserDAO deleteUser is called...
後置通知:模拟記錄日志...目标類是:com.hl[email protected], 被織入的目标方法: deleteUser