快速學習-Spring 中的 AOP
- 2020 年 4 月 2 日
- 筆記
第2章 Spring 中的 AOP[掌握]
2.1 Spring 中 AOP 的細節
2.1.1 說明
我們學習 spring 的 aop,就是通過配置的方式,實現上一章節的功能。
2.1.2 AOP 相關術語
Joinpoint(連接點): 所謂連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因為 spring 只支援方法類型的連接點。
Pointcut(切入點): 所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。
Advice(通知/增強): 所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。 通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知。
Introduction(引介): 引介是一種特殊的通知在不修改類程式碼的前提下, Introduction 可以在運行期為類動態地添加一些方法或 Field。
Target(目標對象): 代理的目標對象。
Weaving(織入): 是指把增強應用到目標對象來創建新的代理對象的過程。spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。
Proxy(代理): 一個類被 AOP 織入增強後,就產生一個結果代理類。
Aspect(切面): 是切入點和通知(引介)的結合。
2.1.3 學習 spring 中的 AOP 要明確的事
a、開發階段(我們做的) 編寫核心業務程式碼(開發主線):大部分程式設計師來做,要求熟悉業務需求。把公用程式碼抽取出來,製作成通知。(開發階段最後再做):AOP 編程人員來做。 在配置文件中,聲明切入點與通知間的關係,即切面。:AOP 編程人員來做。 b、運行階段(Spring 框架完成的) Spring 框架監控切入點方法的執行。一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的程式碼邏輯運行。
2.1.4 關於代理的選擇
在 spring 中,框架會根據目標類是否實現了介面來決定採用哪種動態代理的方式。
2.2 基於 XML 的 AOP 配置
示例: 我們在學習 spring 的 aop 時,採用賬戶轉賬作為示例。並且把 spring 的 ioc 也一起應用進來。
2.2.1 環境搭建
2.2.1.1 第一步:準備必要的程式碼
此處包含了實體類,業務層和持久層程式碼。我們沿用上一章節中的程式碼即可。
2.2.1.2 第二步:拷貝必備的 jar 包到工程的 lib 目錄
此處要拷貝 spring 的 ioc 和 aop 兩組 jar 包

