spring 框架學習安排
目錄
1. 第三天
1. 完善我們的account基於xml開發的IOC案例
-
準備案例
- dao層及實現類
/** * 賬戶的持久層接口 */ public interface IAccountDao { /** * 查詢所有 * @return */ List<Account> findAllAccount(); /** * 查詢一個 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); }
/** * 賬戶的持久層實現類 */ public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } public List<Account> findAllAccount() { try{ return runner.query("select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } public Account findAccountById(Integer accountId) { try{ return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } public void saveAccount(Account account) { try{ runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } public void updateAccount(Account account) { try{ runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } }
- service層及實現類
/** * 賬戶的業務層接口 */ public interface IAccountService { /** * 查詢所有 * @return */ List<Account> findAllAccount(); /** * 查詢一個 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); }
/** * 賬戶的業務層實現類 */ @Service("accountService") public class AccountServiceImpl implements IAccountService{ @Autowired private IAccountDao accountDao; public List<Account> findAllAccount() { return accountDao.findAllAccount(); } public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } public void saveAccount(Account account) { accountDao.saveAccount(account); } public void updateAccount(Account account) { accountDao.updateAccount(account); } public void deleteAccount(Integer acccountId) { accountDao.deleteAccount(acccountId); } }
- 實體類
/** * 賬戶的實體類 */ public class Account implements Serializable { private Integer id; private String name; private Float money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getMoney() { return money; } public void setMoney(Float money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
- bean.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置Service --> <bean id="accountService" class="com.liuzeyu.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao對象--> <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入數據源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </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://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="809080"></property> </bean> </beans>
- 測試類
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:bean.xml") public class AnnotationTest { @Autowired private IAccountService service; }
-
爲service添加轉賬方法,製造異常
public void transfer(String sourceName, String targetName, Float money) { //2.執行操作 //2.1根據sourceName獲取轉出賬戶 Account sourceAccount = accountDao.findAccountByName(sourceName); //2.2根據targetName獲取轉入賬戶 Account targetAccount = accountDao.findAccountByName(targetName); //2.3轉出賬戶數據更新(減錢) Float smoney = sourceAccount.getMoney(); smoney -=money; sourceAccount.setMoney(smoney); accountDao.updateAccount(sourceAccount); //2.4轉入賬戶數據更新(加錢) Float tmoney = targetAccount.getMoney(); tmoney +=money; targetAccount.setMoney(tmoney); ----> int i = 1/0; accountDao.updateAccount(targetAccount); }
-
測試函數
@Test public void testTransfer(){ service.transfer("aaa","bbb",100f); }
-
發現問題:aaa的錢可以減少,但是bbb的錢不可以增加,這是不被允許
-
問題分析,如下圖:
在轉賬方法中,每一次連接數據庫都獲取新的Connection對象,從數據庫連接池中獲取了4次連接,前三次的每一次事務都正常提交,知道遇到除0異常,第四個無法提交。如何解決這一問題呢? -
解決方案
分析:上述四次與數據庫建立連接應當由同一個Connection對象完成,要成功都成功,要失敗全都要失敗。並且這個Connection與當前線程進行綁定,從而使一個線程中只有一個控制事務的Connection對象。 -
實現
- 準備連接和線程綁定的工具類
/** * Created by liuzeyu on 2020/4/22. * * 工具類:作用是用於從數據庫連接池獲取一個連接並和當前線程綁定 */ public class ConnectionUtils { private ThreadLocal<Connection> t1 = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 獲取當前線程上的連接 */ public Connection getLocalThreadConnection(){ try{ //1.先從ThreadLocal上獲取 Connection conn = t1.get(); //2.判斷當前線程是否有連接 if( conn == null){ //3.從數據源獲取一個連接,並存入ThreadLocal中 conn = dataSource.getConnection(); t1.set(conn); } //4.返回當前連接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 處理連接已經返回到連接池中了,但是線程還是和連接綁定在一起 */ public void removeConnection(){ t1.remove(); //線程和連接綁定 } }
- 準備事務管理工具類
/** * Created by liuzeyu on 2020/4/22. 和事務管理相關的工具類,它包含了事務提交,事務開啓,事務回滾 */ public class TransactionManger { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } //開啓事務 public void beginTransaction(){ try { connectionUtils.getLocalThreadConnection().setAutoCommit(false); //返回連接池中 } catch (SQLException e) { e.printStackTrace(); } } //提交事務 public void commitTransaction(){ try { connectionUtils.getLocalThreadConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } //回滾事務 public void rollbackTransaction(){ try { connectionUtils.getLocalThreadConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } //釋放連接 public void release(){ try { connectionUtils.getLocalThreadConnection().close(); connectionUtils.removeConnection(); } catch (SQLException e) { e.printStackTrace(); } } }
- 修改Connection獲取方式,修改爲從的ThreadLocal中獲取
/** * 賬戶的持久層實現類 */ public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } public void setRunner(QueryRunner runner) { this.runner = runner; } public List<Account> findAllAccount() { try{ return runner.query(connectionUtils.getLocalThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } public Account findAccountById(Integer accountId) { try{ return runner.query(connectionUtils.getLocalThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } public void saveAccount(Account account) { try{ runner.update(connectionUtils.getLocalThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } public void updateAccount(Account account) { try{ runner.update(connectionUtils.getLocalThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } }
- 修改bean.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置Service --> <bean id="accountService" class="com.liuzeyu.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> <!--注入事務管理器--> <property name="txManger" ref="txManger"></property> </bean> <!--配置Dao對象--> <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!--注入connectionUtils--> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--<!–注入數據源–>--> <!--連接不從連接池中獲取--> <!--<constructor-arg name="ds" ref="dataSource"></constructor-arg>--> </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://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="809080"></property> </bean> <!--配置Connection工具類 ConnectionUtils--> <bean id="connectionUtils" class="com.liuzeyu.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事務--> <bean id="txManger" class="com.liuzeyu.utils.TransactionManger"> <!--注入connectionUtils--> <property name="connectionUtils" ref="connectionUtils"></property> </bean> </beans>
- 修改service實現類
public void transfer(String sourceName, String targetName, Float money) { try { //1.開啓事務 txManger.beginTransaction(); //2.執行操作 //2.1根據sourceName獲取轉出賬戶 Account sourceAccount = accountDao.findAccountByName(sourceName); //2.2根據targetName獲取轉入賬戶 Account targetAccount = accountDao.findAccountByName(targetName); //2.3轉出賬戶數據更新(減錢) Float smoney = sourceAccount.getMoney(); smoney -=money; sourceAccount.setMoney(smoney); accountDao.updateAccount(sourceAccount); //2.4轉入賬戶數據更新(加錢) Float tmoney = targetAccount.getMoney(); tmoney +=money; targetAccount.setMoney(tmoney); int i = 1/0; accountDao.updateAccount(targetAccount); //3.提交事務 txManger.commitTransaction(); }catch (Exception e){ //4.回滾操作 txManger.rollbackTransaction(); e.printStackTrace(); }finally { //5.釋放資源 txManger.release(); } }
- 測試:可以解決上述問題!
2. 分析上述案例中存在的問題
問題:方法與方法之間的依賴,如事務控制器與service實現類之間產生了依賴關係
修改事務控制器方法名
service實現類不能使用
爲什麼會有這一問題的出現呢,原因很簡單就是因爲service實現類中使用了事務管理器這一工具類,如何做到不使用事務管理器又可以做到事務的控制呢?
3. 動態代理的回顧
4. 解決案例中的問題
思路分析:
由IAccountService的代理對象來實現類中的方法,並且代理對象可以實現了事務的控制。
-
創建沒有事務控制的service實現類
/** * 賬戶的業務層實現類 */ public class AccountServiceImpl_OLD implements IAccountService { private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } private TransactionManger txManger; public void setTxManger(TransactionManger txManger) { this.txManger = txManger; } public List<Account> findAllAccount() { return accountDao.findAllAccount(); } public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } public void saveAccount(Account account) { accountDao.saveAccount(account); } public void updateAccount(Account account) { accountDao.updateAccount(account); } public void deleteAccount(Integer acccountId) { accountDao.deleteAccount(acccountId); } public void transfer(String sourceName, String targetName, Float money) { //2.1根據sourceName獲取轉出賬戶 Account sourceAccount = accountDao.findAccountByName(sourceName); //2.2根據targetName獲取轉入賬戶 Account targetAccount = accountDao.findAccountByName(targetName); //2.3轉出賬戶數據更新(減錢) Float smoney = sourceAccount.getMoney(); smoney -= money; sourceAccount.setMoney(smoney); accountDao.updateAccount(sourceAccount); //2.4轉入賬戶數據更新(加錢) Float tmoney = targetAccount.getMoney(); tmoney += money; targetAccount.setMoney(tmoney); //int i = 1 / 0; accountDao.updateAccount(targetAccount); } }
-
創建代理類,實現的功能有事務的控制,service層代碼的實現
/** * Created by liuzeyu on 2020/4/23. * 創建代理service工廠 * 增強方法:添加事務管理 */ public class BeanFactory { private IAccountService accountService; private TransactionManger txManger; public final void setTxManger(TransactionManger txManger) { this.txManger = txManger; } public void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 獲取service的代理對象 * @return */ public IAccountService getAccountService() { IAccountService iac = (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object rstValue = null; try { //1.開啓事務 txManger.beginTransaction(); //2.執行操作 rstValue = method.invoke(accountService, args); //3.提交事務 txManger.commitTransaction(); //4.返回結果 return rstValue; } catch (Exception e) { //5.回滾操作 txManger.rollbackTransaction(); throw new RuntimeException(e); } finally { //6.釋放資源 txManger.release(); } } }); return iac; } }
-
修改bean.xml文件,使用的是沒有事務控制器的service實現類
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <!-- 配置Service --> <!--<bean id="accountService" class="com.liuzeyu.service.impl.AccountServiceImpl">--> <!--<!– 注入dao –>--> <!--<property name="accountDao" ref="accountDao"></property>--> <!--<!–注入事務管理器–>--> <!--<property name="txManger" ref="txManger"></property>--> <!--</bean>--> <!-- 配置代理Service --> <bean id="accountSerivce_OLD" class="com.liuzeyu.service.impl.AccountServiceImpl_OLD"> <property name="accountDao" ref="accountDao"></property> </bean> <bean id="beanFactory" class="com.liuzeyu.factory.BeanFactory"> <property name="accountService" ref="accountSerivce_OLD"></property> <property name="txManger" ref="txManger"></property> </bean> <bean id="proxy_accpuntService" factory-bean="beanFactory" factory-method="getAccountService"> </bean> <!--配置Dao對象--> <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!--注入connectionUtils--> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--<!–注入數據源–>--> <!--連接不從連接池中獲取--> <!--<constructor-arg name="ds" ref="dataSource"></constructor-arg>--> </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://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="809080"></property> </bean> <!--配置Connection工具類 ConnectionUtils--> <bean id="connectionUtils" class="com.liuzeyu.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事務--> <bean id="txManger" class="com.liuzeyu.utils.TransactionManger"> <!--注入connectionUtils--> <property name="connectionUtils" ref="connectionUtils"></property> </bean> </beans>
重點難點在這些配置文件的修改
-
爲測試類添加新注入的service
小結:
最後:經測試可以實現相同的效果,但是解決了我們剛剛存在的問題嗎?
肯定是解決了,因爲如果我們在實現類中多處用到了事務控制,那麼修改事務控制器的方法被修改了,實現類就要修改多次。
但是如果使用了動態代理,那麼只需要在invoke函數中修改一次即可,提高可維護性。因爲業務層的每次函數執行都會經過invoke函數,所以會經常在裏面編寫一下可重用性的代碼,提高開發效率。
5. AOP的概念
- 什麼是AOP?
AOP全稱Aspect Oriented Programming,即面向切面編程。通過預編譯和運行器動態代理機制實現程序功能的統一維護技術。AOP是OOP的延續,是軟件開發的重點和熱點,也是spring框架的一個重要的內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯各個部分進行隔離,從而使得業務邏輯各個部分之間的耦合度降低,提高了程序的可重用行和開發效率。 - AOP的作用及優勢
作用
在程序期間,不修改源碼,對已有方法進行增強
優勢:
1. 減少重複代碼
2. 提高開發效率
3. 提高可維護性 - AOP的實現方式
動態代理技術
6. Spring中的AOP【掌握】
1. AOP相關術語(基於項目day03_spring_account)
-
Joinpoint(連接點):所謂連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只支持方法類型的連接點。例如項目中IAccountService接口中所有的方法。
-
Pointcut(切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。例如如果在項目IAccountService接口中添加方法test,然後再再將其排除再增強方法之外,就是不增加事務的增強處理,此時的test方法就不是切入點(如下圖),但它還是連接點。其餘的IAccountService接口方法都是切入點。
-
Advice(通知/增強):
所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。
通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知。
例如案例中的事務管理器就是一個通知,它起到的作用有提供了公共代碼的部分
-
Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期爲類動態地添加一些方
法或 Field。 -
Target(目標對象):代理的目標對象,就是被代理對象。案例中的accountService
-
weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程。spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。簡單的說加入事務控制的過程叫做織入。如案例中事務管理的加入過程。
-
Proxy(代理):一個類被 AOP 織入增強後,就產生一個結果代理類。其實就是代理對象,如案例中的,Proxy.newProxyInstance的返回值。
-
Aspect(切面):是切入點和通知(引介)的結合。
2. 學習spring中AOP要明確的事
- 開發階段(我們做的)
- 編寫核心業務代碼(開發主線):大部分由程序員來做,要求熟悉業務需求。
- 把公共代碼抽取出來,製作成通知,是開發階段最後才做,AOP編程人員來做。
- 在配置文件中,聲明切入點和通知點之間的關係,即切面,AOP編程人員來做。
- 運行階段(spring框架做的)
spring框架監控切入點方法的執行,一旦監控到切入點方法被執行了,使用代理機制,動態創建目標對象的代理對象。根據通知類別,在代理對象的相應位置,將通知對象的功能織入,完成完整的代理邏輯運行。
3. 關於代理的選擇
在spring中,框架會根據目標類是否實現了接口來決定採用哪一種的動態代理方式。
4. 基於xml的AOP配置
- 編寫service層代碼
/**
* Created by liuzeyu on 2020/4/24.
* 模擬業務層接口
*/
public interface IAccountService {
//模擬保存方法
public void save();
//模擬更新賬戶
public void updateAccount(int i);
//模擬刪除賬戶
public int deleteAccount();
}
package com.liuzeyu.service.impl;
import com.liuzeyu.service.IAccountService;
/**
* Created by liuzeyu on 2020/4/24.
*/
public class IAccountServiceImpl implements IAccountService {
public void save() {
System.out.println("執行了保存...");
}
public void updateAccount(int i) {
System.out.println("執行了更新...");
}
public int deleteAccount() {
System.out.println("執行了刪除...");
return 0;
}
}
- 編寫utils代碼
/**
* Created by liuzeyu on 2020/4/24.
* 用於記錄日誌的工具類
*/
public class Logger {
/**
* 用於打印日誌:
* 計劃讓其在切入點方法執行之前(切入點方法就是業務層方法)
*/
public void printLog(){
System.out.println("Logger類的printLog()開始打印日誌....");
}
}
- 準備bean.xml
- 把service和通知Bean都交給spring容器管理
- 使用aop:config標籤表示AOP的開始配置
- 使用aop:aspect表示開始配置切面
屬性:
id:給切面提供一個唯一標識。
ref:是指定通知類的bean的id - aop:aspect標籤內部使用對應標籤來配置通知的類型
我們示例讓printLog在切入點方法執行之前被執行,所以是前置通知
aop:before:表示配置前置通知
method屬性:用於執行Logger類中哪個方法是前置通知
pointcut屬性:用於指定切入點表達式,該表達式的含義是對業務層中哪些方法的增強
切入點表達式的寫法:
<?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">
<!--將service和logegr交給spring的IOC容器管理-->
<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl"></bean>
<bean id="logegr" class="com.liuzeyu.utlis.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logegr">
<aop:before method="printLog" pointcut="execution(public void
com.liuzeyu.service.impl.IAccountServiceImpl.save())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
其中切入點表達式的寫法
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logegr">
<!--配置通知類型,並且建立通知方法和切入點相關聯-->
<!--<aop:before method="printLog" pointcut="execution(public void-->
<!--com.liuzeyu.service.impl.IAccountServiceImpl.save())"></aop:before>-->
<!--去除訪問修飾符-->
<!--<aop:before method="printLog" pointcut="execution( void-->
<!--com.liuzeyu.service.impl.IAccountServiceImpl.save())"></aop:before>-->
<!--可以將返回值改爲任意-->
<!--<aop:before method="printLog" pointcut="execution( *-->
<!--com.liuzeyu.service.impl.IAccountServiceImpl.save())"></aop:before>-->
<!--包名可以寫* 表示任意-->
<!--<aop:before method="printLog" pointcut="execution( *-->
<!--*.*.*.*.*.save())"></aop:before>-->
<!--包名可以寫* 表示任意(多層包改進)-->
<!--<aop:before method="printLog" pointcut="execution( *-->
<!--*..*.save())"></aop:before>-->
<!--全通配寫法-->
<!--<aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>-->
<!--方法參數(..)表示可以0或多個任意類型的參數-->
<!--<aop:before method="printLog" pointcut="execution( *-->
<!--*..*.*(..))"></aop:before>-->
<!--方法參數(int)表示只可以是int類型的參數-->
<!--<aop:before method="printLog" pointcut="execution( *-->
<!--*..*.*(int))"></aop:before>-->
<!--方法參數(引用類型)引用類型寫包名+類型的全稱-->
<aop:before method="printLog" pointcut="execution( *
*..*.*(java.lang.String))"></aop:before>
</aop:aspect>
</aop:config>
- 測試方法
package com.liuzeyu;
import com.liuzeyu.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by liuzeyu on 2020/4/24.
*/
public class AopTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService =(IAccountService)ac.getBean("accountService");
accountService.save();
}
}
5. 四種常用的通知類型
- 在上述的案例中,爲通知類添加通知方法
/**
* Created by liuzeyu on 2020/4/24.
* 用於記錄日誌的工具類
*/
public class Logger {
/**
* 用於打印日誌:
* 計劃讓其在切入點方法執行之前(切入點方法就是業務層方法)
*/
public void beforePrintLog(){
System.out.println("前置通知...Logger類的beforePrintLog開始打印日誌....");
}
/**
* 用於打印日誌:
* 計劃讓其在切入點方法執行後(切入點方法就是業務層方法)
*/
public void afterReturingPrintLog(){
System.out.println("後置通知...Logger類的AfterReturingPrintLog()開始打印日誌....");
}
/**
* 用於打印日誌:
* 計劃讓其在切入點方法執行出現異常(切入點方法就是業務層方法)
*/
public void afterThrowingPrintLog(){
System.out.println("異常通知...Logger類的afterThrowingPrintLog()開始打印日誌....");
}
/**
* 用於打印日誌:
* 計劃讓其在切入點方法最終之前(切入點方法就是業務層方法)
*/
public void afterPrintLog(){
System.out.println("最終通知...Logger類的AfterPrintLog()開始打印日誌....");
}
}
- 配置bean.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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--將service和logegr交給spring的IOC容器管理-->
<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl"></bean>
<bean id="logegr" class="com.liuzeyu.utlis.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logegr">
<!--配置前置通知類型,並且建立通知方法和切入點相關聯-->
<aop:before method="beforePrintLog"
pointcut="execution(* *..*.*(..))"
></aop:before>
<!--配置後通知類型,並且建立通知方法和切入點相關聯-->
<aop:after-returning method="afterReturingPrintLog"
pointcut="execution(* *..*.*(..))"
></aop:after-returning>
<!--配置異常通知類型,並且建立通知方法和切入點相關聯-->
<aop:after-throwing method="afterThrowingPrintLog"
pointcut="execution(* *..*.*(..))"
></aop:after-throwing>
<!--配置最終通知類型,並且建立通知方法和切入點相關聯-->
<aop:after method="afterPrintLog"
pointcut="execution(* *..*.*(..))"
></aop:after>
</aop:aspect>
</aop:config>
</beans>
- 測試函數
public class AopTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService =(IAccountService)ac.getBean("accountService");
accountService.save();
}
}
- 執行結果
爲save方法制造異常
測試結果
可見後置通知和異常通知只能其中的一個被執行
發現問題:bean.xml 切入點表達式每處都在寫,而且寫的一樣,出現冗餘,可以對其改造
做到一處寫好,處處引用。但是寫在標籤<aop:aspect 內部只能這個切面引用,如果想讓其它切面引用。也可以寫在外部,當必須寫在<aop:aspect上面,如:
6.環繞通知
- 配置bean.xml
<!--配置環繞通知-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
- 添加通知方法
/**
* 用於打印日誌:
* 環繞通知
*/
public void aroundPrintLog(){
System.out.println("環繞通知...Logger類的aroundPrintLog開始打印日誌....");
}
- 測試方法
/**
* Created by liuzeyu on 2020/4/24.
*/
public class AopTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService =(IAccountService)ac.getBean("accountService");
accountService.save();
}
}
問題:當我們配置完環繞通知後,環繞通知的方法被執行了,但是切入點的方法卻沒有被執行。
分析:
發現環繞通知中缺少明確的切入點方法調用。
所以可以在環繞通知中添加切入點方法的執行,如下
解決:spring框架中爲我們提供了一個接口ProceedingJoinPoint。該接口有一個proceed()方法,調用此方法就是明確調用切入點方法。
該接口可以作爲環繞通知的方法參數,在程序執行時,spring框架會爲我們提供接口的實現類供我們使用。
上述的異常必須由Throwable來抓,否則抓不住的。
重新測試結果:
7. 基於註解的AOP配置
- 將bean.xml的IOC配置拿掉,使用註解配置
替換:
<!--掃描帶有註解的類加入IOC容器-->
<context:component-scan base-package="com.liuzeyu"></context:component-scan>
2. 將bean.xml的AOP配置拿掉,使用註解配置
替換爲
<!--配置AOP開啓註解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
/**
* Created by liuzeyu on 2020/4/24.
* 用於記錄日誌的工具類
*/
@Component("logegr")
@Aspect
public class Logger {
@Pointcut("execution(* com.liuzeyu.service.impl.*.*(..))")
private void pt1(){}
/**
* 用於打印日誌:
* 計劃讓其在切入點方法執行之前(切入點方法就是業務層方法)
*/
//@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知...Logger類的beforePrintLog開始打印日誌....");
}
/**
* 用於打印日誌:
* 計劃讓其在切入點方法執行後(切入點方法就是業務層方法)
*/
@AfterReturning("pt1()")
public void afterReturingPrintLog(){
System.out.println("後置通知...Logger類的AfterReturingPrintLog()開始打印日誌....");
}
/**
* 用於打印日誌:
* 計劃讓其在切入點方法執行出現異常(切入點方法就是業務層方法)
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("異常通知...Logger類的afterThrowingPrintLog()開始打印日誌....");
}
/**
* 用於打印日誌:
* 計劃讓其在切入點方法最終之前(切入點方法就是業務層方法)
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最終通知...Logger類的AfterPrintLog()開始打印日誌....");
}
/**
* 用於打印日誌:
* 環繞通知
*/
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rstValue = null;
try{
Object[] args = pjp.getArgs();
System.out.println("前置通知...Logger類的aroundPrintLog開始打印日誌....");
rstValue = pjp.proceed(args);
System.out.println("後置通知...Logger類的aroundPrintLog開始打印日誌....");
return rstValue;
}catch (Throwable t){
System.out.println("異常通知...Logger類的aroundPrintLog開始打印日誌....");
throw new RuntimeException(t);
}finally {
System.out.println("最終通知...Logger類的aroundPrintLog開始打印日誌....");
}
}
}
測試前置,後置,最終,異常通知的時候發現並沒有按照實際順序執行:
但是測試環繞通知時,有按照實際順序執行,所以當出現有嚴格的執行順序時,我們一般選擇環繞通知
最後如若需要純註解配置,只需要添加配置類:
/**
* Created by liuzeyu on 2020/4/25.
*/
@Configuration
@ComponentScan(basePackages = "com.liuzeyu")
@EnableAspectJAutoProxy
public class SpringConfig {
}
並且在創建容器時執行配置類的字節碼文件
public class AopTest {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
IAccountService accountService =(IAccountService)ac.getBean("accountService");
accountService.save();
}
}