轉賬案例
座標:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
建立實體類daomain
/**
* 賬戶的實體類
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
}
建立介面AccountDao.java
/**
* 賬戶的持久層介面
*/
public interface AccountDao {
/**
* 查詢所有
* @return
*/
List<Account> findAllAccount();
/**
* 查詢一個
* @return
*/
Account findAccountById(Integer accountId);
/**
* 儲存
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 刪除
* @param acccountId
*/
void deleteAccount(Integer acccountId);
/**
* 根據名稱查詢賬戶
* @param accountName
* @return 如果有唯一的一個結果就返回,如果沒有結果就返回null
* 如果結果集超過一個就拋異常
*/
Account findAccountByName(String accountName);
}
建立實現類AccountDaoImpl.java
/**
* 賬戶的持久層實現類
*/
public class AccountDaoImpl implements AccountDao {
private QueryRunner 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);
}
}
public void deleteAccount(Integer accountId) {
try{
runner.update("delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query("select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("結果集不唯一,資料有問題");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
建介面AccountService.java
/**
* 賬戶的業務層介面
*/
public interface AccountService {
/**
* 查詢所有
* @return
*/
List<Account> findAllAccount();
/**
* 查詢一個
* @return
*/
Account findAccountById(Integer accountId);
/**
* 儲存
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 刪除
* @param acccountId
*/
void deleteAccount(Integer acccountId);
/**
* 轉賬
* @param sourceName 轉出賬戶名稱
* @param targetName 轉入賬戶名稱
* @param money 轉賬金額
*/
void transfer(String sourceName, String targetName, Float money);
}
建立介面的實現類,AccountServiceImpl.java
/**
* 賬戶的業務層實現類
*
* 事務控制應該都是在業務層
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = 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 void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根據名稱查詢轉出賬戶
Account source = accountDao.findAccountByName(sourceName);
//2.2根據名稱查詢轉入賬戶
Account target = accountDao.findAccountByName(targetName);
//2.3轉出賬戶減錢
source.setMoney(source.getMoney()-money);
//2.4轉入賬戶加錢
target.setMoney(target.getMoney()+money);
//2.5更新轉出賬戶
accountDao.updateAccount(source);
int i=1/0;
//2.6更新轉入賬戶
accountDao.updateAccount(target);
}
}
配置applicationContext.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.it.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao物件-->
<bean id="accountDao" class="com.it.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/itcastspring"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
測試AccountServiceTest.java
/**
* 使用Junit單元測試:測試我們的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
事務被自動控制了。換言之,我們使用了connection物件的setAutoCommit(true)
新增事務
如果在AccountServiceImpl.java中的transfer方法中,丟擲一個異常。此時事務不會回滾,原因是DBUtils每個運算元據都是獲取一個連線,每個連線的事務都是獨立的,且預設是自動提交。
解決方案:
需要使用ThreadLocal物件把Connection和當前執行緒繫結,從而使一個執行緒中只能有一個能控制事務的連線物件。
ConnectionUtils.java
/**
* 連線的工具類,它用於從資料來源中獲取一個連線,並且實現和執行緒的繫結
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
//注入資料來源
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 獲取當前執行緒上的連線,
* @return
*/
public Connection getThreadConnection() {
try{
//1.先從ThreadLocal上獲取
Connection conn = tl.get();
//2.判斷當前執行緒上是否有連線
if (conn == null) {
//3.從資料來源中獲取一個連線,並且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回當前執行緒上的連線
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把連線和執行緒解綁(在當前執行緒結束的時候執行)
*/
public void removeConnection(){
tl.remove();
}
}
TransactionManager.java
和事務管理相關的工具類,它包含了,開啟事務,提交事務,回滾事務和釋放連線
/**
* 和事務管理相關的工具類,它包含了,開啟事務,提交事務,回滾事務和釋放連線
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 開啟事務
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事務
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滾事務
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 釋放連線
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//把連線還回連線池中
connectionUtils.removeConnection();//執行緒和連線解綁
}catch (Exception e){
e.printStackTrace();
}
}
}
配置AccountDaoImpl.java
注入連線工具物件,使得運算元據庫從同一個連線中獲取
/**
* 賬戶的持久層實現類
*/
public class AccountDaoImpl implements AccountDao {
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.getThreadConnection(),"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.getThreadConnection(),"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.getThreadConnection(),"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.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("結果集不唯一,資料有問題");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
配置AccountServiceImpl.java
事務操作一定需要在Service層控制。
作用:注入事務管理器物件,對每個操作都需要開啟事務、提交事務、關閉事務,如果丟擲異常,需要回滾事務。
/**
* 賬戶的業務層實現類
*
* 事務控制應該都是在業務層
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public List<Account> findAllAccount() {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
List<Account> accounts = accountDao.findAllAccount();
//3.提交事務
txManager.commit();
//4.返回結果
return accounts;
}catch (Exception e){
//5.回滾操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
//6.釋放連線
txManager.release();
}
}
public Account findAccountById(Integer accountId) {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
Account account = accountDao.findAccountById(accountId);
//3.提交事務
txManager.commit();
//4.返回結果
return account;
}catch (Exception e){
//5.回滾操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
//6.釋放連線
txManager.release();
}
}
public void saveAccount(Account account) {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
accountDao.saveAccount(account);
//3.提交事務
txManager.commit();
}catch (Exception e){
//4.回滾操作
txManager.rollback();
}finally {
//5.釋放連線
txManager.release();
}
}
public void updateAccount(Account account) {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
accountDao.updateAccount(account);
//3.提交事務
txManager.commit();
}catch (Exception e){
//4.回滾操作
txManager.rollback();
}finally {
//5.釋放連線
txManager.release();
}
}
public void deleteAccount(Integer acccountId) {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
accountDao.deleteAccount(acccountId);
//3.提交事務
txManager.commit();
}catch (Exception e){
//4.回滾操作
txManager.rollback();
}finally {
//5.釋放連線
txManager.release();
}
}
public void transfer(String sourceName, String targetName, Float money) {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
//2.1根據名稱查詢轉出賬戶
Account source = accountDao.findAccountByName(sourceName);
//2.2根據名稱查詢轉入賬戶
Account target = accountDao.findAccountByName(targetName);
//2.3轉出賬戶減錢
source.setMoney(source.getMoney()-money);
//2.4轉入賬戶加錢
target.setMoney(target.getMoney()+money);
//2.5更新轉出賬戶
accountDao.updateAccount(source);
int i=1/0;
//2.6更新轉入賬戶
accountDao.updateAccount(target);
//3.提交事務
txManager.commit();
}catch (Exception e){
//4.回滾操作
txManager.rollback();
e.printStackTrace();
}finally {
//5.釋放連線
txManager.release();
}
}
}
配置applicationContext.xml
<!-- 配置Service -->
<bean id="accountService" class="com.it.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
<!--注入事務管理器-->
<property name="txManager" ref="txManager"></property>
</bean>
<!--配置Dao物件-->
<bean id="accountDao" class="com.it.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">
<!--這裡要去掉queryRunner的預設連線池配置,由ConnectionUtils 獲取連線-->
<!--<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/itcastspring"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置Connection的工具類 ConnectionUtils -->
<bean id="connectionUtils" class="com.it.utils.ConnectionUtils">
<!-- 注入資料來源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事務管理器-->
<bean id="txManager" class="com.it.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
通過對業務層改造,已經可以實現事務控制了,但是由於我們新增了事務控制,也產生了一個新的問題:
業務層方法變得臃腫了,裡面充斥著很多重複程式碼。並且業務層方法和事務控制方法耦合了。
試想一下,如果我們此時提交,回滾,釋放資源中任何一個方法名變更,都需要修改業務層的程式碼,況且這還只是一個業務層實現類,而實際的專案中這種業務層實現類可能有十幾個甚至幾十個。
【思考】:
這個問題能不能解決呢?
答案是肯定的,使用下一小節中提到的技術
AOP
AOP的概述
AOP (Aspect Oriented Programing) 稱為:面向切面程式設計,它是一種程式設計思想。
AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼的編寫方式(應用場景:例如效能監視、事務管理、安全檢查、快取、日誌記錄等)。
【擴充套件瞭解】AOP 是 OOP(物件導向程式設計(Object Oriented Programming,OOP,物件導向程式設計)是一種計算機程式設計架構),思想延續 !
AOP的作用
- 許可權校驗
- 日誌記錄
- 效能檢測
- 快取技術
- 事務管理
AOP底層實現
代理機制。
2個:spring的aop的底層原理
1:JDK代理(要求目標物件面向介面)(spring預設的代理方式是JDK代理)
2:CGLIB代理(面向介面、面向類)
AOP相關術語
Joinpoint(連線點): (方法)
所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點。
Pointcut(切入點): (方法)
所謂切入點是指我們要對哪些Joinpoint進行攔截的定義。
Advice(通知/增強): (方法)
所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。
通知的型別:前置通知,後置通知,異常通知,最終通知,環繞通知。Aspect(切面): (類)
是切入點和通知(引介)的結合。
- Target(目標物件): 代理的目標物件。
- Weaving(織入): (瞭解)是指把增強應用到目標物件來建立新的代理物件的過程。
spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。 - Proxy(代理): 一個類被AOP織入增強後,就產生一個結果代理類。
Spring的AOP配置(新增日誌)
座標xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
定義Service的介面和實現類,建立介面AccountService.java
**
* 賬戶的業務層介面
*/
public interface AccountService {
/**
* 模擬儲存賬戶
*/
void saveAccount();
/**
* 模擬更新賬戶
* @param i
*/
void updateAccount(int i);
/**
* 刪除賬戶
* @return
*/
int deleteAccount();
}
建立介面的實現類AccountServiceImpl.java
**
* 賬戶的業務層實現類
*/
public class AccountServiceImpl implements AccountService {
public void saveAccount() {
System.out.println("執行了儲存");
}
public void updateAccount(int i) {
System.out.println("執行了更新"+i);
}
public int deleteAccount() {
System.out.println("執行了刪除");
return 0;
}
}
建立增強類Logger.java
/**
* 用於記錄日誌的工具類,它裡面提供了公共的程式碼
*/
public class Logger {
/**
* 用於列印日誌:計劃讓其在切入點方法執行之前執行(切入點方法就是業務層方法)
*/
public void printLog(){
System.out.println("Logger類中的pringLog方法開始記錄日誌了。。。");
}
}
配置applicationContext.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">
<!-- 配置srping的Ioc,把service物件配置進來-->
<bean id="accountService" class="com.it.service.impl.AccountServiceImpl"></bean>
<!--spring中基於XML的AOP配置步驟
1、把通知Bean也交給spring來管理
2、使用aop:config標籤表明開始AOP的配置
3、使用aop:aspect標籤表明配置切面
id屬性:是給切面提供一個唯一標識
ref屬性:是指定通知類bean的Id。
4、在aop:aspect標籤的內部使用對應標籤來配置通知的型別
我們現在示例是讓printLog方法在切入點方法執行之前執行:所以是前置通知
aop:before:表示配置前置通知
method屬性:用於指定Logger類中哪個方法是前置通知
pointcut屬性:用於指定切入點表示式,該表示式的含義指的是對業務層中哪些方法增強
切入點表示式的寫法:
關鍵字:execution(表示式)
-->
<!-- 配置Logger類,宣告切面(建立物件,不是真正aop的切面) -->
<bean id="logger" class="com.it.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的型別,並且建立通知方法和切入點方法的關聯-->
<aop:before method="printLog" pointcut="execution(void com.it.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
<aop:before method="printLog" pointcut="execution(void com.it.service.impl.AccountServiceImpl.updateAccount(int))"></aop:before>
<aop:before method="printLog" pointcut="execution(int com.it.service.impl.AccountServiceImpl.deleteAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
測試
/**
* 測試AOP的配置
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AOPTest {
@Autowired
private AccountService as;
@Test
public void proxy(){
//3.執行方法
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}
切入點表示式的寫法(重點)
切入點表示式的寫法
關鍵字:execution(表示式)
表示式:
引數一:訪問修飾符(非必填)
引數二:返回值(必填)
引數三:包名.類名(非必填)
引數四:方法名(引數)(必填)
引數五:異常(非必填)
訪問修飾符 返回值 包名.包名.包名...類名.方法名(引數列表)
標準的表示式寫法:
public void com.it.service.impl.AccountServiceImpl.saveAccount()
訪問修飾符可以省略
void com.it.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用萬用字元(*:表示任意),表示任意返回值
* com.it.service.impl.AccountServiceImpl.saveAccount()
包名可以使用萬用字元,表示任意包。但是有幾級包,就需要寫幾個*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示當前包及其子包
* *..AccountServiceImpl.saveAccount()
類名和方法名都可以使用*來實現通配(一般情況下,不會這樣配置)
* *..*.*() == * *()
引數列表:
可以直接寫資料型別:
基本型別直接寫名稱 int
引用型別寫包名.類名的方式 java.lang.String
可以使用萬用字元表示任意型別,但是必須有引數
可以使用..表示有無引數均可,有引數可以是任意型別
全通配寫法:* *..*.*(..)
實際開發中切入點表示式的通常寫法:切到業務層實現類下的所有方法:* com.it.service..*.*(..)
最終
<aop:before method="printLog" pointcut="execution(* com.it.service..*.*(..))">
</aop:before>
Spring AOP的五種通知型別(使用XML)
- 前置通知
- 後置通知
- 異常通知
- 最終通知
- 環繞通知
座標xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
建立介面AccountService.java
/**
* 賬戶的業務層介面
*/
public interface AccountService {
/**
* 模擬儲存賬戶
*/
void saveAccount();
/**
* 模擬更新賬戶
* @param i
*/
void updateAccount(int i);
/**
* 刪除賬戶
* @return
*/
int deleteAccount();
}
建立介面的實現類AccountServiceImpl.java
/**
* 賬戶的業務層實現類
*/
public class AccountServiceImpl implements AccountService {
public void saveAccount() {
System.out.println("執行了儲存");
}
public void updateAccount(int i) {
System.out.println("執行了更新"+i);
}
public int deleteAccount() {
System.out.println("執行了刪除");
return 0;
}
}
建立增強類Logger.java
/**
* 用於記錄日誌的工具類,它裡面提供了公共的程式碼
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(JoinPoint jp){
System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了。。。");
}
/**
* 後置通知
*/
public void afterReturningPrintLog(JoinPoint jp){
System.out.println("後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了。。。");
}
/**
* 異常通知
*/
public void afterThrowingPrintLog(JoinPoint jp){
System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日誌了。。。");
}
/**
* 最終通知
*/
public void afterPrintLog(JoinPoint jp){
System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日誌了。。。");
}
/**
* 環繞通知
* 問題:
* 當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
* 分析:
* 通過對比動態代理中的環繞通知程式碼,發現動態代理的環繞通知有明確的切入點方法呼叫,而我們的程式碼中沒有。
* 解決:
* Spring框架為我們提供了一個介面:ProceedingJoinPoint。該介面有一個方法proceed(),此方法就相當於明確呼叫切入點方法。
* 該介面可以作為環繞通知的方法引數,在程式執行時,spring框架會為我們提供該介面的實現類供我們使用。
*
* spring中的環繞通知:
* 它是spring框架為我們提供的一種可以在程式碼中手動控制增強方法何時執行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法執行所需的引數
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。前置");
rtValue = pjp.proceed(args);//明確呼叫業務層方法(切入點方法)
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。後置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。異常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。最終");
}
}
}
配置applicationContext.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">
<!-- 配置srping的Ioc,把service物件配置進來-->
<bean id="accountService" class="com.it.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger類 -->
<bean id="logger" class="com.it.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入點表示式 id屬性用於指定表示式的唯一標識。expression屬性用於指定表示式內容
此標籤寫在aop:aspect標籤內部只能當前切面使用。
它還可以寫在aop:aspect外面,此時就變成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.it.service..*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入點方法執行之前執行
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
<!-- 配置後置通知:在切入點方法正常執行之後值。它和異常通知永遠只能執行一個
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
<!-- 配置異常通知:在切入點方法執行產生異常之後執行。它和後置通知永遠只能執行一個
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
<!-- 配置最終通知:無論切入點方法是否正常執行它都會在其後面執行
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
<!-- 配置環繞通知 詳細的註釋請看Logger類中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
測試
/**
* 測試AOP的配置
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AOPTest {
@Autowired
private AccountService as;
@Test
public void proxy(){
//3.執行方法
as.saveAccount();
}
}
Spring AOP的註解方式配置五種通知型別
座標xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
建立介面AccountService.java
/**
* 賬戶的業務層介面
*/
public interface AccountService {
/**
* 模擬儲存賬戶
*/
void saveAccount();
/**
* 模擬更新賬戶
* @param i
*/
void updateAccount(int i);
/**
* 刪除賬戶
* @return
*/
int deleteAccount();
}
建立介面的實現類AccountServiceImpl.java
/**
* 賬戶的業務層實現類
*/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
public void saveAccount() {
System.out.println("執行了儲存");
//int i=1/0;
}
public void updateAccount(int i) {
System.out.println("執行了更新"+i);
}
public int deleteAccount() {
System.out.println("執行了刪除");
return 0;
}
}
建立增強類Logger.java
/**
* 用於記錄日誌的工具類,它裡面提供了公共的程式碼
*/
@Component("logger")
@Aspect//表示當前類是一個切面類
public class Logger {
@Pointcut("execution(* com.it.service..*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
// @Before("pt1()")
public void beforePrintLog(JoinPoint jp){
System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了。。。");
}
/**
* 後置通知
*/
// @AfterReturning("pt1()")
public void afterReturningPrintLog(JoinPoint jp){
System.out.println("後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了。。。");
}
/**
* 異常通知
*/
// @AfterThrowing("pt1()")
public void afterThrowingPrintLog(JoinPoint jp){
System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日誌了。。。");
}
/**
* 最終通知
*/
// @After("pt1()")
public void afterPrintLog(JoinPoint jp){
System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日誌了。。。");
}
/**
* 環繞通知
* 問題:
* 當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
* 分析:
* 通過對比動態代理中的環繞通知程式碼,發現動態代理的環繞通知有明確的切入點方法呼叫,而我們的程式碼中沒有。
* 解決:
* Spring框架為我們提供了一個介面:ProceedingJoinPoint。該介面有一個方法proceed(),此方法就相當於明確呼叫切入點方法。
* 該介面可以作為環繞通知的方法引數,在程式執行時,spring框架會為我們提供該介面的實現類供我們使用。
*
* spring中的環繞通知:
* 它是spring框架為我們提供的一種可以在程式碼中手動控制增強方法何時執行的方式。
*/
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法執行所需的引數
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。前置");
rtValue = pjp.proceed(args);//明確呼叫業務層方法(切入點方法)
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。後置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。異常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。最終");
}
}
}
配置applicationContext.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.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">
<!-- 配置spring建立容器時要掃描的包-->
<context:component-scan base-package="com.it"></context:component-scan>
<!-- 配置spring開啟註解AOP的支援 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
測試
/**
* 測試AOP的配置
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AOPTest {
@Autowired
private AccountService as;
@Test
public void proxy(){
//3.執行方法
as.saveAccount();
}
}
發現問題:註解開發spring的aop,預設是:最終通知放置到了後置通知/異常通知的前面。要想實現最終通知放置到後置通知/異常通知的後面,怎麼辦?
解決方案:只能使用環繞通知。
完全使用註解
建立類SpringConfiguration.java
@Configuration
@ComponentScan(basePackages="com.it")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
測試類,AOPAnnoTest.java
/**
* 測試AOP的配置
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AOPAnnoTest {
@Autowired
private AccountService as;
@Test
public void proxy(){
//3.執行方法
as.saveAccount();
}
}