文章目錄
- AOP的引入
-
- 案例中存在的問題
- 解決辦法
- 新的問題
- 新問題的解決
-
- 動态代理介紹
- 基于接口的動态代理
- 基于子類的動态代理
- Spring 中的 AOP
-
- 什麼是 AOP
- AOP 中的術語
- 學習 spring 中的 AOP 要明确的事
AOP的引入
在上一篇文章中傳送門,我們做了一個賬戶的 CRUD 的小案例,在講 AOP 之前,我們先來分析該案例,一步一步地了解為什麼要有 AOP 以及什麼是 AOP。
案例中存在的問題
- 回顧上篇文章中的業務層代碼:
package com.cz.service.impl;
import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 賬戶的業務層實作類
*/
package com.cz.service.impl;
import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.service.AccountService;
import java.util.List;
/**
* 賬戶的業務層實作類
*/
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) {
return accountDao.saveAccount(account);
}
public void updateAccount(Account account) {
return accountDao.updateAccount(account);
}
public void removeAccount(Integer accountId) {
return accountDao.removeAccount(accountId);
}
}
問題就是:
事務被自動送出了。換言之,我們使用了
connection
對象的
setAutoCommit(true)
此方式控制事務,如果我們每次都執行一條 sql 語句,沒有問題,但是如果業務方法一次要執行多條 sql語句,這種方式就無法實作功能了。請看下面的示範:
- 修改業務層和持久層,添加一個轉賬方法
業務層:
/**
* 賬戶業務層接口
*/
public interface AccountService {
/**
* 轉賬
* @param sourceName 轉出賬戶名稱
* @param targetName 轉入賬戶名稱
* @param money 轉賬金額
*/
void transfer(String sourceName,String targetName,Float money);
}
/**
* 賬戶的業務層實作類
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void transfer(String sourceName, String targetName, Float money) {
//1.根據名稱查詢轉出賬戶
Account source = accountDao.findAccountByName(sourceName);
//2.根據名稱查詢轉入賬戶
Account target = accountDao.findAccountByName(targetName);
//3.轉出賬戶減錢
source.setMoney(source.getMoney()-money);
//4.轉入賬戶加錢
target.setMoney(target.getMoney()+money);
//5.更新轉出賬戶
accountDao.updateAccount(source);
int i=1/0; //模拟轉賬異常
//6.更新轉入賬戶
accountDao.updateAccount(target);
}
}
持久層:
/**
* 賬戶的持久層接口
*/
public interface AccountDao {
/**
* 根據名稱查詢賬戶
* @param accountName
* @return 如果有唯一結果,就傳回。如果沒有結果就傳回null;
* 如果結果集超過一個就抛異常
*/
Account findAccountByName(String accountName);
}
/**
* 賬戶的持久層實作類
*/
public class AccountDaoImpl implements AccountDao {
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("結果集不為1,資料異常");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
實作類:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("張三","李四",200f);
}
}

