基于動(dòng)態(tài)代理改造上限案例
掌握Spring AOP 基于配置文件方式
掌握Spring AOP 基于注解方式
重點(diǎn)掌握:Spring AOP 基于XML和注解方式
全稱是Aspect Oriented Programming即:面向切面編程
1.1.2 AOP的作用及優(yōu)勢(shì)
作用:
在程序運(yùn)行期間七芭,不修改源碼對(duì)已有方法進(jìn)行增強(qiáng)。
優(yōu)勢(shì):
減少重復(fù)代碼
提高開發(fā)效率
維護(hù)方便
我們可以想辦法讓上面4個(gè)鏈接對(duì)象合并成一個(gè)鏈接對(duì)象,然后類似JDBC代碼一樣袖牙,手動(dòng)控制事務(wù)酒觅,實(shí)現(xiàn)讓業(yè)務(wù)層來控制事務(wù)的提交和回滾悯恍。
創(chuàng)建ConnectionUtil,用于實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接管理
public class ConnectionUtil {
? ? //線程本地變量
? ? private? ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
? ? private DataSource dataSource;
? ? //注入DataSource
? ? public void setDataSource(DataSource dataSource) {
? ? ? ? this.dataSource = dataSource;
? ? }
? ? /**
? ? * 獲取當(dāng)前線程上綁定的連接
? ? * @return
? ? */
? ? public Connection getThreadConnection() {
? ? ? ? try {
? ? ? ? ? ? //1.先看看線程上是否綁了
? ? ? ? ? ? Connection conn = tl.get();
? ? ? ? ? ? if(conn == null) {
? ? ? ? ? ? ? ? //2.從數(shù)據(jù)源中獲取一個(gè)連接
? ? ? ? ? ? ? ? conn = dataSource.getConnection();
? ? ? ? ? ? ? ? //3.和線程局部變量綁定
? ? ? ? ? ? ? ? tl.set(conn);
? ? ? ? ? ? }
? ? ? ? ? ? //4.返回線程上的連接
? ? ? ? ? ? return tl.get();
? ? ? ? } catch (SQLException e) {
? ? ? ? ? ? throw new RuntimeException(e);
? ? ? ? }
? ? }
? ? /**
? ? * 把連接和當(dāng)前線程解綁
? ? */
? ? public void remove() {
? ? ? ? tl.remove();
? ? }
}
創(chuàng)建TransactionManager奶段,實(shí)現(xiàn)事務(wù)的管理控制
public class TransactionManager {
? ? private ConnectionUtil connectionUtil;
? ? //數(shù)據(jù)庫(kù)連接管理注入
? ? public void setConnectionUtil(ConnectionUtil connectionUtil) {
? ? ? ? this.connectionUtil = connectionUtil;
? ? }
? ? //開啟事務(wù)
? ? public void beginTransaction() {
? ? ? ? //從當(dāng)前線程上獲取連接饥瓷,實(shí)現(xiàn)開啟事務(wù)
? ? ? ? try {
? ? ? ? ? ? connectionUtil.getThreadConnection().setAutoCommit(false);
? ? ? ? } catch (SQLException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? //提交事務(wù)
? ? public void commit() {
? ? ? ? try {
? ? ? ? ? ? connectionUtil.getThreadConnection().commit();
? ? ? ? } catch (SQLException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? //回滾事務(wù)
? ? public void rollback() {
? ? ? ? try {
? ? ? ? ? ? connectionUtil.getThreadConnection().rollback();
? ? ? ? } catch (SQLException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? //釋放連接
? ? public void release() {
? ? ? ? try {
? ? ? ? ? ? //關(guān)閉連接(還回池中)
? ? ? ? ? ? connectionUtil.getThreadConnection().close();
? ? ? ? ? ? //解綁線程:把連接和線程解綁
? ? ? ? ? ? connectionUtil.remove();
? ? ? ? } catch (SQLException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
public class AccountDaoImpl implements AccountDao {
? ? //注入進(jìn)來
? ? private QueryRunner runner;
? ? //注入數(shù)據(jù)庫(kù)連接對(duì)象
? ? private ConnectionUtil connectionUtil;
? ? //提供注入
? ? public void setRunner(QueryRunner runner) {
? ? ? ? this.runner = runner;
? ? }
? ? public void setConnectionUtil(ConnectionUtil connectionUtil) {
? ? ? ? this.connectionUtil = connectionUtil;
? ? }
? ? /**
? ? * 修改操作
? ? * @param account:賬號(hào)數(shù)據(jù)
? ? */
? ? @Override
? ? public void update(Account account) {
? ? ? ? try {
? ? ? ? ? ? runner.update(connectionUtil.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
? ? ? ? } catch (SQLException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? /***
? ? * 根據(jù)名字查找賬號(hào)信息
? ? * @param numberName:賬號(hào)名字
? ? * @return
? ? */
? ? @Override
? ? public Account findByName(String numberName) {
? ? ? ? try {
? ? ? ? ? ? return runner.query(connectionUtil.getThreadConnection(),"select * from account where name=?",new BeanHandler<Account>(Account.class),numberName);
? ? ? ? } catch (SQLException e) {
? ? ? ? ? ? throw new RuntimeException(e);
? ? ? ? }
? ? }
}
public class AccountServiceImpl implements AccountService {
? ? //注入AccountDao
? ? private AccountDao accountDao;
? ? //注入事務(wù)管理器
? ? private TransactionManager txManager;
? ? //提供注入
? ? public void setAccountDao(AccountDao accountDao) {
? ? ? ? this.accountDao = accountDao;
? ? }
? ? public void setTxManager(TransactionManager txManager) {
? ? ? ? this.txManager = txManager;
? ? }
? ? /***
? ? * 轉(zhuǎn)賬操作
? ? * @param sourceName:轉(zhuǎn)出賬戶名
? ? * @param targetName:轉(zhuǎn)入賬戶名
? ? * @param money:轉(zhuǎn)賬金額
? ? */
? ? @Override
? ? public void transfer(String sourceName, String targetName, Float money) {
? ? ? ? try {
? ? ? ? ? ? //開啟事務(wù)
? ? ? ? ? ? txManager.beginTransaction();
? ? ? ? ? ? //根據(jù)名稱查詢兩個(gè)賬戶信息
? ? ? ? ? ? Account source = accountDao.findByName(sourceName);
? ? ? ? ? ? Account target = accountDao.findByName(targetName);
? ? ? ? ? ? //轉(zhuǎn)出賬戶減錢,轉(zhuǎn)入賬戶加錢
? ? ? ? ? ? source.setMoney(source.getMoney()-money);
? ? ? ? ? ? target.setMoney(target.getMoney()+money);
? ? ? ? ? ? //更新兩個(gè)賬戶
? ? ? ? ? ? accountDao.update(source);
? ? ? ? ? ? accountDao.update(target);
? ? ? ? ? ? //提交事務(wù)
? ? ? ? ? ? txManager.commit();
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? //事務(wù)回滾
? ? ? ? ? ? txManager.rollback();
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }finally {
? ? ? ? ? ? //關(guān)閉資源
? ? ? ? ? ? txManager.release();
? ? ? ? }
? ? }
}
<?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">
? ? <!--
? ? ? ? Dao
? ? -->
? ? <bean id="accountDao" class="com.oppo.dao.impl.AccountDaoImpl">
? ? ? ? <!--注入QueryRunner對(duì)象? 必須有set方法-->
? ? ? ? <property name="runner" ref="runner" />
? ? ? ? <!--注入數(shù)據(jù)庫(kù)連接對(duì)象-->
? ? ? ? <property name="connectionUtil" ref="connectionUtil" />
? ? </bean>
? ? <!--
? ? ? ? Service
? ? -->
? ? <bean id="accountService" class="com.oppo.service.impl.AccountServiceImpl">
? ? ? ? <!--注入dao? 必須有set方法-->
? ? ? ? <property name="accountDao" ref="accountDao" />
? ? ? ? <!--注入事務(wù)管理器-->
? ? ? ? <property name="txManager" ref="txManager" />
? ? </bean>
? ? <!--
? ? ? ? QueryRunner對(duì)象
? ? -->
? ? <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
? ? ? ? <!--帶參構(gòu)造函數(shù)注入-->
? ? ? ? <constructor-arg name="ds" ref="dataSource" />
? ? </bean>
? ? <!--
? ? ? ? 創(chuàng)建DataSource
? ? -->
? ? <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
? ? ? ? <property name="driverClass" value="com.mysql.jdbc.Driver" />
? ? ? ? <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/spring5" />
? ? ? ? <property name="user" value="root" />
? ? ? ? <property name="password" value="123456" />
? ? </bean>
? ? <!--數(shù)據(jù)庫(kù)連接對(duì)象-->
? ? <bean id="connectionUtil" class="com.oppo.util.ConnectionUtil">
? ? ? ? <property name="dataSource" ref="dataSource" />
? ? </bean>
? ? <!--事務(wù)管理器-->
? ? <bean id="txManager" class="com.oppo.util.TransactionManager">
? ? ? ? <property name="connectionUtil" ref="connectionUtil" />
? ? </bean>
</beans>
上一小節(jié)的代碼痹籍,通過對(duì)業(yè)務(wù)層改造呢铆,已經(jīng)可以實(shí)現(xiàn)事務(wù)控制了,但是由于我們添加了事務(wù)控制蹲缠,也產(chǎn)生了一個(gè)新的問題:
業(yè)務(wù)層方法變得臃腫了棺克,里面充斥著很多重復(fù)代碼。并且業(yè)務(wù)層方法和事務(wù)控制方法耦合了吼砂。
試想一下逆航,如果我們此時(shí)提交,回滾渔肩,釋放資源中任何一個(gè)方法名變更因俐,都需要修改業(yè)務(wù)層的代碼,況且這還只是一個(gè)業(yè)務(wù)層實(shí)現(xiàn)類周偎,而實(shí)際的項(xiàng)目中這種業(yè)務(wù)層實(shí)現(xiàn)類可能有十幾個(gè)甚至幾十個(gè)抹剩。
?動(dòng)態(tài)代理回顧
1.2.4.1 動(dòng)態(tài)代理的特點(diǎn)
字節(jié)碼隨用隨創(chuàng)建,隨用隨加載蓉坎。
它與靜態(tài)代理的區(qū)別也在于此澳眷。因?yàn)殪o態(tài)代理是字節(jié)碼一上來就創(chuàng)建好,并完成加載蛉艾。
裝飾者模式就是靜態(tài)代理的一種體現(xiàn)钳踊。
動(dòng)態(tài)代理常用的有兩種方式
基于接口的動(dòng)態(tài)代理? ? 提供者:JDK官方的Proxy類。? ? 要求:被代理類最少實(shí)現(xiàn)一個(gè)接口勿侯⊥氐桑基于子類的動(dòng)態(tài)代理? ? 提供者:第三方的CGLib,如果報(bào)asmxxxx異常助琐,需要導(dǎo)入asm.jar祭埂。? ? 要求:被代理類不能用final修飾的類(最終類)。
cglibcglib2.1_3
AOP相關(guān)術(shù)語
Joinpoint(連接點(diǎn)):
所謂連接點(diǎn)是指那些被攔截到的點(diǎn)兵钮。在spring中,這些點(diǎn)指的是方法,因?yàn)閟pring只支持方法類型的連接點(diǎn)蛆橡。
Pointcut(切入點(diǎn)):
所謂切入點(diǎn)是指我們要對(duì)哪些Joinpoint進(jìn)行攔截的定義。
Advice(通知/增強(qiáng)):
所謂通知是指攔截到Joinpoint之后所要做的事情就是通知掘譬。
通知的類型:前置通知,后置通知,異常通知,最終通知,環(huán)繞通知泰演。
Introduction(引介):
引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運(yùn)行期為類動(dòng)態(tài)地添加一些方法或Field。
Target(目標(biāo)對(duì)象):
代理的目標(biāo)對(duì)象屁药。
Weaving(織入):
是指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來創(chuàng)建新的代理對(duì)象的過程粥血。
spring采用動(dòng)態(tài)代理織入柏锄,而AspectJ采用編譯期織入和類裝載期織入酿箭。
Proxy(代理):
一個(gè)類被AOP織入增強(qiáng)后复亏,就產(chǎn)生一個(gè)結(jié)果代理類。
Aspect(切面):
是切入點(diǎn)和通知(引介)的結(jié)合缭嫡。
a缔御、開發(fā)階段(我們做的)
編寫核心業(yè)務(wù)代碼(開發(fā)主線):大部分程序員來做,要求熟悉業(yè)務(wù)需求妇蛀。
把公用代碼抽取出來耕突,制作成通知。(開發(fā)階段最后再做):AOP編程人員來做评架。
在配置文件中眷茁,聲明切入點(diǎn)與通知間的關(guān)系,即切面纵诞。:AOP編程人員來做上祈。
b、運(yùn)行階段(Spring框架完成的)
Spring框架監(jiān)控切入點(diǎn)方法的執(zhí)行浙芙。一旦監(jiān)控到切入點(diǎn)方法被運(yùn)行登刺,使用代理機(jī)制,動(dòng)態(tài)創(chuàng)建目標(biāo)對(duì)象的代理對(duì)象嗡呼,根據(jù)通知類別纸俭,在代理對(duì)象的對(duì)應(yīng)位置,將通知對(duì)應(yīng)的功能織入南窗,完成完整的代碼邏輯運(yùn)行揍很。
在spring中,框架會(huì)根據(jù)目標(biāo)類是否實(shí)現(xiàn)了接口來決定采用哪種動(dòng)態(tài)代理的方式万伤。
示例:我們?cè)趯W(xué)習(xí)spring的aop時(shí)窒悔,采用輸出日志作為示例。在業(yè)務(wù)層方法執(zhí)行的前后壕翩,加入日志的輸出蛉迹。并且把spring的ioc也一起應(yīng)用進(jìn)來。
<dependencies>
? <dependency>
? <groupId>org.springframework</groupId>
? <artifactId>spring-context</artifactId>
? <version>5.0.2.RELEASE</version>
? </dependency>
? <dependency>
? <groupId>org.aspectj</groupId>
? <artifactId>aspectjweaver</artifactId>
? <version>1.8.7</version>
? </dependency>
? </dependencies>
<!--
aop的配置步驟:
第一步:把通知類的創(chuàng)建也交給spring來管理
第二步:使用aop:config標(biāo)簽開始aop的配置
第三步:使用aop:aspect標(biāo)簽開始配置切面放妈,寫在aop:config標(biāo)簽內(nèi)部
id屬性:給切面提供一個(gè)唯一標(biāo)識(shí)
ref屬性:用于引用通知bean的id北救。
第四步:使用對(duì)應(yīng)的標(biāo)簽在aop:aspect標(biāo)簽內(nèi)部配置通知的類型
使用aop:befored標(biāo)簽配置前置通知,寫在aop:aspect標(biāo)簽內(nèi)部
method屬性:用于指定通知類中哪個(gè)方法是前置通知
pointcut屬性:用于指定切入點(diǎn)表達(dá)式芜抒。
切入點(diǎn)表達(dá)式寫法:
關(guān)鍵字:execution(表達(dá)式)
表達(dá)式內(nèi)容:
全匹配標(biāo)準(zhǔn)寫法:
訪問修飾符? 返回值? 包名.包名.包名...類名.方法名(參數(shù)列表)
例如:
public void com.oppo.service.impl.AccountServiceImpl.saveAccount()
-->
<!-- 配置通知類 -->
<bean id="logger" class="com.oppo.utils.Logger"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知 -->
<aop:before method="printLog" pointcut="execution( * com.oppo.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
<!-- 配置通知類 -->
<bean id="logger" class="com.oppo.utils.Logger"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知 -->
<aop:before method="printLog" pointcut="execution( * com.oppo.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
execution:匹配方法的執(zhí)行(常用)
execution(表達(dá)式)
表達(dá)式語法:execution([修飾符] 返回值類型 包名.類名.方法名(參數(shù)))
寫法說明:
全匹配方式:
public void com.oppo.service.impl.AccountServiceImpl.saveAccount(com.oppo.domain.Account)
訪問修飾符可以省略
void com.oppo.service.impl.AccountServiceImpl.saveAccount(com.oppo.domain.Account)
返回值可以使用*號(hào)珍策,表示任意返回值
* com.oppo.service.impl.AccountServiceImpl.saveAccount(com.oppo.domain.Account)
包名可以使用*號(hào),表示任意包宅倒,但是有幾級(jí)包攘宙,需要寫幾個(gè)*
* *.*.*.*.AccountServiceImpl.saveAccount(com.oppo.domain.Account)
使用..來表示當(dāng)前包,及其子包
* com..AccountServiceImpl.saveAccount(com.oppo.domain.Account)
類名可以使用*號(hào),表示任意類
* com..*.saveAccount(com.oppo.domain.Account)
方法名可以使用*號(hào)蹭劈,表示任意方法
* com..*.*( com.oppo.domain.Account)
參數(shù)列表可以使用*疗绣,表示參數(shù)可以是任意數(shù)據(jù)類型,但是必須有參數(shù)
* com..*.*(*)
參數(shù)列表可以使用..表示有無參數(shù)均可铺韧,有參數(shù)可以是任意類型
* com..*.*(..)
全通配方式:
* *..*.*(..)
注:
通常情況下多矮,我們都是對(duì)業(yè)務(wù)層的方法進(jìn)行增強(qiáng),所以切入點(diǎn)表達(dá)式都是切到業(yè)務(wù)層實(shí)現(xiàn)類哈打。
execution(* com.oppo.service.impl.*.*(..))
aop:config:
? ? 作用:用于聲明開始aop的配置
<aop:config>
? ? <!-- 配置的代碼都寫在此處 -->? ?
</aop:config>
aop:aspect:
? ? 作用:
? ? ? 用于配置切面塔逃。
? ? 屬性:
? ? ? id:給切面提供一個(gè)唯一標(biāo)識(shí)。
? ? ? ref:引用配置好的通知類bean的id料仗。
<aop:aspect id="logAdvice" ref="logger">
? ? ? <!--配置通知的類型要寫在此處-->
</aop:aspect>
aop:aspect:
? ? 作用:
? ? ? 用于配置切面湾盗。
? ? 屬性:
? ? ? id:給切面提供一個(gè)唯一標(biāo)識(shí)。
? ? ? ref:引用配置好的通知類bean的id立轧。
<aop:aspect id="logAdvice" ref="logger">
? ? ? <!--配置通知的類型要寫在此處-->
</aop:aspect>
aop:pointcut:
作用:
用于配置切入點(diǎn)表達(dá)式格粪。就是指定對(duì)哪些類的哪些方法進(jìn)行增強(qiáng)。
屬性:
expression:用于定義切入點(diǎn)表達(dá)式肺孵。
id:用于給切入點(diǎn)表達(dá)式提供一個(gè)唯一標(biāo)識(shí)
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
aop:before
作用:
用于配置前置通知匀借。指定增強(qiáng)的方法在切入點(diǎn)方法之前執(zhí)行
屬性:
method:用于指定通知類中的增強(qiáng)方法名稱
ponitcut-ref:用于指定切入點(diǎn)的表達(dá)式的引用
poinitcut:用于指定切入點(diǎn)表達(dá)式
執(zhí)行時(shí)間點(diǎn):
切入點(diǎn)方法執(zhí)行之前執(zhí)行
<aop:before method="beginPrintLog" pointcut-ref="pt1"/>
aop:after-returning
作用:
用于配置后置通知
屬性:
method:指定通知中方法的名稱。
pointct:定義切入點(diǎn)表達(dá)式
pointcut-ref:指定切入點(diǎn)表達(dá)式的引用
執(zhí)行時(shí)間點(diǎn):
切入點(diǎn)方法正常執(zhí)行之后平窘。它和異常通知只能有一個(gè)執(zhí)行
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
aop:after-throwing
作用:
用于配置異常通知
屬性:
method:指定通知中方法的名稱吓肋。
pointct:定義切入點(diǎn)表達(dá)式
pointcut-ref:指定切入點(diǎn)表達(dá)式的引用
執(zhí)行時(shí)間點(diǎn):
切入點(diǎn)方法執(zhí)行產(chǎn)生異常后執(zhí)行。它和后置通知只能執(zhí)行一個(gè)
<aop:after-throwing method="afterThrowingPringLog" pointcut-ref="pt1"/>
aop:after
作用:
用于配置最終通知
屬性:
method:指定通知中方法的名稱瑰艘。
pointct:定義切入點(diǎn)表達(dá)式
pointcut-ref:指定切入點(diǎn)表達(dá)式的引用
執(zhí)行時(shí)間點(diǎn):
無論切入點(diǎn)方法執(zhí)行時(shí)是否有異常是鬼,它都會(huì)在其后面執(zhí)行。
<aop:after method="afterPringLog" pointcut-ref="pt1"/>
環(huán)繞通知
配置方式:
<aop:config>
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置環(huán)繞通知 -->
<aop:around method="transactionAround" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
aop:around:
作用:
用于配置環(huán)繞通知
屬性:
method:指定通知中方法的名稱紫新。
pointct:定義切入點(diǎn)表達(dá)式
pointcut-ref:指定切入點(diǎn)表達(dá)式的引用
說明:
它是spring框架為我們提供的一種可以在代碼中手動(dòng)控制增強(qiáng)代碼什么時(shí)候執(zhí)行的方式均蜜。
注意:
通常情況下,環(huán)繞通知都是獨(dú)立使用的
/**
* 環(huán)繞通知
* 問題:
* 當(dāng)配置完環(huán)繞通知之后芒率,沒有業(yè)務(wù)層方法執(zhí)行(切入點(diǎn)方法執(zhí)行)
* 分析:
*? 通過動(dòng)態(tài)代理的代碼分析囤耳,我們現(xiàn)在的環(huán)繞通知沒有明確的切入點(diǎn)方法調(diào)用
* 解決:
* spring框架為我們提供了一個(gè)接口,該接口可以作為環(huán)繞通知的方法參數(shù)來使用
* ProceedingJoinPoint偶芍。當(dāng)環(huán)繞通知執(zhí)行時(shí)充择,spring框架會(huì)為我們注入該接口的實(shí)現(xiàn)類。
*? 它有一個(gè)方法proceed()匪蟀,就相當(dāng)于invoke椎麦,明確的業(yè)務(wù)層方法調(diào)用
*?
*? spring的環(huán)繞通知:
*? 它是spring為我們提供的一種可以在代碼中手動(dòng)控制增強(qiáng)方法何時(shí)執(zhí)行的方式。
*/
public void aroundPrintLog(ProceedingJoinPoint pjp) {
try {
System.out.println("前置Logger類中的aroundPrintLog方法開始記錄日志了");
pjp.proceed();//明確的方法調(diào)用
System.out.println("后置Logger類中的aroundPrintLog方法開始記錄日志了");
} catch (Throwable e) {
System.out.println("異常Logger類中的aroundPrintLog方法開始記錄日志了");
e.printStackTrace();
}finally {
System.out.println("最終Logger類中的aroundPrintLog方法開始記錄日志了");
}
}
基于注解的AOP配置
把通知類也使用注解配置
/**
* 模擬一個(gè)用于記錄日志的工具類
*/
@Component("logger")
public class Logger {
}
作用:
把當(dāng)前類聲明為切面類材彪。
/**
* 模擬一個(gè)用于記錄日志的工具類
*/
@Component("logger")
@Aspect//表示當(dāng)前類是一個(gè)切面類
public class Logger {}
使用注解配置通知類型
@Before
作用:
把當(dāng)前方法看成是前置通知观挎。
屬性:
value:用于指定切入點(diǎn)表達(dá)式琴儿,還可以指定切入點(diǎn)表達(dá)式的引用。
/**
* 前置通知
*/
@Before("execution(* com.oppo.service.impl.*.*(..))")
public void beforePrintLog() {
System.out.println("前置通知:Logger類中的beforePrintLog方法開始記錄日志了嘁捷。造成。。");
}
@AfterReturning
作用:
把當(dāng)前方法看成是后置通知普气。
屬性:
value:用于指定切入點(diǎn)表達(dá)式谜疤,還可以指定切入點(diǎn)表達(dá)式的引用
/**
* 后置通知
*/
@AfterReturning("execution(* com.oppo.service.impl.*.*(..))")
public void afterReturningPrintLog() {
System.out.println("后置通知:Logger類中的afterReturningPrintLog方法開始記錄日志了佃延。现诀。。");
}
@AfterThrowing
作用:
把當(dāng)前方法看成是異常通知履肃。
屬性:
value:用于指定切入點(diǎn)表達(dá)式仔沿,還可以指定切入點(diǎn)表達(dá)式的引用
/**
* 異常通知
*/
@AfterThrowing("execution(* com.oppo.service.impl.*.*(..))")
public void afterThrowingPrintLog() {
System.out.println("異常通知:Logger類中的afterThrowingPrintLog方法開始記錄日志了。尺棋。封锉。");
}
@After
作用:
把當(dāng)前方法看成是最終通知。
屬性:
value:用于指定切入點(diǎn)表達(dá)式膘螟,還可以指定切入點(diǎn)表達(dá)式的引用
/**
* 最終通知
*/
@After("execution(* com.oppo.service.impl.*.*(..))")
public void afterPrintLog() {
System.out.println("最終通知:Logger類中的afterPrintLog方法開始記錄日志了成福。。荆残。");
}
2.3.7 第四步:在spring配置文件中開啟spring對(duì)注解AOP的支持
<!-- 開啟spring對(duì)注解AOP的支持 -->
<aop:aspectj-autoproxy/>
2.3.8 環(huán)繞通知注解配置
@Around
作用:
把當(dāng)前方法看成是環(huán)繞通知奴艾。
屬性:
value:用于指定切入點(diǎn)表達(dá)式,還可以指定切入點(diǎn)表達(dá)式的引用内斯。
/**
* 環(huán)繞通知
* 問題:
* 當(dāng)配置完環(huán)繞通知之后蕴潦,沒有業(yè)務(wù)層方法執(zhí)行(切入點(diǎn)方法執(zhí)行)
* 分析:
*? 通過動(dòng)態(tài)代理的代碼分析,我們現(xiàn)在的環(huán)繞通知沒有明確的切入點(diǎn)方法調(diào)用
* 解決:
* spring框架為我們提供了一個(gè)接口俘闯,該接口可以作為環(huán)繞通知的方法參數(shù)來使用
* ProceedingJoinPoint潭苞。當(dāng)環(huán)繞通知執(zhí)行時(shí),spring框架會(huì)為我們注入該接口的實(shí)現(xiàn)類真朗。
*? 它有一個(gè)方法proceed()此疹,就相當(dāng)于invoke,明確的業(yè)務(wù)層方法調(diào)用
*?
*? spring的環(huán)繞通知:
*? 它是spring為我們提供的一種可以在代碼中手動(dòng)控制增強(qiáng)方法何時(shí)執(zhí)行的方式遮婶。
*/
@Around("execution(* com.oppo.service.impl.*.*(..))")
public void aroundPrintLog(ProceedingJoinPoint pjp) {
try {
System.out.println("前置Logger類中的aroundPrintLog方法開始記錄日志了");
pjp.proceed();//明確的方法調(diào)用
System.out.println("后置Logger類中的aroundPrintLog方法開始記錄日志了");
} catch (Throwable e) {
System.out.println("異常Logger類中的aroundPrintLog方法開始記錄日志了");
e.printStackTrace();
}finally {
System.out.println("最終Logger類中的aroundPrintLog方法開始記錄日志了");
}
}
2.3.9 切入點(diǎn)表達(dá)式注解
@Pointcut
作用:
指定切入點(diǎn)表達(dá)式
屬性:
value:指定表達(dá)式的內(nèi)容
@Pointcut("execution(* com.oppo.service.impl.*.*(..))")
private void pt1() {}
引用方式:
/**
* 環(huán)繞通知
* @param pjp
* @return
*/
@Around("pt1()")//注意:千萬別忘了寫括號(hào)
public void aroundPrintLog(ProceedingJoinPoint pjp) {
}
2.3.10 不使用XML的配置方式
@Configuration
@ComponentScan(basePackages="com.oppo")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}