1. AOP術(shù)語
JointPoint(連接點):指的是可用于把增強代碼加入到業(yè)務(wù)主線中的點扛门。方法開始、結(jié)束纵寝、正常運行完畢论寨、方法異常時等這些特殊的時機點星立,我們稱之為連接點。
PointPut(切點):指的是那些已經(jīng)把增強代碼假如到業(yè)務(wù)主線進(jìn)來之后的連接點葬凳。每個程序類都擁有多個連接點绰垂,如一個擁有兩個方法的類,這兩個方法都是連接點火焰,即連接點是程序類中客觀存在的事物劲装。AOP通過“切點”定位特定的連接點。連接點相當(dāng)于數(shù)據(jù)庫中的記錄昌简,而切點相當(dāng)于查詢條件占业。切點和連接點不是一對一的關(guān)系,一個切點可以匹配多個連接點纯赎。
Advice(通知/增強):指的是切面類中用于提供增強功能的方法谦疾。
Target(目標(biāo)對象):指的是代理的目標(biāo)對象,即被代理對象犬金。
Proxy(代理):指的是一個類被AOP織入增強后念恍,產(chǎn)生的代理類,即代理對象佑附。
Weaving(織入):指的是把增強應(yīng)用到目標(biāo)對象來創(chuàng)建新的代理對象的過程樊诺。Spring采用動態(tài)代理織入,而AspectJ采用編譯期織入和類裝載期織入音同。
Aspect(切面):指的是增強的代碼所關(guān)注的方面词爬,把這些相關(guān)的增強代碼定義到一個類中,這個類就是切面類权均。例如顿膨,事務(wù)切?,它??定義的?法就是和事務(wù)相關(guān)的叽赊,像開啟事務(wù)恋沃,提交事務(wù),回滾事務(wù)等等必指,不會定義其他與事務(wù)?關(guān)的?法囊咏。
Aspect切? = 切?點+增強= 切?點(鎖定?法) + ?位點(鎖定?法中的特殊時機)+ 橫切邏輯。
眾多的概念塔橡,?的就是為了鎖定要在哪個地?插?什么橫切邏輯代碼
2. Spring中AOP的代理選擇
Spring實現(xiàn)AOP思想使用的是動態(tài)代理技術(shù)梅割。
默認(rèn)下Spring會根據(jù)被代理對象是否實現(xiàn)接口來選擇適用JDK還是CGLIB。當(dāng)被代理對象中沒有實現(xiàn)任何接口時葛家,Spring會選擇CGLIB,如果對象實現(xiàn)接口户辞,則會使用JDK官方的代理技術(shù)。不過我們可以通過配置的方式癞谒,讓Spring強制使用CGLIB底燎。
3. Spring AOP中的配置方式
在Spring的AOP配置中刃榨,也和IOC一樣,支持三種配置
第?類:使?XML配置
第?類:使?XML+注解組合配置
第三類:使?純注解配置
4. Spring中AOP實現(xiàn)
需求:橫切邏輯代碼是打印?志双仍,希望把打印?志的邏輯織?到?標(biāo)?法的特定位置(service層transfer?法)
4.1 XML模式
- 坐標(biāo)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- AOP核心配置
<!--
Spring基于XML的AOP配置前期準(zhǔn)備:
在spring的配置?件中加?aop的約束
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
Spring基于XML的AOP配置步驟:
第?步:把通知Bean交給Spring管理
第?步:使?aop:config開始aop的配置
第三步:使?aop:aspect配置切?
第四步:使?對應(yīng)的標(biāo)簽配置通知的類型
??案例采?前置通知枢希,標(biāo)簽為aop:before
-->
<!--把通知bean交給spring來管理-->
<bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>
<!--開始aop的配置-->
<aop:config>
<!--配置切?-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<aop:before method="printLog" pointcut="execution(public *
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou
.pojo.Account))"></aop:before>
</aop:aspect>
</aop:config>
- 細(xì)節(jié)
a. 關(guān)于切入點表達(dá)式
上述配置實現(xiàn)了對 TransferServiceImpl 的 updateAccountByCardNo ?法進(jìn)?增強,在其執(zhí)?之前朱沃,輸出了記錄?志的語句晴玖。這??,我們接觸了?個?較陌?的名稱:切?點表達(dá)式为流。
切?點表達(dá)式,也稱之為AspectJ切?點表達(dá)式让簿,指的是遵循特定語法結(jié)構(gòu)的字符串敬察,其作?是?于對符合語法格式的連接點進(jìn)?增強。(可以百度一下具體規(guī)則)
b. 改變代理方式的配置
Spring在選擇創(chuàng)建代理對象時尔当,會根據(jù)被代理對象的實際情況來選擇的莲祸。被代理對象實現(xiàn)了接?,則采?基于接?的動態(tài)代理椭迎。當(dāng)被代理對象沒有實現(xiàn)任何接?的時候锐帜,Spring會?動切換到基于?類的動態(tài)代理?式。
但是我們都知道畜号,?論被代理對象是否實現(xiàn)接?缴阎,只要不是final修飾的類(被final修飾的類無法被繼承)都可以采?cglib提供的?式創(chuàng)建代理對象。所以Spring也考慮到了這個情況简软,提供了配置的?式實現(xiàn)強制使?基于?類的動態(tài)代理(即cglib的?式)蛮拔,配置的?式有兩種
- 使用aop:config標(biāo)簽配置
<aop:config proxy-target-class="true">
- 使用aop:aspectj-autoproxy標(biāo)簽配置
<!--此標(biāo)簽是基于XML和注解組合配置AOP時的必備標(biāo)簽,表示Spring開啟注解配置AOP的?持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj?autoproxy>
c. 五種通知類型
- 前置通知
- 正常執(zhí)行時通知
- 異常通知
- 最終通知
- 環(huán)繞通知
配置方式demo 痹升,以環(huán)繞通知舉例:
<!--
作?:
?于配置環(huán)繞通知建炫。
出現(xiàn)位置:
它只能出現(xiàn)在aop:aspect標(biāo)簽的內(nèi)部
屬性:
method:?于指定環(huán)繞通知的?法名稱
pointcut:?于指定切?點表達(dá)式
pointcut-ref:?于指定切?點表達(dá)式的引?
-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
4.1 XML+注解模式
- XML中開啟Spring對注解AOP的支持
<!--開啟spring對注解aop的?持-->
<aop:aspectj-autoproxy/>
- 示例
/**
* 模擬記錄?志
* @author 應(yīng)癲
*/
@Component
@Aspect
public class LogUtil {
/**
* 我們在xml中已經(jīng)使?了通?切?點表達(dá)式,供多個切?使?疼蛾,那么在注解中如何使?呢肛跌?
* 第?步:編寫?個?法
* 第?步:在?法使?@Pointcut注解
* 第三步:給注解的value屬性提供切?點表達(dá)式
* 細(xì)節(jié):
* 1.在引?切?點表達(dá)式時,必須是?法名+()察郁,例如"pointcut()"衍慎。
* 2.在當(dāng)前切?中使?,可以直接寫?法名绳锅。在其他切?中使?必須是全限定?法名西饵。
*/
@Pointcut("execution(* com.lagou.service.impl.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,參數(shù)是:"+
Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue")
public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog鳞芙,返回值
是:"+rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowingPrintLog(Throwable e){
System.out.println("異常通知:afterThrowingPrintLog眷柔,異常是:"+e);
}
@After("pointcut()")
public void afterPrintLog(){
System.out.println("最終通知:afterPrintLog");
}
/**
* 環(huán)繞通知
* @param pjp
* @return
*/
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
//定義返回值
Object rtValue = null;
try{
//前置通知
System.out.println("前置通知");
//1.獲取參數(shù)
Object[] args = pjp.getArgs();
//2.執(zhí)?切?點?法
rtValue = pjp.proceed(args);
//后置通知
System.out.println("后置通知");
}catch (Throwable t){
//異常通知
System.out.println("異常通知");
t.printStackTrace();
}finally {
//最終通知
System.out.println("最終通知");
}
return rtValue;
}
}
4.3 注解模式
在使用注解驅(qū)動開發(fā)aop時期虾,我們要明確的是,是注解替換掉配置文件中下面的這行配置
<!--開啟spring對注解aop的?持-->
<aop:aspectj-autoproxy/>
在配置類中使用如下注解進(jìn)行替換上述配置
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //開啟spring對注解AOP的?持
public class SpringConfiguration {
}
5. Spring 聲明型事務(wù)的支持
編程型事務(wù):在業(yè)務(wù)中添加事務(wù)控制代碼驯嘱,這樣的事務(wù)控制機制就叫做編程型事務(wù)镶苞。
聲明型事務(wù):通過xml或者注解配置的方式達(dá)到事務(wù)控制的目的,叫做聲明型事務(wù)鞠评。
5.1 事務(wù)的四大特性
- 原子性(Atomicity):原子性是指事務(wù)是一個不可分割的單位茂蚓,事務(wù)中的操作要么都發(fā)生,要么都不發(fā)生剃幌。
- 持久性(Durability):持久性是指一個事務(wù)一旦被提交聋涨,它對數(shù)據(jù)庫的數(shù)據(jù)的改變就是永久性的,接下來即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)對其由任何影響负乡。
- 一致性(Consistency):事務(wù)必須使數(shù)據(jù)庫從一致性狀態(tài)變換到另一個一致性狀態(tài)牍白。
- 隔離性(Isolation):事務(wù)的隔離性是多個用戶并發(fā)訪問數(shù)據(jù)庫時,數(shù)據(jù)庫為每一個用戶開啟的事務(wù)抖棘,每個事務(wù)不能被其他事務(wù)的操作數(shù)據(jù)所干擾茂腥,多個并發(fā)事務(wù)之間要相互隔離。
5.2 事務(wù)的隔離級別
不考慮隔離級別切省,會出現(xiàn)以下情況:(以下情況都是錯誤的)最岗,也即為隔離級別在解決事務(wù)并發(fā)問題
- 臟讀:一個線程中的事務(wù)讀到了另外一個線程中未提交的數(shù)據(jù)。
- 不可重復(fù)讀:一個線程中的事務(wù)讀到了另一個線程中已經(jīng)提交的update數(shù)據(jù)(前后內(nèi)容不一樣)
場景(不可重復(fù)讀):
員工A發(fā)起事務(wù)1朝捆,查詢工資般渡,工資為1w,此時事務(wù)1未關(guān)閉
財務(wù)人員發(fā)起事務(wù)2右蹦,給A漲了2000塊诊杆,并提交了事務(wù)。
此時A通過事務(wù)1再次發(fā)起查詢請求何陆,發(fā)現(xiàn)工資為1.2w晨汹,原來讀出來的1w已經(jīng)讀不到了,叫做不可重復(fù)讀贷盲。
- 幻讀:一個線程中的事務(wù)讀到了另外一個線程中已經(jīng)提交的insert或delete的數(shù)據(jù)(前后條數(shù)不一樣)
場景(幻讀)
事務(wù)1查詢所有工資為1w的員工的總數(shù)淘这,查詢出來10個人,此時事務(wù)未關(guān)閉巩剖。
事務(wù)2財務(wù)人員發(fā)起铝穷,新來員工,工資1w佳魔,向表中插入了2條數(shù)據(jù)曙聂,并且提交了事務(wù)。
事務(wù)1再次查詢工資1w的員工發(fā)現(xiàn)有12個人鞠鲜,叫做幻讀宁脊。
數(shù)據(jù)庫共定義了四種隔離級別:
- Serializable(串行化):可避免臟讀断国,不可重復(fù)讀,幻讀等情況發(fā)生榆苞。
- Repeatable read(可重復(fù)讀):可避免臟讀稳衬、不可重復(fù)讀等情況的發(fā)生(幻讀可能發(fā)生),該機制下會對要update的行進(jìn)行加鎖坐漏。
- Read committed(讀已提交):可避免臟讀情況發(fā)生薄疚,不可重復(fù)讀和幻讀一定會發(fā)生。
- Read uncommitted(讀未提交):最低級別赊琳,以上情況均無法保證街夭。
注意:級別依次升高,效率依次降低躏筏。
Mysql默認(rèn)隔離級別為 Repeatable read莱坎。
5.3 事務(wù)的傳播行為
事務(wù)往往由service層進(jìn)行控制,如果出現(xiàn)service層方法A調(diào)用另一個service方法B寸士,A和B方法本身被添加了事務(wù)控制,那么A調(diào)用B的時候碴卧,就需要進(jìn)行事物的一些協(xié)商弱卡,這就叫做事務(wù)的傳播行為。
A調(diào)用B住册,我們站在B的角度來定義事務(wù)的傳播行為婶博。
- propagation_required: 如果當(dāng)前沒有事務(wù),就新建?個事務(wù)荧飞,如果已經(jīng)存在?個事務(wù)中凡人,加?到這個事務(wù)中。這是最常?的選擇
第一點:如果serviceA沒有事務(wù)叹阔,serviceB有事務(wù)挠轴,serviceB拋異常,則serviceB回滾耳幢,serviceA不回滾岸晦。為什么呢,因為當(dāng)前沒有事務(wù)睛藻,則新建一個事務(wù)启上。新建的事務(wù)不受serviceB的影響。新建的事務(wù)和serviceB的事務(wù)相互獨立店印。
第二點:如果serviceA有事務(wù)冈在,serviceB有事務(wù),serviceB拋異常按摘,則serviceB回滾包券,serviceA回滾纫谅。為什么呢,因為當(dāng)前有事務(wù)兴使,則支持當(dāng)前事務(wù)系宜。serviceA的事務(wù)和serviceB的事務(wù)建立了聯(lián)系,不是相互獨立的发魄。
- propagation_supports:支持當(dāng)前事務(wù)盹牧,如當(dāng)前沒有就以非事務(wù)方式執(zhí)行。(有點類似不加事務(wù)注解)
- propagation_mandatory:使?當(dāng)前的事務(wù)励幼,如果當(dāng)前沒有事務(wù)汰寓,就拋出異常。
- propagation_requires_new: 新建事務(wù)苹粟,如果當(dāng)前存在事務(wù)有滑,把當(dāng)前事務(wù)掛起
- propagation_not_supported: 以?事務(wù)?式執(zhí)?操作,如果當(dāng)前存在事務(wù)嵌削,就把當(dāng)前事務(wù)掛起毛好。
- propagation_never: 以?事務(wù)?式執(zhí)?,如果當(dāng)前存在事務(wù)苛秕,則拋出異常肌访。
- propagation_nested: 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)?艇劫。如果當(dāng)前沒有事務(wù)吼驶,則執(zhí)?與PROPAGATION_REQUIRED類似的操作。
5.4 Spring中事務(wù)的API
mybatis:sqlSession.commit;
hibernate:session.commit;
PlatformTransactionManager
public interface PlatformTransactionManager extends TransactionManager {
//獲取事務(wù)狀態(tài)信息
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事務(wù)
void commit(TransactionStatus var1) throws TransactionException;
//回滾事務(wù)
void rollback(TransactionStatus var1) throws TransactionException;
}
作用
此接口是Spring的事務(wù)管理器核心接口店煞。Spring本身并不支持事務(wù)實現(xiàn)蟹演,只是提供標(biāo)準(zhǔn),應(yīng)用底層支持什么樣的事務(wù)顷蟀,需要提供具體實現(xiàn)類酒请。此處也是策略模式的具體應(yīng)用。
5.5 Spring聲明型事務(wù)配置
- 純xml模式
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--定制事務(wù)細(xì)節(jié)鸣个,傳播?為蚌父、隔離級別等-->
<tx:attributes>
<!--?般性配置
propagation 事務(wù)傳播行為
isolation="DEFAULT" 采用數(shù)據(jù)庫默認(rèn)隔離級別
-->
<tx:method name="*" read-only="false"
propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
<!--針對查詢的覆蓋性配置-->
<tx:method name="query*" read-only="true"
propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--advice-ref指向增強=橫切邏輯+?位-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(*
com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>
- 基于XML+注解
XML配置
<!--配置事務(wù)管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManage
r">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--開啟spring對注解事務(wù)的?持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在接?、類或者?法上添加@Transactional注解
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
- 基于純注解
Spring基于注解驅(qū)動開發(fā)的事務(wù)控制配置毛萌,只需要把xml配置部分改為注解實現(xiàn)苟弛。只是需要一個注解替換掉xml配置文件中的
<tx:annotation-driven transaction-manager="transactionManager"/>配置。
在Spring的配置類上添加@EnableTransactionManagement 注解即可阁将。
@EnableTransactionManagement//開啟spring注解事務(wù)的?持
public class SpringConfiguration {
}