Spring 黑馬程序員四天筆記(第三天:AOP)

第1章 AOP 的相關概念[理解]

1.1AOP 概述

1.1.1 什麼是 AOP

AOP:全稱是 Aspect Oriented Programming 即:面向切面編程。

簡單的說它就是把我們程序重複的代碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改源碼的 基礎上,對我們的已有方法進行增強。

1.1.2 AOP 的作用及優勢

作用:

在程序運行期間,不修改源碼對已有方法進行增強。

優勢:

減少重複代碼

提高開發效率

維護方便

1.1.3 AOP 的實現方式

使用動態代理技術

1.2 AOP 的具體應用

1.2.1 案例中問題

這是我們昨天課程中做的增刪改查例子。下面是客戶的業務層實現類。我們能看出什麼問題嗎?

/**
* 賬戶的業務層實現類
* @author 黑馬程序員
* @Company http://www.ithiema.com
* @Version 1.0
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao;
}
@Override
public void saveAccount(Account account) throws SQLException { accountDao.save(account);
}
@Override
public void updateAccount(Account account) throws SQLException{ accountDao.update(account);
}
@Override
public void deleteAccount(Integer accountId) throws SQLException{ accountDao.delete(accountId);
}
@Override
public Account findAccountById(Integer accountId) throws SQLException {
return accountDao.findById(accountId); }
@Override
public List<Account> findAllAccount() throws SQLException{
return accountDao.findAll(); }
}

問題就是:

事務被自動控制了。換言之,我們使用了 connection 對象的 setAutoCommit(true)

此方式控制事務,如果我們每次都執行一條 sql 語句,沒有問題,但是如果業務方法一次要執行多條 sql 語句,這種方式就無法實現功能了。

請看下面的示例: 我們在業務層中多加入一個方法。

業務層接口

/**
* 轉賬
* @param sourceName * @param targetName * @param money
*/
void transfer(String sourceName,String targetName,Float money);

業務層實現類:

@Override
public void transfer(String sourceName, String targetName, Float money) { 
//根據名稱查詢兩個賬戶信息
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName); 
//轉出賬戶減錢,轉入賬戶加錢
source.setMoney(source.getMoney()-money); 
target.setMoney(target.getMoney()+money); 
//更新兩個賬戶
accountDao.update(source);
int i=1/0; //模擬轉賬異常
accountDao.update(target); }

當我們執行時,由於執行有異常,轉賬失敗。但是因爲我們是每次執行持久層方法都是獨立事務,導致無法實 現事務控制(不符合事務的一致性 )

1.2.2 問題的解決

解決辦法:

讓業務層來控制事務的提交和回滾。(這個我們之前已經在 web 階段講過了)

改造後的業務層實現 類:

注:此處沒有使用 spring 的 IoC.

/**
* 賬戶的業務層實現類
* @author 黑馬程序員
* @Company http://www.ithiema.com * @Version 1.0
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
@Override
public void saveAccount(Account account) {
try { 
TransactionManager.beginTransaction(); 
accountDao.save(account); 
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace(); }finally {
TransactionManager.release();
} }
@Override
public void updateAccount(Account account) {
try { TransactionManager.beginTransaction(); 
accountDao.update(account); 
TransactionManager.commit();
} catch (Exception e) { 
TransactionManager.rollback(); 
e.printStackTrace();
}finally { 
TransactionManager.release();
} }
@Override
public void deleteAccount(Integer accountId) { 
try {
TransactionManager.beginTransaction();
accountDao.delete(accountId); TransactionManager.commit();
} catch (Exception e) { 
TransactionManager.rollback(); e.printStackTrace();
}finally {
TransactionManager.release(); }
}
@Override
public Account findAccountById(Integer accountId) { 
Account account = null;
try {
TransactionManager.beginTransaction(); 
account = accountDao.findById(accountId); 
TransactionManager.commit();
return account;
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace(); }finally {
TransactionManager.release();
}
return null; }
@Override
public List<Account> findAllAccount() { 
List<Account> accounts = null;
try {
TransactionManager.beginTransaction();
accounts = accountDao.findAll(); TransactionManager.commit(); 
return accounts;
} catch (Exception e) { TransactionManager.rollback();
e.printStackTrace(); }finally {
TransactionManager.release(); 
}
return null; 
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
try {
TransactionManager.beginTransaction();
Account source = accountDao.findByName(sourceName); 
Account target = accountDao.findByName(targetName);
source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); 
accountDao.update(source);
int i=1/0; accountDao.update(target); 
TransactionManager.commit();
} catch (Exception e) { 
TransactionManager.rollback();
e.printStackTrace(); 
}finally {
TransactionManager.release(); }
}
}

TransactionManager 類的代碼:

/**
* 事務控制類
* @author 黑馬程序員
* @Company http://www.ithiema.com * @Version 1.0
*/
public class TransactionManager {
//定義一個 DBAssit
private static DBAssit dbAssit = new DBAssit(C3P0Utils.getDataSource(),true);
//開啓事務
public static void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false); 
} catch (SQLException e) {
e.printStackTrace(); }
}
//提交事務
public static void commit() { 
try {
dbAssit.getCurrentConnection().commit(); } 
catch (SQLException e) {
e.printStackTrace(); 
}
}
//回滾事務
public static void rollback() { 
try {
dbAssit.getCurrentConnection().rollback(); 
} catch (SQLException e) {e.printStackTrace();
} }
//釋放資源
public static void release() {
try { dbAssit.releaseConnection();
} catch (Exception e) { 

e.printStackTrace();
} }
}

