目錄
- Spring 中的 AOP[掌握]
-
- 01,AOP相關術語
- 02,spring 的 AOP 要幹什麼事
- 03,關于代理的選擇
- 04,基于 XML 的 AOP 配置
- 05,AOP配置标簽詳解
-
- 5.1,aop:config标簽
- 5.2,aop:aspect标簽
- 5.3,aop:xxx 标簽
- 5.4,aop:pointcut 标簽
- 5.5 ,aop:around标簽(環繞通知)
- 06,案例示範(基于XML)
-
- 6.1,建立一個maven項目(spring)
- 6.2,編寫IAccountService接口和AccountServiceImpl.java實作類
- 6.3,編寫工具類Advice(通知類)
- 6.4,bean.xml
- 6.5, 編寫測試類AOPTest.java
- 6.6,運作結果
- 07,案例示範(基于注解)
- 08,AOP中使用基于注解和xml的注意事項
Spring 中的 AOP[掌握]
- 、 spring 的 aop就是通過配置的方式實作動态代理
01,AOP相關術語
-
Joinpoint(連接配接點):
所謂連接配接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因為 spring 隻支援方法類型的連接配接點。
通俗的說就是被代理類中的所有方法
-
Pointcut(切入點):
所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義
通俗的說就是被代理類中的被代理的方法,因為被代理類中并不是所有的方法都被代理了
-
Advice(通知/增強):
所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。
通俗的說就是對被代理的方法進行增強的代碼
通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知
前置通知:在被代理方法執行之前執行
後置通知:在被代理方法執行之後執行
異常通知:在被代理方法執行出錯的時候執行
最終通知:無論怎樣都會執行
注意:後置通知和異常通知隻能有一個會被執行,因為發生異常執行異常通知,然後就不會繼續向下執行,自然後置通知也就不會被執行,反之亦然。
-
Introduction(引介):
引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運作期為類動态地添加一些方法或Field。
-
Target(目标對象):
代理的目标對象
通俗的說就是被代理的對象
-
Weaving(織入):
是指把增強應用到目标對象來建立新的代理對象的過程。
spring 采用動态代理織入,而 AspectJ 采用編譯期織入和類裝載期織入。
通俗的說就是讓增強的代碼(通知)植入到待增強的方法(切入點)中
-
Proxy(代理) :
一個類被 AOP 織入增強後,就産生一個結果代理類。
-
Aspect(切面):
是切入點和通知(引介)的結合
通俗的說就是建立切入點和通知方法在建立時的對應關系
02,spring 的 AOP 要幹什麼事
-
開發階段(我們做的)
1,編寫核心業務代碼(前期),
2,把公用代碼抽取出來,制作成通知(後期)(把公共的代碼通過增強的的方式織入到方法中)
3,在配置檔案中,聲明切入點與通知間的關系,即切面(重點)
-
運作階段( Spring 架構完成的)
1,Spring 架構監控切入點方法的執行。一旦監控到切入點方法被運作,使用代理機制,動态建立目标對,象的代理對象,根據通知類别,在代理對象的對應位置,将通知對應的功能織入,完成完整的代碼邏輯運作。
03,關于代理的選擇
- 看過動态代理就知道動态代理分為兩種:基于接口的動态代理和基于子類的動态代理(cglib)
- 當代理類實作了某個接口的時候,使用基于接口的動态代理
- 當代理類沒有實作任何接口的時候,使用基于子類的動态代理(cglib)
04,基于 XML 的 AOP 配置
大緻步驟為:
1,導入依賴jar包或者坐标
2,建立 spring 的配置檔案(bean.xml)并導入限制(aop限制)
3,在配置檔案中配置 spring 的 ioc(IoC内容)
4,抽取公共代碼制作成通知(Transaction事務)
下面是aop配置
5,把通知類用 bean 标簽配置起來
6,使用 aop:config 聲明 aop 配置
7,使用 aop:aspect 配置切面
8,使用 aop:pointcut 配置切入點表達式
9,使用 aop:xxx 配置對應的通知類型
05,AOP配置标簽詳解
5.1,aop:config标簽
- aop:config标簽:表明開始AOP的配置
<aop:config>
<!-- 添加内容-->
</aop:config>
5.2,aop:aspect标簽
- aop:aspect标簽:表明配置切面
-
标簽位置:aop:config标簽内部
id屬性:是給切面提供一個唯一辨別(可随意指定,一般都隻指定有意義的名字)
ref屬性:是指定通知類bean的Id
<aop:config>
<!--配置切面 -->
<aop:aspect id=" " ref=" ">
</aop:aspect>
</aop:config>
5.3,aop:xxx 标簽
- 标簽位置:aop:aspect标簽内部
- 在aop:aspect标簽的内部使用對應标簽來配置通知的類型
- 在說以下通知類型:前置通知,後置通知。異常通知,最終通知
-
aop:before:表示配置前置通知
method屬性:用于指定通知類中哪個方法是前置通知
pointcut屬性:用于指定切入點表達式,該表達式的含義指的是對哪些方法進行增強
聯系起來就是:使用通知類中的那個方法,對待增強的方法進行增強,且增強的方法在待增強方法之前執行
-
切入點表達式的寫法:
關鍵字:execution(表達式)
表達式:通路修飾符 傳回值 包名.包名.包名…類名.方法名(參數清單)
标準的表達式寫法範例:
通路修飾符可以省略public void com.aismall.service.impl.AccountServiceImpl.saveAccount()
傳回值可以使用通配符*,表示任意傳回值void com.aismall.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有幾級包,就需要寫幾個*.* com.aismall.service.impl.AccountServiceImpl.saveAccount()
包名可以使用* *.*.*.*.AccountServiceImpl.saveAccount())
表示目前包及其子包..
類名和方法名都可以使用*來實作通配* *..AccountServiceImpl.saveAccount()
* *..*.*()
參數清單:
可以直接寫資料類型:
基本類型直接寫名稱 :int
引用類型寫包名.類名的方式 :java.lang.String
可以使用通配符表示任意類型,但是必須有參數
可以使用…表示有無參數均可,有參數可以是任意類型
全通配寫法:
實際開發中切入點表達式的通常寫法,切到業務層實作類下的所有方法:* *..*.*(..)
例如:* com.aismall.service.impl.*.*(..)
<aop:config>
<!--配置切面 -->
<aop:aspect id=" " ref=" ">
<aop:before method=" " pointcut="execution(* com.aismall.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
- aop:after-returning:表示配置後置通知
- aop:after-throwing:表示配置異常通知
- aop:after :表示配置最終通知
- 注意:這幾種通知裡面的寫法都是相同的,配置完之後通知出現的順序不同。
5.4,aop:pointcut 标簽
- 作用:結合
使用,簡化配置aop:XXX
-
位置:
可以再
标簽内部使用,與aop:aspect
aop:XXX
标簽同級,這樣就隻目前切面可用
也可以在
标簽内使用,與aop:config
标簽同級,這樣aop:aspect
aop:config
标簽内的所有切面都可以使用
例如:
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.aismall.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="advice" ref="Advice">
<!-- 配置前置通知:在切入點方法執行之前執行-->
<aop:before method="beforeAdvice" pointcut-ref="pt1" ></aop:before>
<!-- 配置後置通知:在切入點方法正常執行之後值。它和異常通知永遠隻能執行一個-->
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置異常通知:在切入點方法執行産生異常之後執行。它和後置通知永遠隻能執行一個-->
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最終通知:無論切入點方法是否正常執行它都會在其後面執行-->
<aop:after method="afterAdvice" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
5.5 ,aop:around标簽(環繞通知)
- 在spring的通知(Advice)中 一共有五種通知,之前已經介紹了四種,為什麼不把環繞通知和他們放在一起說,因為環繞通知可以把前面的四種通知都表示出來,而且環繞通知一般單獨使用
環繞通知的使用:
-
環繞通知使用會出現的問題?:
當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
-
分析:
通過對比動态代理中的環繞通知代碼,發現動态代理的環繞通知有明确的切入點方法調用,而我們的代碼中沒有。
-
解決:
Spring架構為我們提供了一個接口:
。該接口有一個方法ProceedingJoinPoint
proceed()
,此方法就相當于明确調用切入點方法。
該接口可以作為環繞通知的方法參數,在程式執行時,spring架構會為我們提供該接口的實作類供我們使用。
- 注意:增強代碼寫在調用
方法之前為前置通知,之後為後置通知,寫在catch中為異常通知,寫在finally中為最終通知proceed()
-
spring中的環繞通知:
它是spring架構為我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。
使用步驟:
1,先寫通知類中内容
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法執行所需的參數
System.out.println("通知類中的aroundAdvice方法執行了。。前置");
rtValue = pjp.proceed(args);//明确調用業務層方法(切入點方法)
System.out.println("通知類中的aroundAdvice方法執行了。。後置");
return rtValue;
}catch (Throwable t){
System.out.println("通知類中的aroundAdvice方法執行了。。異常");
throw new RuntimeException(t);
}finally {
System.out.println("通知類中的aroundAdvice方法執行了。。最終");
}
}
2,在配置中配置環繞通知
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.aismall.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="aroundAdvice" ref="Advice">
<!-- 配置環繞通知 詳細的注釋請看Logger類中-->
<aop:around method="aroundAdvice" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
06,案例示範(基于XML)
- 示範spring中的AOP,具體看看spring是如何通過配置來實作動态代理的
- 在業務層示範如何通過配置的方式将業務代碼進行增強
6.1,建立一個maven項目(spring)
- 導入坐标
<packaging>jar</packaging>
<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>
6.2,編寫IAccountService接口和AccountServiceImpl.java實作類
- IAccountService
/**
* 賬戶的業務層接口
*/
public interface IAccountService {
/**
* 模拟儲存賬戶
*/
void saveAccount();
}
- AccountServiceImpl.java
/**
* 賬戶的業務層實作類
*/
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("執行了儲存");
// int i=1/0;
}
}
6.3,編寫工具類Advice(通知類)
/**
* 通知類,它裡面提供了公共的代碼
*/
public class Advice {
/**
* 前置通知
*/
public void beforeAdvice(){
System.out.println("前置通知Advice類中的beforeAdvice方法執行了。。。");
}
/**
* 後置通知
*/
public void afterReturningAdvice(){
System.out.println("後置通知Advice類中的afterReturningAdvice方法開執行了。。。");
}
/**
* 異常通知
*/
public void afterThrowingAdvice(){
System.out.println("異常通知Advice類中的afterThrowingAdvice方法執行了。。。");
}
/**
* 最終通知
*/
public void afterAdvice(){
System.out.println("最終通知Advice類中的afterAdvice方法執行了。。。");
}
/**
* 環繞通知
*/
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法執行所需的參數
System.out.println("通知類中的aroundAdvice方法執行了。。前置");
rtValue = pjp.proceed(args);//明确調用業務層方法(切入點方法)
System.out.println("通知類中的aroundAdvice方法執行了。。後置");
return rtValue;
}catch (Throwable t){
System.out.println("通知類中的aroundAdvice方法執行了。。異常");
throw new RuntimeException(t);
}finally {
System.out.println("通知類中的aroundAdvice方法執行了。。最終");
}
}
}
6.4,bean.xml
- 注意:此處要導入AOP的限制
<?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.aismall.service.impl.AccountServiceImpl"></bean>
<!-- 配置advice通知類 -->
<bean id="advice" class="com.aismall.utils.Advice"></bean>
<!--配置AOP-->
<aop:config>
<!--為了使所有切面都可以使用,此标簽配置在外面-->
<aop:pointcut id="pt1" expression="execution(* com.aismall.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="advice" ref="advice">
<!-- 配置前置通知:在切入點方法執行之前執行-->
<aop:before method="beforeAdvice" pointcut-ref="pt1"></aop:before>
<!-- 配置後置通知:在切入點方法正常執行之後值。它和異常通知永遠隻能執行一個-->
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置異常通知:在切入點方法執行産生異常之後執行。它和後置通知永遠隻能執行一個-->
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最終通知:無論切入點方法是否正常執行它都會在其後面執行-->
<aop:after method="afterAdvice" pointcut-ref="pt1"></aop:after>
<!-- 配置環繞通知-->
<!--<aop:around method="aroundAdvice" pointcut-ref="pt1"></aop:around>-->
</aop:aspect>
</aop:config>
</beans>
6.5, 編寫測試類AOPTest.java
/**
* 測試AOP的配置
*/
public class AOPTest {
public static void main(String[] args) {
//1.擷取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.擷取對象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.執行方法
as.saveAccount();
}
}
6.6,運作結果
前置通知Advice類中的beforeAdvice方法執行了。。。
執行了儲存
後置通知Advice類中的afterReturningAdvice方法開執行了。。。
最終通知Advice類中的afterAdvice方法執行了。。。
07,案例示範(基于注解)
- 案例的總體實作功能和上面那個案例是一樣的,隻不過是使用注解的方式
- 建立工程後需要導入的坐标,測試方法AOPTest.java和IAccount接口都和之前的一樣。
- 在AccountServiceImpl.java中添加注解:
@Service("accountService")
/**
* 賬戶的業務層實作類
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("執行了儲存");
//int i=1/0;
}
}
- bean.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.aismall"></context:component-scan>
<!-- 配置spring開啟注解AOP的支援 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 通知類使用注解進行配置(重點)
/**
* 通知類,它裡面提供了公共的代碼
*/
@Component("advice")
@Aspect//表示目前類是一個切面類
public class Advice {
@Pointcut("execution(* com.aismall.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Advice類中的beforeAdvice方法執行了。。。");
}
/**
* 後置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("後置通知Advice類中的afterReturningAdvice方法開執行了。。。");
}
/**
* 異常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("異常通知Advice類中的afterThrowingAdvice方法執行了。。。");
}
/**
* 最終通知
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最終通知Advice類中的afterAdvice方法執行了。。。");
}
//@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法執行所需的參數
System.out.println("通知類中的aroundAdvice方法執行了。。前置");
rtValue = pjp.proceed(args);//明确調用業務層方法(切入點方法)
System.out.println("通知類中的aroundAdvice方法執行了。。後置");
return rtValue;
}catch (Throwable t){
System.out.println("通知類中的aroundAdvice方法執行了。。異常");
throw new RuntimeException(t);
}finally {
System.out.println("通知類中的aroundAdvice方法執行了。。最終");
}
}
}
08,AOP中使用基于注解和xml的注意事項
- AOP中使用注解進行配置時選用:環繞通知
- 原因:使用(前置通知,後置通知,異常通知,最終通知)時,執行順序有問題,先執行前置通知,然後執行最終通知,再執行後置通知或者異常通知
- AOP中使用XML進行配置時選用環繞通知或者使用這一組通知(前置通知,後置通知,異常通知,最終通知)都可以,優先使用(前置通知,後置通知,異常通知,最終通知),因為簡單