上一篇博客我們引出了 AOP 的概念,以及 AOP 的具體實現方式。但是為什么要這樣實現?以及提出的切入點表達式到底該怎么理解?
這篇博客我們通過對 AspectJ 框架的介紹來詳細了解。
1、什么是 AspectJ?
AspectJ是一個面向切面的框架,它擴展了Java語言。AspectJ定義了AOP語法,也可以說 AspectJ 是一個基於 Java 語言的 AOP 框架。通常我們在使用 Spring AOP 的時候,都會導入 AspectJ 的相關 jar 包。

在 spring2.0以后,spring新增了對AspectJ 切點表達式的支持;Aspect1.5新增注解功能,通過 JDK5的注解技術,能直接在類中定義切面;新版本的 spring 框架,也都建議使用 AspectJ 來實現 AOP。所以說在 spring AOP 的核心包 Spring-aop-3.2.jar 里面也有對 AspectJ 的支持。
2、切入點表達式
上一篇博客中,我們在spring配置文件中配置如下:
<!-- 切入點表達式 --> <aop:pointcut expression="execution(* com.ys.aop.*.*(..))" id="myPointCut"/>
那么它表達的意思是 返回值任意,包名為 com.ys.aop 下的任意類名中的任意方法名,參數任意。那么這到底是什么意思呢?
首先 execution 是 AspectJ 框架定義的一個切入點函數,其語法形式如下:
execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 類修飾符 返回值 方法所在的包 方法名 方法拋出的異常
簡單點來說就是:
語法:execution(修飾符 返回值 包.類.方法名(參數) throws異常)
具體解釋我們用下面一張思維導圖來看:

注意:如果切入點表達式有多個不同目錄呢? 可以通過 || 來表示或的關系。
<aop:pointcut expression="execution(* com.ys.*Service1.*(..)) ||
execution(* com.ys.*Service2.*(..))" id="myPointCut"/>
表示匹配 com.ys包下的,以 Service1結尾或者以Service2結尾的類的任意方法。
AOP 切入點表達式支持多種形式的定義規則:
1、execution:匹配方法的執行(常用)
execution(public *.*(..))
2.within:匹配包或子包中的方法(了解)
within(com.ys.aop..*)
3.this:匹配實現接口的代理對象中的方法(了解)
this(com.ys.aop.user.UserDAO)
4.target:匹配實現接口的目標對象中的方法(了解)
target(com.ys.aop.user.UserDAO)
5.args:匹配參數格式符合標准的方法(了解)
args(int,int)
6.bean(id) 對指定的bean所有的方法(了解)
bean('userServiceId')
2、Aspect 通知類型
Aspect 通知類型,定義了類型名稱以及方法格式。類型如下:
before:前置通知(應用:各種校驗) 在方法執行前執行,如果通知拋出異常,阻止方法運行 afterReturning:后置通知(應用:常規數據處理) 方法正常返回后執行,如果方法中拋出異常,通知無法執行 必須在方法執行后才執行,所以可以獲得方法的返回值。 around:環繞通知(應用:十分強大,可以做任何事情) 方法執行前后分別執行,可以阻止方法的執行 必須手動執行目標方法 afterThrowing:拋出異常通知(應用:包裝異常信息) 方法拋出異常后執行,如果方法沒有拋出異常,無法執行 after:最終通知(應用:清理現場) 方法執行完畢后執行,無論方法中是否出現異常
這里最重要的是around,環繞通知,它可以代替上面的任意通知。
在程序中表示的意思如下:
try{
//前置:before
//手動執行目標方法
//后置:afterRetruning
} catch(){
//拋出異常 afterThrowing
} finally{
//最終 after
}
對應的 jar 包如下:

我們可以查看源碼:


