學習筆記【SSM-第三節:Spring框架的AOP】
事務問題
轉賬例子:
Account sourceAccount = accountDao.findByName(sourceName);
Account targetAccount = accountDao.findByName(targetName);
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
accountDao.UpdateAccount(sourceAccount) ;
accountDao.UpdateAccount(targetAccount);
若其中出現錯誤,會造成嚴重後果,雖然會提交,但他會把每一次操作看成是一次事務。
所以我們用到了ThreadLocal,將Connection傳入其中,這裡寫一個ConnectionUtils:
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<>();
private DataSource dataSource;
public void setDataSource (DataSource dataSource) {
this.dataSource = dataSource;
}
//獲取當前執行緒上的連線
public Connection getThreadConnection(){
try {
//先從ThreadLocal上獲取
Connection conn=tl.get();
//判斷當前執行緒上是否有連結
if (conn==null) {
//從資料來源中獲取一個連線,並存入ThreadLocal中
conn=dataSource.getConnection();
tl.set(conn);
}
//返回當前執行緒上的連線
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void removeConnection(){
tl.remove();
}
}
再寫一個事務類,有開啟事務、提交事務回滾事務、釋放連線:
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//開啟事務
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//提交事務
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//回滾事務
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//釋放連線
public void release(){
try {
connectionUtils.getThreadConnection().close();//還回連線池中
connectionUtils.removeConnection();//解綁ThreadLocal和Connection
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
最終轉賬方法變成了:
public void transfer(String sourceName, String targetName, double money) {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
Account sourceAccount = accountDao.findByName(sourceName);
Account targetAccount = accountDao.findByName(targetName);
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
accountDao.UpdateAccount(sourceAccount);
accountDao.UpdateAccount(targetAccount);
//3.提交事務
txManager.commit();
} catch (Exception e) {
//回滾事務
txManager.rollback();
e.printStackTrace();
}finally {
//釋放資源
txManager.release();
}
}
還需要挺多配置,如dao中的獲取連線和xml的配置。我們會發現,這樣會非常的麻煩,若服務有很多方法,要每個方法中都加上事務,還要寫好多類,配置很多。
動態代理
特點: 位元組碼隨用隨建立,隨用隨載入
作用: 不修改原始碼的基礎上對方法增強
分類:
- 基於介面的動態代理
- 基於子類的動態代理
基於介面的動態代理:
涉及的類:Proxy
如何建立代理物件:
使用Proxy中的newProxyInstance方法
建立代理物件的要求:
被代理類最少實現一個介面,如果沒有則不能用
newProxyInstance方法的引數:
- ClassLoader:類載入器:載入代理物件位元組碼的。和被代理物件使用相同的類載入器,固定寫法。
- Class[]:位元組碼陣列:用於讓代理物件和被代理物件有相同的方法。固定寫法。
- InvocationHandler:用於提供增強的程式碼:通常情況下都是匿名內部類,但不必須
基於子類的動態代理
涉及的類:Enhancer(第三方cglib庫)
如何建立代理物件:
使用Enhancer中的create方法
建立代理物件的要求:
被代理類不能是最終類
create方法的引數:
- Class:用於指定被代理物件的位元組碼。
- Callback:用於提供增強的程式碼:通常情況下都是匿名內部類,但不必須。我們一般寫的都是該介面的子介面的實行類:MethodIntercepetor
所以上面的轉賬可加一個動態代理,以做到事務管理的一種方法增強:
public class BeanFactory {
private AccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
public AccountService getAccountService() {
AccountService as = (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
//新增事務的支援
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
rtValue = method.invoke(accountService, args);
//3.提交事務
txManager.commit();
//4.返回結果
return rtValue;
} catch (Exception e) {
//回滾事務
txManager.rollback();
throw new RuntimeException(e);
} finally {
//釋放資源
txManager.release();
}
}
});
return as;
}
}
xml:
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<bean id="beanFactory" class="cn.huangyy.factory.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="txManager" ref="txManager"></property>
</bean>
AOP
Aspect Oriented Programming 面向切面程式設計
通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。可利用AOP對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
作用:
在程式執行期間,不修改原始碼對已有的方法進行增強。
優勢:
- 減少重複程式碼
- 提高開發效率
- 維護方便
AOP相關術語:
- Joinpoint(連線點):所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點。通俗說就是業務和增強方法的連線口。
- Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義。通俗點說就是被增強的。
- Adive(增強/通知):所謂通知是指攔截到Joinponit之後要做的事就是通知。型別:前置通知、後置通知、異常通知、最終通知、環繞通知。
- Introduction(引介):引介是一種特殊的通知,在不修改類程式碼的前提下,Introduction可以在執行期為類動態地新增一些方法或Field
- Target(目標物件):代理的目標物件
- Weaving(織入):是指把增強應用到目標物件來建立新的代理物件的過程。Spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。
- Proxy(代理):一個類被AOP織入增強後,就產出一個結果代理類
- Aspect(切面):是切入點和通知(引介)的結合。
xml配置aop
例:
-
1.把通知bean交給spring來管理
-
2.配置AOP 。
aop:config
-
3.使用
aop:aspect
標籤配置切面- id屬性:給切面提供一個唯一標誌
- ref屬性:指定通知類bean的id
-
4.我們如今是讓printLog在切入點方法執行之前執行,則前置通知
aop:before
- method屬性:指定logger類哪個方法是前置通知
- pointcut屬性:指定切入點表示式,該表示式的含義指的是對業務層中哪些方法增強
-
5.切入點表示式的寫法:
- 關鍵字:execution(表示式)
- 表示式:訪問修飾符 返回值 包名.包名.包名…類名.方法名(引數列表)例:
public void cn.huangyy.service.impl.AccountService.saveAccount()
- 細節:訪問修飾符可以省略
- 可以使用萬用字元
*
來標識,如* cn.huangyy.service.impl.AccountService.saveAccount()
- 可以使用…表示當前包及其子包,如
* *..AccountService.saveAccount()
- 可以使用
*
來標識類名及方法名,如* *..*.*()
- 引數列表:基本型別直接寫名稱,引用寫
包名.類名
的方式。可用*則必須有引數,使用…則有無引數都可 - 全通配寫法
* *..*.*(..)
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountService" class="cn.huangyy.service.impl.AccountServiceImpl"></bean>
<!--把通知bean也交給spring來管理-->
<bean id="logger" class="cn.huangyy.utils.Logger"></bean>
<!--配置AOP.aop:config-->
<aop:config>
<!--使用aop:aspect標籤配置切面
id屬性:給切面提供一個唯一標誌
ref屬性:指定通知類bean的id
-->
<aop:aspect id="logAdvice" ref="logger">
<!--我們如今是讓printLog在切入點方法執行之前執行,則前置通知aop:before
method屬性:指定logger類哪個方法是前置通知
pointcut屬性:指定切入點表示式,該表示式的含義指的是對業務層中哪些方法增強
切入點表示式的寫法:
關鍵字:execution(表示式)
表示式:訪問修飾符 返回值 包名.包名.包名...類名.方法名(引數列表)
例:public void cn.huangyy.service.impl.AccountService.saveAccount()
-->
<aop:before method="printLog" pointcut="execution(public void cn.huangyy.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
通知型別:
- 前置通知:
aop:before
- 後置通知:
aop:after-returning
- 異常通知:
aop:after-throwing
- 最終通知:
aop:after
配置切入點表示式:
可用aop:pointcut
- id屬性:用於指定表示式的唯一標誌
- expression屬性:用於指定表示式內容
- 細節:寫在切面內只能在切面內用,還可寫在aop:aspect前面,讓所有切面可用
例:
<!--前置通知-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--後置通知-->
<aop:after-returning method="afterPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--異常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--最終通知-->
<aop:after method="endPrintLog" pointcut-ref="pt1"></aop:after>
<!--配置切入點表示式 id屬性用於指定表示式的唯一標誌,expression屬性用於指定表示式內容-->
<aop:pointcut id="pt1" expression="execution(* cn.huangyy.service.impl.*.*(..))"/>
環繞通知
aop:around
當我們配置了環繞通知之後,切入點方法沒有執行,對比動態代理中的環繞代理中的環繞通知程式碼,發現動態代理的環繞通知有明確的切入點方法呼叫
Spring框架為我們提供了一個介面:ProceedingJoinPoint
。
該介面有個方法proceed()
,此方法就明確呼叫切入點方法。該介面可以作為環繞通知的方法引數,在程式執行時,spring框架會為我們提供該介面的實現類供我們使用。
例子:
xml:
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
方法:
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue=null;
try {
Object[] args=pjp.getArgs();//得到方法所需的引數
System.out.println("前置");
rtValue=pjp.proceed(args);//明確呼叫業務層方法(切入點方法)
System.out.println("後置");
return rtValue;
} catch (Throwable t) {
System.out.println("異常");
throw new RuntimeException(t);
}finally {
System.out.println("最終");
}
}
註解的AOP
xml中:
- context:component-scan:註解會掃描的範圍
- aop:aspectj-autoproxy:配置spring開啟註解AOP的支援
切面類:
- @Aspect:表示當前類是一個切面類
- @Pointcut:配置切入點表示式(寫個空方法即可)
- @Before:前置通知
- @AfterReturning:後置通知
- @AfterThrowing:異常通知
- @After:最終通知
- @Around:環繞通知
例:
@Component("logger")
@Aspect
public class Logger {
@Pointcut("execution(* cn.huangyy.service.impl.*.*(..))")
private void pt1(){}
@Before("pt1()")
public void beforePrintLog(){
System.out.println("Logger中的--before開始記錄日誌了。。。");
}
@AfterReturning("pt1()")
public void afterPrintLog(){
System.out.println("Logger中的--after開始記錄日誌了。。。");
}
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("Logger中的--異常開始記錄日誌了。。。");
}
@After("pt1()")
public void endPrintLog(){
System.out.println("Logger中的--最終開始記錄日誌了。。。");
}
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue=null;
try {
Object[] args=pjp.getArgs();//得到方法所需的引數
System.out.println("前置");
rtValue=pjp.proceed(args);//明確呼叫業務層方法(切入點方法)
System.out.println("後置");
return rtValue;
} catch (Throwable t) {
System.out.println("異常");
throw new RuntimeException(t);
}finally {
System.out.println("最終");
}
}
}
Spring中的事務控制
- 需要spring-tx和jdbc的jar包
- 配置事務管理器
- 配置事務的通知(此時需要匯入事務的約束,tx和aop的)。 使用
tx:advice
標籤配置事務控制- 屬性:
- id:給事務通知起唯一標誌
- transaction-manager:給事務通知提供一個事務管理器
- 配置事務的屬性
tx:attributes
及其中的tx:method
- 屬性:
- isolation:用於指定事務的隔離級別,預設值是default,表示使用資料庫的隔離級別
- propagation:用於指定事務的傳播行為。預設值為Required,表示一定會有事務,增刪改的選擇。查詢方法可是選擇Supports
- read-only:用於指定事務是否只讀,只有查詢方法才能設定為true。預設值為false,表示讀寫。
- timeout:用於指定事務的超時時間,預設為-1,永不超時。如果指定了數值,則以秒為單位
- rollback-for:用於指定一個異常,當產生異常時,事務回滾,產生其他異常時,事務不回滾。沒有預設值,表示都回滾
- rollback-for:用於指定一個異常,當產生異常時,事務不回滾,產生其他異常時,事務回滾。沒有預設值,表示都回滾
- 配置AOP的切入點表示式
- 建立事務通知和切入點表示式的對應關係
aop:advisor
<!--配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事務的通知(此時需要匯入事務的約束,tx和aop的)-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事務的屬性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置AOP的切入點表示式-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* cn.huangyy.service.impl.*.*(..))"/>
<!--建立事務通知和切入點表示式的對應關係-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
基於註解的事務控制
- 配置事務管理器
- 開啟spring對註解事務的支援
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 在需要事務支援的地方使用
@Transactional
註解