由于執行有異常,轉賬失敗。但是因為每次執行持久層方法都是獨立事務,導緻無法實作事務控制 ( 不符合事務的一緻性)
解決辦法
為了解決這個問題,我們可以讓業務層來控制事務的送出和復原。
首先我們先對業務層的實作類的代碼進行分析:
- 建立工具類
,該類用于擷取和線程綁定的ConnectionUtils
對象,保證每個線程中隻能有一個控制事務的Connection
對象Connection
package com.cz.utils;
import javafx.scene.chart.PieChart;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* 連接配接的工具類,它用于從資料源中擷取一個連接配接,并且實作和線程的綁定
*/
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.先從ThredLocal擷取
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
package com.cz.utils;
import java.sql.Connection;
/**
* 和事務管理相關的工具類,它包含了開啟事務,送出事務,復原事務和釋放連接配接
*/
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();
}
}
}
要注意方法中,因為使用連接配接池,是以調用
release()
對象的
Connection
方法後,是将連接配接歸還到連接配接池中,是以要把連接配接與綁定的線程解綁,不然下次從線程池中取出該線程,并擷取綁定的連接配接進行使用時,由于連接配接被調用過
close()
方法,會抛出異常
close()
(問題存在于 WEB 工程中)
You can't operate on a closed Connection
- 修改業務層代碼,為所有方法加上事務管理
package com.cz.service.impl;
import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.service.AccountService;
import com.cz.utils.TransactionManager;
import java.util.List;
/**
* 賬戶的業務層實作類
* 事務控制都在業務層
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionManager tsManager;
public void setTsManager(TransactionManager tsManager) {
this.tsManager = tsManager;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public List<Account> findAllAccount() {
try{
//1.開啟事務
tsManager.beginTransaction();
//2.執行操作
List<Account> accounts = accountDao.findAllAccount();
//3.送出事務
tsManager.commit();
//4.傳回結果
return accounts;
}catch (Exception e){
//5.復原操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.釋放連接配接
tsManager.release();
}
}
public Account findAccountById(Integer accountId) {
try{
//1.開啟事務
tsManager.beginTransaction();
//2.執行操作
Account account = accountDao.findAccountById(accountId);
//3.送出事務
tsManager.commit();
//4.傳回結果
return account;
}catch (Exception e){
//5.復原操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.釋放連接配接
tsManager.release();
}
}
public void saveAccount(Account account) {
try{
//1.開啟事務
tsManager.beginTransaction();
//2.執行操作
accountDao.saveAccount(account);
//3.送出事務
tsManager.commit();
}catch (Exception e){
//4.復原操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//5.釋放連接配接
tsManager.release();
}
}
public void updateAccount(Account account) {
try{
//1.開啟事務
tsManager.beginTransaction();
//2.執行操作
accountDao.updateAccount(account);
//3.送出事務
tsManager.commit();
}catch (Exception e){
//4.復原操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//5.釋放連接配接
tsManager.release();
}
}
public void removeAccount(Integer accountId) {
try{
//1.開啟事務
tsManager.beginTransaction();
//2.執行操作
accountDao.removeAccount(accountId);
//3.送出事務
tsManager.commit();
}catch (Exception e){
//4.復原操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//5.釋放連接配接
tsManager.release();
}
}
public void transfer(String sourceName, String targetName, Float money) {
try{
//1.開啟事務
tsManager.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.送出事務
tsManager.commit();
}catch (Exception e){
//4.復原操作
tsManager.rollback();
}finally {
//5.釋放連接配接
tsManager.release();
}
}
}
- 修改持久層代碼,因為我們現在的
對象是由Connection
工具類來提供的(與線程綁定),而不是讓ConnectionUtils
對象自動去連接配接池中取(沒有與線程綁定),是以QueryRunner
對象不再需要注入資料源了。QueryRunner
package com.cz.dao.impl;
import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.util.List;
/**
* 賬戶的持久層實作類
*/
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 int saveAccount(Account account) {
try{
return runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,? )",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public int updateAccount(Account account) {
try{
return 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 int removeAccount(Integer accountId) {
try{
return 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("結果集不為1,資料異常");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
- 編寫配置檔案,管理依賴關系
<?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.cz.service.impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
<!-- 注入事務管理器-->
<property name="tsManager" ref="tsManager"/>
</bean>
<!-- 配置dao對象-->
<bean id="accountDao" class="com.cz.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<!-- 配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置資料源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入連接配接資料庫的必備資訊-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC&characterEncoding=utf-8"/>
<property name="user" value="root"/>
<property name="password" value="7107883"/>
</bean>
<!-- 配置 Connection 工具類 ConnectionUtils-->
<bean id="connectionUtils" class="com.cz.utils.ConnectionUtils">
<!-- 注入資料源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="tsManager" class="com.cz.utils.TransactionManager">
<!-- 注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
</beans>
新的問題
通過對業務層加上事務控制,此時的轉賬方法已經可以正常運作,但是由于我們的業務層每個方法都加上了事務控制,是以顯得業務層的方法特别臃腫,業務核心代碼可能就
accountDao.removeAccount(accountId);
這麼一句,其餘的都是和事務有關,這樣也就造成了業務層方法的耦合度很高。
如果此時我們的
TransactionManager
類中的方法名進行了更改,那麼我們需要在每處調用的地方都進行修改,這樣不利于我們後期的開發。
新問題的解決
那麼如何解決呢?這裡我們可以使用動态代理,把事務的控制交給代理對象,是以下面我們先來看看關于動态代理的基本使用。
動态代理介紹
之前在講mybatis的入門案例的時候也給大家貼過兩篇其他大佬寫的動态代理,下面我們簡單的了解一下,想深入的朋友可以去看看那兩篇文章。
什麼是動态代理:
簡單來說就是使用反射動态建立代理對象,使用代理對象來代替目标對象,是以達到增強目标對象的功能
動态代理的特點
位元組碼随用随建立,随用随加載。它與靜态代理的差別也在于此,因為靜态代理是位元組碼一上來就建立好,并完成加載。裝飾者模式就是靜态代理的一種展現。
動态代理的常用兩種方式
基于接口的動态代理
基于子類的動态代理
基于接口的動态代理
提供者:JDK 官方的 Proxy 類。
要求:被代理類最少實作一個接口。
- 在這裡我們使用廠家的例子:以前廠家不僅要對産品進行售後處理,還得負責銷售的環節,把産品賣給客戶。但是随着時間的推移,出現了一個新的角色:代理商,這時候廠家隻需要負責對産品的售後處理,銷售的事情交給代理商即可。如果客戶需要售後維修,那麼隻需要将産品交給代理商,由代理商送往廠家進行維修。如下圖:
SSM之Sping系列(五)---- Spring中AOP的引入及相關概念AOP的引入Spring 中的 AOP - 定義接口,表示廠家需要具備的功能(銷售以及售後)
package com.cz.proxy;
/**
* 生産廠家需要實作的接口
*/
public interface IProducer {
/**
* 銷售
* @param money
*/
void saleProduct(float money);
/**
* 售後
* @param money
*/
void afterService(float money);
}
- 定義廠家類
package com.cz.proxy;
/**
* 一個生産者
*/
public class Producer implements IProducer{
/**
* 銷售
* @param money
*/
public void saleProduct(float money){
System.out.println("銷售産品,并拿到錢:" +money);
}
/**
* 售後
* @param money
*/
public void afterService(float money){
System.out.println("提供售後服務,并拿到錢:"+money);
}
}
- 定義測試類,進行動态代理
package com.cz.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 消費者
*/
public class Client {
public static void main(String[] args) {
//廠家
final Producer producer = new Producer();
// 該對象相當于代理商
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:執行被代理對象的任何方法都會被該方法攔截到
* @param proxy 代理對象的引用
* @param method 目前執行的方法
* @param args 目前執行方法所需的參數
* @return 傳回值類型與被代理對象方法一緻
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增強代碼
Object returnValue = null;
//1.擷取方法執行的參數
Float money = (Float)args[0];
//2.判斷目前方法是不是銷售
if ("saleProduct".equals(method.getName())){
System.out.println("消費者花了 " + money + "元購買商品...");
// 對參數進行增強,代理商需要抽取 2 成利潤
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
- 運作結果如圖
- 使用
建立代理對象,參數如下:
Proxy.newProxyInstance()
:類加載器,用于加載代理對象位元組碼,和被代理對象使用相同的類加載器。固定寫法:
ClassLoader
被代理對象.getClass().getClassLoader()
:位元組碼數組,用于讓代理對象和被代理對象具有相同的接口方法。固定寫法:
Class[]
被代理對象.getClass().getInterfaces()
: 處理器,用于提供增強的代碼,一般都是編寫一個該接口的匿名内部類。此接口的實作類都是誰用誰寫。
InvocationHandler
方法用于攔截被代理對象的方法,執行被代理對象的任何方法都會被該方法攔截到,參數如下:
invoke()
:代理對象的引用
Object proxy
:目前執行的方法
Method method
:目前執行方法所需的參數
Object[] args
- 傳回值類型與被代理對象方法一緻
- 匿名内部類通路外部方法的成員變量時都要求外部成員變量添加
修飾符,
final
final
修飾變量代表該變量隻能被初始化一次,以後不能被修改。參考連結:
匿名内部類如何通路外部類的成員變量
匿名内部類通路方法成員變量需要加final的原因及證明
基于子類的動态代理
要求:被代理對象不能是最終類
- 還是使用上面的例子
- 基于子類的動态代理是使用第三方庫
提供的CGLIB
類,要求被代理類不能是被Enhancer
final 修飾的類(最終類)
- 導入第三方庫依賴
或者手動導入jar 包CGLIB
和cglib-2.1.3.jar
asm.jar
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
老樣子,報錯的看這兩篇文章傳送門1
傳送門2
- 廠家類無需實作接口
package com.cz.cglib;
/**
* 一個生産者
*/
public class Producer{
/**
* 銷售
* @param money
*/
public void saleProduct(float money){
System.out.println("銷售産品,廠家拿到錢:" +money);
}
/**
* 售後
* @param money
*/
public void afterService(float money){
System.out.println("提供售後服務,并拿到錢:"+money);
}
}
- 編寫測試類
package com.cz.cglib;
import com.cz.proxy.IProducer;
import com.cz.proxy.Producer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 消費者
*/
public class Client {
public static void main(String[] args) {
//廠家
final Producer producer = new Producer();
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(),
new MethodInterceptor() {
/**
*執行被代理對象的任何方法,都會經過該方法。在此方法内部就可以對被代理對象的任何方法進行增強。
* @param proxy
* @param method
* @param args
* 前三個和基于接口的動态代理是一樣的。
* @param methodProxy 目前執行方法的代理對象。
* @return 目前執行方法的傳回值
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增強代碼
Object returnValue = null;
//1.擷取方法執行的參數
Float money = (Float)args[0];
//2.判斷目前方法是不是銷售
if ("saleProduct".equals(method.getName())){
System.out.println("消費者花了 " + money + "元購買商品...");
// 對參數進行增強,代理商需要抽取 2 成利潤
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(120000f);
}
}
- 使用
建立代理對象,參數如下:
Enhancer.create()
: 位元組碼,用于指定被代理對象的位元組碼。
Class
固定寫法:被代理對象.getClass()
: 回調接口,用于提供增強的代碼,一般都是編寫該接口的子接口實作類
Callback
MethodInterceptor
方法用于攔截被代理對象的方法,執行被代理對象的任何方法都會被該方法攔截到,參數如下:
intercept()
:代理對象的引用
Object proxy
:目前執行的方法
Method method
:目前執行方法所需的參數
Object[] args
:目前執行方法的代理對象
MethodProxy methodProxy
- 傳回值類型與被代理對象方法一緻
動态代理的基本使用就講到這了,接下來我們回歸正題。如何解決問題:
- 修改業務層代碼,将其恢複原樣,不再添加事務的控制代碼
- 建立代理工廠,用于擷取 service 的代理對象
package com.cz.factory;
import com.cz.service.AccountService;
import com.cz.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 用于建立Service的代理對象的工廠
*/
public class BeanFactory {
private AccountService accountService;
private TransactionManager tsManager;
public void setTsManager(TransactionManager tsManager) {
this.tsManager = tsManager;
}
public final void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
/**
* 擷取Service代理對象
*/
public AccountService getAccountService() {
return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加對事務的支援
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
try {
//1.開啟事務
tsManager.beginTransaction();
//2.執行操作
rtValue = method.invoke(accountService, args);
//3.送出事務
tsManager.commit();
//4.傳回結果
return rtValue;
} catch (Exception e) {
//5.復原操作
tsManager.rollback();
throw new RuntimeException(e);
} finally {
//6.釋放連接配接
tsManager.release();
}
}
});
}
}
- 修改配置檔案
<!-- 配置代理的service -->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<!-- 配置 BeanFactory -->
<bean id="beanFactory" class="com.cz.factory.BeanFactory">
<!-- 注入 service -->
<property name="accountService" ref="accountService"/>
<!-- 注入事務管理器 -->
<property name="tsManager" ref="tsManager"/>
</bean>
<!--配置service-->
<bean id="accountService" class="com.cz.service.impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
</bean>
到這裡的時候,改造已經完成,通過代理對象一樣可以控制住事務,同時業務層不再需要編寫一堆的重複事務控制代碼。但是如果每次管理事務的時候,我們都要像這樣自己建立代理對象,那不是也挺麻煩的嗎?有沒有更好的方法呢?這時候就需要 AOP 的出場了。
Spring 中的 AOP
什麼是 AOP
- AOP 即面向切面程式設計,英文全稱為 Aspect Oriented Programming
- 簡單的說它就是把我們程式重複的代碼抽取出來,在需要執行的時候,
,在不修改源碼的基礎上,對我們的已有方法進行增強。使用動态代理的技術
- 簡單的說它就是把我們程式重複的代碼抽取出來,在需要執行的時候,
- 作用
-
。在程式運作期間,不修改源碼對已有方法進行增強
-
- 優勢
- 減少重複代碼
- 提高開發效率
- 維護友善
AOP 中的術語
Joinpoint (連接配接點)
- 所謂連接配接點是指那些
,在 spring 中,這些點指的是方法,因為 spring 隻支援方法類型的連接配接點被攔截到的方法
- 比如說,我們的業務層中的方法都是連接配接點,因為我們對業務層中的所有方法都進行了攔截
Pointcut (切入點)
- 所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義,簡單來說就是
被增強的方法
- 比如說,我們在業務層新增了一個方法
,但是在代理工廠中,我們不對該方法進行增強,而是直接放行,那麼此時的test()
就不是切入點,僅僅是一個連接配接點,而其他的方法都被事務管理,也就是切入點test()
SSM之Sping系列(五)---- Spring中AOP的引入及相關概念AOP的引入Spring 中的 AOP
Advice (通知/ 增強)
- 所謂通知是指攔截到方法之後所要做的事情,簡單來說就是
對切入點進行的增強操作
- 通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知
SSM之Sping系列(五)---- Spring中AOP的引入及相關概念AOP的引入Spring 中的 AOP
Introduction (引介)
- 引介是一種特殊的通知,在不修改類代碼的前提下,Introduction 可以在運作期為類動态地添加一些方法或 Field
Target (目标對象)
- 被代理的對象
Weaving (織入)
- 是指把增強應用到目标對象來建立新的代理對象的過程
Proxy (代理)
- 一個類被 AOP 織入增強後,就産生一個結果代理類
Aspect (切面)
- 是切入點和通知(引介)的結合
學習 spring 中的 AOP 要明确的事
-
開發階段(我們做的)
編寫核心業務代碼(開發主線):大部分程式員來做,要求熟悉業務需求。
把公用代碼抽取出來,制作成通知。(開發階段最後再做):AOP 程式設計人員來做。
在配置檔案中,聲明切入點與通知間的關系,即切面。:AOP 程式設計人員來做。
-
運作階段(Spring 架構完成的)
Spring 架構監控切入點方法的執行。一旦監控到切入點方法被運作,使用代理機制,動态建立目标對
象的代理對象,根據通知類别,在代理對象的對應位置,将通知對應的功能織入,完成完整的代碼邏輯運作。
這篇文章就到這了,有點長,能看完的都很了不起。下一篇文章将講解Spring 中基于注解的 AOP 配置