1.2.3 新的問題

上一小節的代碼,通過對業務層改造,已經可以實現事務控制了,但是由於我們添加了事務控制,也產生了一 個新的問題:

業務層方法變得臃腫了,裏面充斥着很多重複代碼。並且業務層方法和事務控制方法耦合了。

試想一下,如果我們此時提交,回滾,釋放資源中任何一個方法名變更,都需要修改業務層的代碼,況且這還 只是一個業務層實現類,而實際的項目中這種業務層實現類可能有十幾個甚至幾十個。

思考:

這個問題能不能解決呢? 答案是肯定的,使用下一小節中提到的技術。

1.2.4 動態代理回顧

1.2.4.1 動態代理的特點

字節碼隨用隨創建,隨用隨加載。

它與靜態代理的區別也在於此。因爲靜態代理是字節碼一上來就創建好,並完成加載。 裝飾者模式就是靜態代理的一種體現。

1.2.4.2 動態代理常用的有兩種方式

基於接口的動態代理

提供者:JDK 官方的 Proxy 類。

要求:被代理類最少實現一個接口。

基於子類的動態代理

提供者:第三方的 CGLib,如果報 asmxxxx 異常,需要導入 asm.jar。 要求:被代理類不能用 final 修飾的類(最終類)。

1.2.4.3 使用 JDK 官方的 Proxy 類創建代理對象

此處我們使用的是一個演員的例子:

在很久以前,演員和劇組都是直接見面聯繫的。沒有中間人環節。

而隨着時間的推移,產生了一個新興職業:經紀人(中間人),這個時候劇組再想找演員就需要通過經紀 人來找了。下面我們就用代碼演示出來。