2.2.1.3 第三步:創建 spring 的配置文件並導入約束
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
2.2.1.4 第四步:配置 spring 的 ioc
<!-- 配置 service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置 dao --> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <property name="dbAssit" ref="dbAssit"></property> </bean> <!-- 配置資料庫操作對象 --> <bean id="dbAssit" class="com.itheima.dbassit.DBAssit"> <property name="dataSource" ref="dataSource"></property> <!-- 指定 connection 和執行緒綁定 --> <property name="useCurrentConnection" value="true"></property> </bean> <!-- 配置數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property> </bean>
2.2.1.5 第五步:抽取公共程式碼製作成通知
public class TransactionManager { //定義一個 DBAssit private DBAssit dbAssit; public void setDbAssit(DBAssit dbAssit) { this.dbAssit = dbAssit; } //開啟事務 public void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } //提交事務 public void commit() { try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } //回滾事務 public void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } //釋放資源 public void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } } }2.2.2 配置步驟
2.2.2.1 第一步:把通知類用 bean 標籤配置起來
<!-- 配置通知 --> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <property name="dbAssit" ref="dbAssit"></property> </bean>
2.2.2.2 第二步:使用 aop:config 聲明 aop 配置
aop:config: 作用:用於聲明開始 aop 的配置
<aop:config> <!-- 配置的程式碼都寫在此處 --> </aop:config>
2.2.2.3 第三步:使用 aop:aspect 配置切面
aop:aspect: 作用: 用於配置切面。 屬性: id:給切面提供一個唯一標識。 ref:引用配置好的通知類 bean 的 id。
<aop:aspect id="txAdvice" ref="txManager"> <!--配置通知的類型要寫在此處--> </aop:aspect>
2.2.2.4 第四步:使用 aop:pointcut 配置切入點表達式
aop:pointcut: 作用: 用於配置切入點表達式。就是指定對哪些類的哪些方法進行增強。 屬性: expression:用於定義切入點表達式。 id:用於給切入點表達式提供一個唯一標識
<aop:pointcut expression="execution( public void com.itheima.service.impl.AccountServiceImpl.transfer( java.lang.String, java.lang.String, java.lang.Float) )" id="pt1"/>
2.2.2.5 第五步:使用 aop:xxx 配置對應的通知類型
aop:before 作用: 用於配置前置通知。指定增強的方法在切入點方法之前執行 屬性:
method:用於指定通知類中的增強方法名稱 ponitcut-ref:用於指定切入點的表達式的引用 poinitcut:用於指定切入點表達式 執行時間點: 切入點方法執行之前執行 <aop:before method="beginTransaction" pointcut-ref="pt1"/> aop:after-returning 作用: 用於配置後置通知 屬性: method:指定通知中方法的名稱。 pointct:定義切入點表達式 pointcut-ref:指定切入點表達式的引用 執行時間點: 切入點方法正常執行之後。它和異常通知只能有一個執行 <aop:after-returning method="commit" pointcut-ref="pt1"/> aop:after-throwing 作用: 用於配置異常通知 屬性: method:指定通知中方法的名稱。 pointct:定義切入點表達式 pointcut-ref:指定切入點表達式的引用 執行時間點: 切入點方法執行產生異常後執行。它和後置通知只能執行一個 <aop:after-throwing method="rollback" pointcut-ref="pt1"/> aop:after 作用: 用於配置最終通知 屬性: method:指定通知中方法的名稱。 pointct:定義切入點表達式 pointcut-ref:指定切入點表達式的引用 執行時間點: 無論切入點方法執行時是否有異常,它都會在其後面執行。 <aop:after method="release" pointcut-ref="pt1"/>
2.2.3 切入點表達式說明
execution:匹配方法的執行(常用) execution(表達式) 表達式語法:execution([修飾符] 返回值類型 包名.類名.方法名(參數)) 寫法說明: 全匹配方式: public void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account) 訪問修飾符可以省略 void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account) 返回值可以使用*號,表示任意返回值 * com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account) 包名可以使用*號,表示任意包,但是有幾級包,需要寫幾個* * *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account) 使用..來表示當前包,及其子包 * com..AccountServiceImpl.saveAccount(com.itheima.domain.Account) 類名可以使用*號,表示任意類 * com..*.saveAccount(com.itheima.domain.Account) 方法名可以使用*號,表示任意方法 * com..*.*( com.itheima.domain.Account) 參數列表可以使用*,表示參數可以是任意數據類型,但是必須有參數 * com..*.*(*) 參數列表可以使用..表示有無參數均可,有參數可以是任意類型 * com..*.*(..) 全通配方式: * *..*.*(..) 註: 通常情況下,我們都是對業務層的方法進行增強,所以切入點表達式都是切到業務層實現類。 execution(* com.itheima.service.impl.*.*(..))
2.2.4 環繞通知
配置方式: <aop:config> <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/> <aop:aspect id="txAdvice" ref="txManager"> <!-- 配置環繞通知 --> <aop:around method="transactionAround" pointcut-ref="pt1"/> </aop:aspect> </aop:config> aop:around: 作用: 用於配置環繞通知 屬性: method:指定通知中方法的名稱。 pointct:定義切入點表達式 pointcut-ref:指定切入點表達式的引用 說明: 它是 spring 框架為我們提供的一種可以在程式碼中手動控制增強程式碼什麼時候執行的方式。 注意: 通常情況下,環繞通知都是獨立使用的
/** * 環繞通知 * @param pjp * spring 框架為我們提供了一個介面:ProceedingJoinPoint,它可以作為環繞通知的方法參數。 * 在環繞通知執行時,spring 框架會為我們提供該介面的實現類對象,我們直接使用就行。 * @return */ public Object transactionAround(ProceedingJoinPoint pjp) { //定義返回值 Object rtValue = null; try { //獲取方法執行所需的參數 Object[] args = pjp.getArgs(); //前置通知:開啟事務 beginTransaction(); //執行方法 rtValue = pjp.proceed(args); //後置通知:提交事務 commit(); } catch (Throwable e) { //異常通知:回滾事務 rollback(); e.printStackTrace(); } finally { //最終通知:釋放資源 release(); } return rtValue; }2.3 基於註解的 AOP 配置
2.3.1 環境搭建
2.3.1.1 第一步:準備必要的程式碼和 jar 包
拷貝上一小節的工程即可。
2.3.1.2 第二步:在配置文件中導入 context 的名稱空間
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置資料庫操作對象 --> <bean id="dbAssit" class="com.itheima.dbassit.DBAssit"> <property name="dataSource" ref="dataSource"></property> <!-- 指定 connection 和執行緒綁定 --> <property name="useCurrentConnection" value="true"></property> </bean> <!-- 配置數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property> </bean> </beans>
2.3.1.3 第三步:把資源使用註解配置
@Service("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; } @Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private DBAssit dbAssit; }2.3.1.4 第四步:在配置文件中指定 spring 要掃描的包
<!-- 告知 spring,在創建容器時要掃描的包 --> <context:component-scan base-package="com.itheima"></context:component-scan>
2.3.2 配置步驟
2.3.2.1 第一步:把通知類也使用註解配置
@Component("txManager") public class TransactionManager { //定義一個 DBAssit @Autowired private DBAssit dbAssit; }2.3.2.2 第二步:在通知類上使用@Aspect 註解聲明為切面
作用: 把當前類聲明為切面類。
@Component("txManager") @Aspect //表明當前類是一個切面類 public class TransactionManager { //定義一個 DBAssit @Autowired private DBAssit dbAssit; }2.3.2.3 第三步:在增強的方法上使用註解配置通知
@Before 作用: 把當前方法看成是前置通知。 屬性: value: 用於指定切入點表達式, 還可以指定切入點表達式的引用。 //開啟事務 @Before("execution(* com.itheima.service.impl.*.*(..))") public void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } @AfterReturning 作用: 把當前方法看成是後置通知。 屬性: value: 用於指定切入點表達式, 還可以指定切入點表達式的引用 //提交事務 @AfterReturning("execution(* com.itheima.service.impl.*.*(..))") public void commit() { try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } @AfterThrowing 作用: 把當前方法看成是異常通知。 屬性: value: 用於指定切入點表達式, 還可以指定切入點表達式的引用 //回滾事務 @AfterThrowing("execution(* com.itheima.service.impl.*.*(..))") public void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } @After 作用: 把當前方法看成是最終通知。 屬性: value: 用於指定切入點表達式, 還可以指定切入點表達式的引用 //釋放資源 @After("execution(* com.itheima.service.impl.*.*(..))") public void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } }2.3.2.4 第四步:在 spring 配置文件中開啟 spring 對註解 AOP 的支援
<!-- 開啟 spring 對註解 AOP 的支援 --> <aop:aspectj-autoproxy/>
2.3.3 環繞通知註解配置
@Around 作用: 把當前方法看成是環繞通知。 屬性: value: 用於指定切入點表達式, 還可以指定切入點表達式的引用。 /** * 環繞通知 * @param pjp * @return */ @Around("execution(* com.itheima.service.impl.*.*(..))") public Object transactionAround(ProceedingJoinPoint pjp) { //定義返回值 Object rtValue = null; try { //獲取方法執行所需的參數 Object[] args = pjp.getArgs(); //前置通知:開啟事務 beginTransaction(); //執行方法 rtValue = pjp.proceed(args); //後置通知:提交事務 commit(); } catch (Throwable e) { //異常通知:回滾事務 rollback(); e.printStackTrace(); } finally { //最終通知:釋放資源 release(); } return rtValue; }2.3.4 切入點表達式註解
@Pointcut 作用: 指定切入點表達式 屬性: value: 指定表達式的內容 @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1() {} 引用方式: /** * 環繞通知 * @param pjp * @return */ @Around("pt1()") //注意:千萬別忘了寫括弧 public Object transactionAround(ProceedingJoinPoint pjp) { //定義返回值 Object rtValue = null; try { //獲取方法執行所需的參數 Object[] args = pjp.getArgs(); //前置通知:開啟事務 beginTransaction(); //執行方法 rtValue = pjp.proceed(args); //後置通知:提交事務 commit(); } catch (Throwable e) { //異常通知:回滾事務 rollback(); e.printStackTrace(); } finally { //最終通知:釋放資源 release(); } return rtValue; }2.3.5 不使用 XML 的配置方式
@Configuration @ComponentScan(basePackages="com.itheima") @EnableAspectJAutoProxy public class SpringConfiguration { }
