寫在前面: 最近學習Spring告一段落了,這文章可以簡單的告訴你什麼是AOP,AOP相關術語,AOP的xml配置,AOP註解開發 。 作者是一個學生,沒有能力寫得太深,需要的可以去看看大佬們的手撕Spring源碼。
如果對你有幫助可以點贊支持一下^ _ ^
公衆號:小白編碼
目錄
什麼是AOP
AOP:全稱是Aspect Oriented Programming即:面向切面編程。
簡單的說它就是把我們程序重複的代碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改源碼的基礎上,對我們的已有方法進行增強。
作用:在程序運行期間,不修改源碼對已有方法進行增強。
優勢:減少重複代碼、提高開發效率、維護方便
實現方式:使用動態代理技術
AOP相關術語
Joinpoint(連接點): 所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因爲spring只支持方法類型的連接點。
Pointcut(切入點): 所謂切入點是指我們要對哪些Joinpoint進行攔截的定義。
Advice(通知/增強): 所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。 通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知。
Introduction(引介): 引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運行期爲類動態地添加一些方法或Field。
Target(目標對象): 代理的目標對象。
Weaving(織入): 是指把增強應用到目標對象來創建新的代理對象的過程。 spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。
Proxy(代理): 一個類被AOP織入增強後,就產生一個結果代理類。
Aspect(切面): 是切入點和通知(引介)的結合。
AOP明確的事
a、開發階段(我們做的)
編寫核心業務代碼(開發主線)
把公用代碼抽取出來,製作成通知。(開發階段最後再做)
在配置文件中,聲明切入點與通知間的關係,即切面。
b、運行階段(Spring框架完成的)
Spring框架監控切入點方法的執行。一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的代碼邏輯運行。
XML的AOP配置
1、把通知Bean也交給spring來管理
2、使用aop:config
標籤表明開始AOP的配置
3、使用aop:aspect
標籤表明配置切面
id
屬性:是給切面提供一個唯一標識
ref
屬性:是指定通知類bean的Id。
4、在aop:aspect
標籤的內部使用對應標籤來配置通知的類型
aop:before
:表示配置前置通知
method
屬性:用於指定Logger類中哪個方法是前置通知
pointcut
屬性:用於指定切入點表達式,該表達式的含義指的是對業務層中哪些方法增強
切入點表達式的寫法:
關鍵字:execution(表達式)
表達式:
訪問修飾符 返回值 包名.包名.包名...類名.方法名(參數列表)
標準的表達式寫法:
public void cn.codewhite.service.impl.AccountServiceImpl.saveAccount()
訪問修飾符可以省略
void cn.codewhite.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* cn.codewhite.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有幾級包,就需要寫幾個*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示當前包及其子包
* *..AccountServiceImpl.saveAccount()
類名和方法名都可以使用*來實現通配
* *..*.*()
參數列表:
可以直接寫數據類型:
基本類型直接寫名稱 int
引用類型寫包名.類名的方式 java.lang.String
可以使用通配符表示任意類型,但是必須有參數
可以使用..表示有無參數均可,有參數可以是任意類型
全通配寫法:
* *..*.*(..)
實際開發中切入點表達式的通常寫法:
切到業務層實現類下的所有方法
* com.itheima.service.impl.*.*(..)
配置通知:非環繞通知
Service:
public class AccountServiceImpl implements AccountService {
//切入點方法
@Override
public void saveAccount() {
System.out.println("執行了保存");
int i = 1 / 0;
}
}
Logger:
public class Logger {
//前置通知
public void beforePrintLog() {
System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了。。。");
}
//後置通知
public void afterReturningPrintLog() {
System.out.println("後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了。。。");
}
//異常通知
public void afterThrowingPrintLog() {
System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日誌了。。。");
}
//最終通知
public void afterPrintLog() {
System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日誌了。。。");
}
}
<?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="cn.codewhite.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger類 -->
<bean id="logger" class="cn.codewhite.util.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入點表達式 id屬性用於指定表達式的唯一標識。expression屬性用於指定表達式內容
此標籤寫在aop:aspect標籤內部只能當前切面使用。
它還可以寫在aop:aspect外面,此時就變成了所有切面可用
這裏配置了cn.codewhite.service.impl下的所有類,所有方法作爲切入點
-->
<aop:pointcut id="pt1" expression="execution(* cn.codewhite.service.impl.*.*(..))"/>
<!--配置切面:切入點和通知(引介)的結合。就是增強切入點(攔截到的方法)讓它增強 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的類型,並且建立通知方法和切入點方法的關聯-->
<!-- 配置前置通知-->
<aop:before method="beforePrintLog"
pointcut-ref="pt1"></aop:before>
<!-- 配置後置通知-->
<aop:after-returning method="afterReturningPrintLog"
pointcut-ref="pt1"></aop:after-returning>
<!-- 配置異常通知-->
<aop:after-throwing method="afterThrowingPrintLog"
pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最終通知-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
配置環繞通知:
<?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="cn.codewhite.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger類 -->
<bean id="logger" class="cn.codewhite.util.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入點表達式 id屬性用於指定表達式的唯一標識。expression屬性用於指定表達式內容
此標籤寫在aop:aspect標籤內部只能當前切面使用。
它還可以寫在aop:aspect外面,此時就變成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* cn.codewhite.service.impl.*.*(..))"/>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置環繞通知 詳細的註釋請看Logger類中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
Logger:
/**
* 環繞通知
* 問題:
* 當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
* 分析:
* 通過對比動態代理中的環繞通知代碼,發現動態代理的環繞通知有明確的切入點方法調用,而我們的代碼中沒有。
* 解決:
* Spring框架爲我們提供了一個接口:ProceedingJoinPoint。該接口有一個方法proceed(),此方法就相當於明確調用切入點方法。
* 該接口可以作爲環繞通知的方法參數,在程序執行時,spring框架會爲我們提供該接口的實現類供我們使用。
* <p>
* spring中的環繞通知:
* 它是spring框架爲我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。前置");
Object[] args = pjp.getArgs();//得到方法執行所需的參數
rtValue = pjp.proceed(args);//明確調用業務層方法(切入點方法)
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。後置");
return rtValue;
} catch (Throwable t) {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。異常");
throw new RuntimeException(t);
} finally {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。最終");
}
}
非環繞通知總結:
就相當於在原有方法中,做了增強。測試方法中只調用了acountService.saveAccount();
//切點方法
但是配置了AOP切面後,增加了通知,所以調用saveAccount()的時候會相當於調用下圖:
測試代碼: 只調用了切入點方法!
所以測試結果: 配置了通知,切入點方法增強!
註解的AOP配置
第一步:準備好依賴:
<dependencies>
<!-- Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- Spring切入點表達式支持-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
第二步:在配置文件中配置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"
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="cn.codewhite"></context:component-scan>
<!-- 配置spring開啓註解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
Service:
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
System.out.println("執行了保存");
int i = 1 / 0;
}
}
Logger:
package cn.codewhite.util;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 用於記錄日誌的工具類,它裏面提供了公共的代碼
*
* @author JUNSHI [email protected]
* @create 2020-05-25 22:31
*/
@Component("logger")//放入IOC容器
@Aspect//表示當前類是一個切面類
public class Logger {
@Pointcut("execution(* cn.codewhite.service.impl.*.*(..))")//配置切入點表達式
private void pt1(){}
@Before("pt1()")//前置通知註解,value是引用pt1的切面表達式
public void beforePrintLog() {
System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了。。。");
}
@AfterReturning("pt1()")//後置通知註解,value是引用pt1的切面表達式
public void afterReturningPrintLog() {
System.out.println("後置通知Logger類中的afterReturningPrintLog方法開始記錄日誌了。。。");
}
@AfterThrowing("pt1()")//異常通知註解,value是引用pt1的切面表達式
public void afterThrowingPrintLog() {
System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日誌了。。。");
}
@After("pt1()")//最終通知註解,value是引用pt1的切面表達式
public void afterPrintLog() {
System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日誌了。。。");
}
@Around("pt1()")//配置成環繞通知,value是引用pt1的切面表達式
public Object aroundPringLog(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。前置");
Object[] args = pjp.getArgs();//得到方法執行所需的參數
rtValue = pjp.proceed(args);//明確調用業務層方法(切入點方法)
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。後置");
return rtValue;
} catch (Throwable t) {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。異常");
throw new RuntimeException(t);
} finally {
System.out.println("Logger類中的aroundPringLog方法開始記錄日誌了。。。最終");
}
}
}
表明當前類是一個切面類
@Aspect
表明當前類是一個切面類
增強方法配置
@before
作用: 把當前方法看成是前置通知。
屬性: value:用於指定切入點表達式,還可以指定切入點表達式的引用。
@AfterReturning
作用: 把當前方法看成是後置通知。
屬性: value:用於指定切入點表達式,還可以指定切入點表達式的引用
@AfterThrowing
作用: 把當前方法看成是異常通知。
屬性: value:用於指定切入點表達式,還可以指定切入點表達式的引用
@After
作用: 把當前方法看成是最終通知。
屬性: value:用於指定切入點表達式,還可以指定切入點表達式的引用
@Around
作用: 把當前方法看成是環繞通知。
屬性: value:用於指定切入點表達式,還可以指定切入點表達式的引用。
切入點表達式註解
@Pointcut
作用: 指定切入點表達式
屬性: value:指定表達式的內容
開啓註解AOP支持
不使用XML的配置方式
@EnableAspectJAutoProxy
在配置類中輸入此註解,代表開啓了註解AOP支持
@Configuration
@ComponentScan(basePackages="cn.codewhite")
@EnableAspectJAutoProxy
public class SpringConfiguration {}
寫在後邊:
寫得不好,請見諒,如果需要PDF版的可以找我。