/**
* 一個經紀公司的要求:
* 能做基本的表演和危險的表演 */
public interface IActor { /**
* 基本演出
* @param money */
public void basicAct(float money);
/**
* 危險演出
* @param money */
public void dangerAct(float money);
}
/**
* 一個演員
*/
//實現了接口,就表示具有接口中的方法實現。即:符合經紀公司的要求 public class Actor implements IActor{
public void basicAct(float money){
System.out.println("拿到錢,開始基本的表演:"+money); }
public void dangerAct(float money){ System.out.println("拿到錢,開始危險的表演:"+money);
} }
public class Client {
public static void main(String[] args) { //一個劇組找演員:
final Actor actor = new Actor();//直接
/**
* 代理:
* 間接。
* 獲取代理對象:
* 要求:
* 被代理類最少實現一個接口
* 創建的方式
* Proxy.newProxyInstance(三個參數)
* * * * *
參數含義: ClassLoader:和被代理對象使用相同的類加載器。
Interfaces:和被代理對象具有相同的行爲。實現相同的接口。 InvocationHandler:如何代理。
策略模式:使用場景是:
* 數據有了,目的明確。
* 如何達成目標,就是策略。 *
*/
IActor proxyActor = (IActor) Proxy.newProxyInstance( actor.getClass().getClassLoader(),
/**
actor.getClass().getInterfaces(), new InvocationHandler() {
* 執行被代理對象的任何方法,都會經過該方法。 * 此方法有攔截的功能。
*
* 參數:
* proxy:代理對象的引用。不一定每次都用得到 * method:當前執行的方法對象
* args:執行方法所需的參數
* 返回值:
* 當前執行方法的返回值
throws Throwable {
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
String name = method.getName(); Float money = (Float) args[0]; Object rtValue = null;
//每個經紀公司對不同演出收費不一樣,此處開始判斷 if("basicAct".equals(name)){
//基本演出,沒有 2000 不演 if(money > 2000){
//看上去劇組是給了 8000,實際到演員手裏只有 4000
//這就是我們沒有修改原來 basicAct 方法源碼,對方法進行了增強
rtValue = method.invoke(actor, money/2); }
} if("dangerAct".equals(name)){
//危險演出,沒有 5000 不演 if(money > 5000){
//看上去劇組是給了 50000,實際到演員手裏只有 25000 //這就是我們沒有修改原來 dangerAct 方法源碼,對方法進行了增強
rtValue = method.invoke(actor, money/2); }
}
    return rtValue;
}
});
//沒有經紀公司的時候,直接找演員。
// actor.basicAct(1000f);
// actor.dangerAct(5000f);
//劇組無法直接聯繫演員,而是由經紀公司找的演員 proxyActor.basicAct(8000f); proxyActor.dangerAct(50000f);
}
}

1.2.4.4 使用 CGLib 的 Enhancer 類創建代理對象

還是那個演員的例子,只不過不讓他實現接口。

/**
 * 一個演員 
 */
public class Actor{
//沒有實現任何接口
public void basicAct(float money){ 
System.out.println("拿到錢,開始基本的表演:"+money);
}
public void dangerAct(float money){ 
System.out.println("拿到錢,開始危險的表演:"+money);
} 
}
public class Client { /**
* * * * *
基於子類的動態代理
 要求:
   被代理對象不能是最終類
 用到的類:
Enhancer
* 用到的方法:
* create(Class, Callback) * 方法的參數:
* Class:被代理對象的字節碼
* Callback:如何代理 * @param args
*/
public static void main(String[] args) { final Actor actor = new Actor();
Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() {
/**
方法進行增強。
* 執行被代理對象的任何方法,都會經過該方法。在此方法內部就可以對被代理對象的任何
*
* 參數:
* 前三個和基於接口的動態代理是一樣的。
* MethodProxy:當前執行方法的代理對象。 * 返回值:
* 當前執行方法的返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String name = method.getName(); Float money = (Float) args[0];
Object rtValue = null; if("basicAct".equals(name)){
//基本演出 if(money > 2000){
rtValue = method.invoke(actor, money/2); }
}
if("dangerAct".equals(name)){ //危險演出
if(money > 5000){
rtValue = method.invoke(actor, money/2);
}
}
        return rtValue;
    }
}); cglibActor.basicAct(10000);
cglibActor.dangerAct(100000); }
}

思考:

這個故事(示例)講完之後,我們從中受到什麼啓發呢?它到底能應用在哪呢?

1.2.5 解決案例中的問題

/**
* 用於創建客戶業務層對象工廠(當然也可以創建其他業務層對象,只不過我們此處不做那麼繁瑣)
* @author 黑馬程序員
* @Company http://www.ithiema.com * @Version 1.0
*/
public class BeanFactory {
/**
* 創建賬戶業務層實現類的代理對象 * @return
*/
public static IAccountService getAccountService() {
//1.定義被代理對象
final IAccountService accountService = new AccountServiceImpl();
//2.創建代理對象
IAccountService proxyAccountService = (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
InvocationHandler() {
accountService.getClass().getInterfaces(),new
/**
* 執行被代理對象的任何方法,都會經過該方法。 * 此處添加事務控制
*/
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
Object rtValue = null; try {
//開啓事務 TransactionManager.beginTransaction();
//執行業務層方法
rtValue = method.invoke(accountService, args); //提交事務
TransactionManager.commit();
}catch(Exception e) {
//回滾事務 TransactionManager.rollback(); e.printStackTrace();
}finally {
//釋放資源
TransactionManager.release(); }
return rtValue; }
} }

當我們改造完成之後 ,業務層用於控制事務的重複代碼就都可以 刪掉了。

第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 目錄

2.2.1.3 第三步:創建 spring 的配置文件並導入約束

此處要導入 aop 的約束

<?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
</beans>
http://www.springframework.org/schema/aop/spring-aop.xsd">

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 第五步:抽取公共代碼製作成通知

/**
* 事務控制類
* @author 黑馬程序員
* @Company http://www.ithiema.com * @Version 1.0
*/
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 切入點表達式說明

xecution:匹配方法的執行(常用) 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 第三步:把資源使用註解配置

/**
* 賬戶的業務層實現類
* @author 黑馬程序員
* @Company http://www.ithiema.com
* @Version 1.0
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
}
/**
* 賬戶的持久層實現類 * @author 黑馬程序員
* @Company http://www.ithiema.com * @Version 1.0
*/
@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 第一步:把通知類也使用註解配置

**
* 事務控制類
* @author 黑馬程序員
* @Company http://www.ithiema.com * @Version 1.0
*/
@Component("txManager")
public class TransactionManager {
//定義一個 DBAssit @Autowired
private DBAssit dbAssit ; }

2.3.2.2 第二步:在通知類上使用@Aspect 註解聲明爲切面

作用:

把當前類聲明爲切面類。

/**
* 事務控制類
* @author 黑馬程序員
* @Company http://www.ithiema.com
* @Version 1.0
*/
@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 {
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章