3、AOP具體實例
①、創建接口
package com.ys.aop;
public interface UserService {
//添加 user
public void addUser();
//刪除 user
public void deleteUser();
}
②、創建實現類
package com.ys.aop;
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("增加 User");
}
@Override
public void deleteUser() {
System.out.println("刪除 User");
}
}
③、創建切面類(包含各種通知)
package com.ys.aop;
import org.aspectj.lang.JoinPoint;
public class MyAspect {
/**
* JoinPoint 能獲取目標方法的一些基本信息
* @param joinPoint
*/
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知 : " + joinPoint.getSignature().getName());
}
public void myAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
}
public void myAfter(){
System.out.println("最終通知");
}
}
④、創建spring配置文件applicationContext.xml
我們首先測試前置通知、后置通知、最終通知
<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">
<!--1、 創建目標類 -->
<bean id="userService" class="com.ys.aop.UserServiceImpl"></bean>
<!--2、創建切面類(通知) -->
<bean id="myAspect" class="com.ys.aop.MyAspect"></bean>
<!--3、aop編程
3.1 導入命名空間
3.2 使用 <aop:config>進行配置
proxy-target-class="true" 聲明時使用cglib代理
如果不聲明,Spring 會自動選擇cglib代理還是JDK動態代理
<aop:pointcut> 切入點 ,從目標對象獲得具體方法
<aop:advisor> 特殊的切面,只有一個通知 和 一個切入點
advice-ref 通知引用
pointcut-ref 切入點引用
3.3 切入點表達式
execution(* com.ys.aop.*.*(..))
選擇方法 返回值任意 包 類名任意 方法名任意 參數任意
-->
<aop:config>
<aop:aspect ref="myAspect">
<!-- 切入點表達式 -->
<aop:pointcut expression="execution(* com.ys.aop.*.*(..))" id="myPointCut"/>
<!-- 3.1 前置通知
<aop:before method="" pointcut="" pointcut-ref=""/>
method : 通知,及方法名
pointcut :切入點表達式,此表達式只能當前通知使用。
pointcut-ref : 切入點引用,可以與其他通知共享切入點。
通知方法格式:public void myBefore(JoinPoint joinPoint){
參數1:org.aspectj.lang.JoinPoint 用於描述連接點(目標方法),獲得目標方法名等
-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 3.2后置通知 ,目標方法后執行,獲得返回值
<aop:after-returning method="" pointcut-ref="" returning=""/>
returning 通知方法第二個參數的名稱
通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
參數1:連接點描述
參數2:類型Object,參數名 returning="ret" 配置的
-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />
<!-- 3.3 最終通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
⑤、測試
@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService useService = (UserService) context.getBean("userService");
useService.addUser();
}
控制台打印:

注意,后置通知的返回值為 null,是因為我們的目標方法 addUser() 沒有返回值。如果有返回值,這里就是addUser() 的返回值。
4、測試異常通知
目標接口保持不變,目標類我們手動引入異常:
public void addUser() {
int i = 1/0;//顯然這里會拋出除數不能為 0
System.out.println("增加 User");
}
接着配置切面:MyAspect.java
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("拋出異常通知 : " + e.getMessage());
}
接着在 applicationContext.xml 中配置如下:
<!-- 3.4 拋出異常
<aop:after-throwing method="" pointcut-ref="" throwing=""/>
throwing :通知方法的第二個參數名稱
通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
參數1:連接點描述對象
參數2:獲得異常信息,類型Throwable ,參數名由throwing="e" 配置
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
測試:
@Test
public void testAop(){
String str = "com/ys/execption/applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(str);
UserService useService = (UserService) context.getBean("userService");
useService.addUser();
}
控制台打印:

5、測試環繞通知
目標接口和目標類保持不變,切面MyAspect 修改如下:
public class MyAspect {
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("前置通知");
//手動執行目標方法
Object obj = joinPoint.proceed();
System.out.println("后置通知");
return obj;
}
}
applicationContext.xml 配置如下:
<!-- 環繞通知
<aop:around method="" pointcut-ref=""/>
通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
返回值類型:Object
方法名:任意
參數:org.aspectj.lang.ProceedingJoinPoint
拋出異常
執行目標方法:Object obj = joinPoint.proceed();
-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
測試:
@Test
public void testAop(){
String str = "com/ys/around/applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(str);
UserService useService = (UserService) context.getBean("userService");
useService.addUser();
}
打印結果:

那么至此,通過 xml 配置的方式我們講解了Spring AOP 的配置。下一章將通過注解的方式來實現。
