Spring學習(六)Spring AOP
Spring AOP
轉載:Spring AOP(面向切面程式設計) (biancheng.net)
前言
AOP 的全稱是“Aspect Oriented Programming”,即面向切面程式設計,和 OOP(面向物件程式設計)類似,也是一種程式設計思想。
AOP 採取橫向抽取機制(動態代理),取代了傳統縱向繼承機制的重複性程式碼,其應用主要體現在事務處理、日誌管理、許可權控制、異常處理等方面。主要作用是分離功能性需求和非功能性需求,使開發人員可以集中處理某一個關注點或者橫切邏輯,減少對業務程式碼的侵入,增強程式碼的可讀性和可維護性。
簡單的說,AOP 的作用就是保證開發者在不修改原始碼的前提下,為系統中的業務元件新增某種通用功能。AOP 就是代理模式的典型應用。
目前最流行的 AOP 框架有兩個,分別為 Spring AOP 和 AspectJ。
Spring AOP 是基於 AOP 程式設計模式的一個框架,它能夠有效的減少系統間的重複程式碼,達到鬆耦合的目的。Spring AOP 使用純 Java 實現,不需要專門的編譯過程和類載入器,在執行期間通過代理方式向目標類植入增強的程式碼。
AspectJ 是一個基於 Java 語言的 AOP 框架,從 Spring 2.0 開始,Spring AOP 引入了對 AspectJ 的支援。AspectJ 擴充套件了 Java 語言,提供了一個專門的編譯器,在編譯時提供橫向程式碼的植入。
為什麼使用AOP
AOP 提供了一種可插入的方式,可以在實際邏輯之前、之後或周圍新增其它關注點。比如一個類中有以下 10 個方法。
class A{
public void m1(){...}
public void m2(){...}
public void m3(){...}
public void m4(){...}
public void m5(){...}
public void n1(){...}
public void n2(){...}
public void p1(){...}
public void p2(){...}
public void p3(){...}
}
以 m 開頭的方法有 5 種,以 n 開頭的方法有 2 種,以 p 開頭的方法有 3 種。現在要求在以 m 開頭的方法後添加發送通知功能。
在不使用 AOP 的情況下,我們必須修改以 m 開頭的 5 種方法,在方法中呼叫傳送通知的方法。
如果使用 AOP,我們不用在方法內呼叫傳送通知的方法,只需要在類的方法中定義切入點,然後在 XML 檔案中呼叫。如果需要刪除或修改此功能,那麼只需要在 XML 檔案中進行更改。由此可以看出,使用 AOP 可以增強程式碼的可維護性。
AOP術語
為了更好地理解 AOP,我們需要了解一些它的相關術語。這些專業術語並不是 Spring 特有的,有些也同樣適用於其它 AOP 框架,如 AspectJ。它們的含義如下表所示。
名稱 | 說明 |
---|---|
Joinpoint(連線點) | 指那些被攔截到的點,在 Spring 中,指可以被動態代理攔截目標類的方法。 |
Pointcut(切入點) | 指要對哪些 Joinpoint 進行攔截,即被攔截的連線點。 |
Advice(通知) | 指攔截到 Joinpoint 之後要做的事情,即對切入點增強的內容。 |
Target(目標) | 指代理的目標物件。 |
Weaving(植入) | 指把增強程式碼應用到目標上,生成代理物件的過程。 |
Proxy(代理) | 指生成的代理物件。 |
Aspect(切面) | 切入點和通知的結合。 |
Advice 直譯為通知,也有的資料翻譯為“增強處理”,共有 5 種類型,如下表所示。
通知 | 說明 |
---|---|
before(前置通知) | 通知方法在目標方法呼叫之前執行 |
after(後置通知) | 通知方法在目標方法返回或異常後呼叫 |
after-returning(返回後通知) | 通知方法會在目標方法返回後呼叫 |
after-throwing(丟擲異常通知) | 通知方法會在目標方法丟擲異常後呼叫 |
around(環繞通知) | 通知方法會將目標方法封裝起來 |
AOP 是 Spring 的核心之一,在 Spring 中經常會使用 AOP 來簡化程式設計。在 Spring 框架中使用 AOP 主要有以下優勢。
- 提供宣告式企業服務,特別是作為 EJB 宣告式服務的替代品。最重要的是,這種服務是宣告式事務管理。
- 允許使用者實現自定義切面。在某些不適合用 OOP 程式設計的場景中,採用 AOP 來補充。
- 可以對業務邏輯的各個部分進行隔離,從而使業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時也提高了開發效率。
Spring JDK動態代理
Spring JDK 動態代理需要實現 InvocationHandler 介面,重寫 invoke 方法,客戶端使用 Java.lang.reflect.Proxy 類產生動態代理類的物件。
package com.aop.learn1;
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String userName, String password) {
System.out.println("正在執行新增使用者方法");
System.out.println("使用者名稱稱:"+userName+"密碼:"+password);
}
@Override
public void delUser(String userName) {
System.out.println("正在執行刪除使用者方法");
System.out.println("使用者名稱稱: " + userName);
}
}
package com.aop.learn1;
public interface UserManager {
/**
* 新增使用者
* @param userName
* @param password
*/
void addUser(String userName,String password);
/**
* 根據使用者名稱刪除使用者
* @param userName 使用者名稱
*/
void delUser(String userName);
}
package com.aop.learn1;
public class MyAspect {
public void myBefore() {
System.out.println("方法執行之前");
}
public void myAfter() {
System.out.println("方法執行之後");
}
}
JdkProxy
package com.aop.learn1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK動態代理實現InvocationHandler介面
*/
public class JdkProxy implements InvocationHandler {
private Object target; // 需要代理的目標物件
final MyAspect myAspect = new MyAspect();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myAspect.myBefore();
Object result = method.invoke(target, args);
myAspect.myAfter();
return result;
}
// 定義獲取代理物件方法
private Object getJDKProxy(Object targetObject) {
// 為目標物件target賦值
this.target = targetObject;
// JDK動態代理只能代理實現了介面的類,從 newProxyInstance 函式所需的引數就可以看出來
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
this);
}
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy(); // 例項化JDKProxy物件
UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl()); // 獲取代理物件
user.addUser("bianchengbang", "www.biancheng.net"); // 執行新增方法
user.delUser("bianchengbang"); // 執行刪除方法
}
}
執行main方法後
方法執行之前
正在執行新增使用者方法
使用者名稱稱: bianchengbang 密碼: www.biancheng.net
方法執行之後
方法執行之前
正在執行刪除使用者方法
使用者名稱稱: bianchengbang
方法執行之後
若你想了解更多:
Java動態代理之InvocationHandler最簡單的入門教程 - 簡書 (jianshu.com)
Spring CGLlB動態代理
JDK 動態代理使用起來非常簡單,但是 JDK 動態代理的目標類必須要實現一個或多個介面,具有一定的侷限性。如果不希望實現介面,可以使用 CGLIB代理。
CGLIB(Code Generation Library)是一個高效能開源的程式碼生成包,它被許多 AOP 框架所使用,其底層是通過使用一個小而快的位元組碼處理框架 ASM(Java 位元組碼操控框架)轉換位元組碼並生成新的類。使用 CGLIB 需要匯入 CGLIB 和 ASM 包,即 asm-x.x.jar 和 CGLIB-x.x.x.jar 。如果您已經匯入了 Spring 的核心包 spring-core-x.x.x.RELEASE.jar,就不用再匯入 asm-x.x.jar 和 cglib-x.x.x.jar 了。
Spring 核心包中包含 CGLIB 和 asm,也就是說 Spring 核心包已經集成了 CGLIB 所需要的包,所以在開發中不需要另外匯入asm-x.x.jar 和 cglib-x.x.x.jar 包了。
示例
新增CglibProxy類
package com.aop.learn1;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB動態代理,實現MethodInterceptor介面
*/
public class CglibProxy implements MethodInterceptor {
private Object target;// 需要代理的目標物件
final MyAspect myAspect = new MyAspect();
// 重寫攔截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
myAspect.myBefore();
Object invoke = method.invoke(target, arr);// 方法執行,引數:target目標物件 arr引數陣列
myAspect.myAfter();
return invoke;
}
// 定義獲取代理物件方法
public Object getCglibProxy(Object objectTarget) {
// 為目標物件target賦值
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
// 設定父類,因為CGLIB是針對指定的類生成一個子類,所以需要指定父類
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 設定回撥
Object result = enhancer.create();// 建立並返回代理物件
return result;
}
public static void main(String[] args) {
CglibProxy cglib= new CglibProxy();// 例項化CglibBProxy物件
UserManager user = (UserManager) cglib.getCglibProxy(new UserManagerImpl());// 獲取代理物件
user.addUser("bianchengbang", "www.biancheng.net"); // 執行新增方法
user.delUser("bianchengbang"); // 執行刪除方法
}
}
執行main
方法執行之前
正在執行新增使用者方法
使用者名稱稱:bianchengbang密碼:www.biancheng.net
方法執行之後
方法執行之前
正在執行刪除使用者方法
使用者名稱稱: bianchengbang
方法執行之後
JDK代理和CGLIB代理的區別
JDK 動態代理是利用反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫 InvokeHandler 來處理。而 CGLIB 動態代理是利用 ASM 開源包,載入代理物件類的 class 檔案,通過修改其位元組碼生成子類來處理。
JDK 動態代理只能對實現了介面的類生成代理,而不能針對類。
CGLIB 是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法。因為是繼承,所以該類或方法不能宣告成 final 型別。
JDK動態代理特點
- 代理物件必須實現一個或多個介面
- 以介面的形式接收代理例項,而不是代理類
CGLIB動態代理特點
- 代理物件不能被 final 修飾
- 以類或介面形式接收代理例項
JDK與CGLIB動態代理的效能比較
生成代理例項效能:JDK > CGLIB
代理例項執行效能:JDK > CGLIB
Spring整合AspectJ
Spring 2.0 以後,Spring 新增了對 AspectJ 的支援。在新版本的 Spring 框架中,建議使用 AspectJ 方式開發 AOP。
AspectJ 是一個基於 Java 語言的 AOP 框架,它擴充套件了 Java 語言,提供了強大的 AOP 功能。
使用 AspectJ 引入以下依賴
<!--AspectJ 開始-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!--AspectJ 結束-->
使用 AspectJ 開發 AOP 通常有以下 2 種方式:
基於AspectJ XML開發
基於 XML 的宣告式是指通過 Spring 配置檔案的方式來定義切面、切入點及通知,而所有的切面和通知都必須定義在 aop:config 元素中。
在使用 aop:config 元素之前,我們需要先匯入 Spring 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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
...
</beans>
定義切面<aop:aspect>
在 Spring 配置檔案中,使用 aop:aspect 元素定義切面,該元素可以將定義好的 Bean 轉換為切面 Bean,所以使用 aop:aspect 之前需要先定義一個普通的 Spring Bean。
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
其中,id 用來定義該切面的唯一表示名稱,ref 用於引用普通的 Spring Bean。
定義切入點<aop:pointcut>
aop:pointcut 用來定義一個切入點,當 aop:pointcut元素作為 aop:config 元素的子元素定義時,表示該切入點是全域性切入點,它可被多個切面所共享;當 aop:pointcut 元素作為 aop:aspect 元素的子元素時,表示該切入點只對當前切面有效。
<aop:config>
<aop:pointcut id="myPointCut"
expression="execution(* com.aop.service.*.*(..))"/>
</aop:config>
其中,id 用於指定切入點的唯一標識名稱,execution 用於指定切入點關聯的切入點表示式。
execution 格式為:
execution(modifiers-pattern returning-type-pattern declaring-type-pattern name-pattern(param-pattern)throws-pattern)
其中:
- returning-type-pattern、name-pattern、param-pattern 是必須的,其它引數為可選項。
- modifiers-pattern:指定修飾符,如 private、public。
- returning-type-pattern:指定返回值型別,
*
表示可以為任何返回值。如果返回值為物件,則需指定全路徑的類名。 - declaring-type-pattern:指定方法的包名。
- name-pattern:指定方法名,
*
代表所有,set*
代表以 set 開頭的所有方法。 - param-pattern:指定方法引數(宣告的型別),
(..)
代表所有引數,(*)
代表一個引數,(*,String)
代表第一個引數可以為任何值,第二個為 String 型別的值。 - throws-pattern:指定丟擲的異常型別。
例如:execution(* com.aop.*.*(..))
表示匹配 com.aop 包中任意類的任意方法。
定義通知
AspectJ 支援 5 種類型的 advice,如下。
<aop:aspect id="myAspect" ref="aBean">
<!-- 前置通知 -->
<aop:before pointcut-ref="myPointCut" method="..."/>
<!-- 後置通知 -->
<aop:after-returning pointcut-ref="myPointCut" method="..."/>
<!-- 環繞通知 -->
<aop:around pointcut-ref="myPointCut" method="..."/>
<!-- 異常通知 -->
<aop:after-throwing pointcut-ref="myPointCut" method="..."/>
<!-- 最終通知 -->
<aop:after pointcut-ref="myPointCut" method="..."/>
....
</aop:aspect>
示例
Logging
package com.aop.learn2;
public class Logging {
/**
* 前置通知
*/
public void beforeAdvice() {
System.out.println("前置通知");
}
/**
* 後置通知
*/
public void afterAdvice() {
System.out.println("後置通知");
}
/**
* 返回後通知
*/
public void afterReturningAdvice(Object retVal) {
System.out.println("返回值為:" + retVal.toString());
}
/**
* 丟擲異常通知
*/
public void afterThrowingAdvice(IllegalArgumentException ex) {
System.out.println("這裡的異常為:" + ex.toString());
}
}
Man
package com.aop.learn2;
public class Man {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void throwException() {
System.out.println("丟擲異常");
throw new IllegalArgumentException();
}
}
Beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll"
expression="execution(* net.biancheng.*.*(..))" />
<aop:before pointcut-ref="selectAll" method="beforeAdvice" />
<aop:after pointcut-ref="selectAll" method="afterAdvice" />
<aop:after-returning pointcut-ref="selectAll"
returning="retVal" method="afterReturningAdvice" />
<aop:after-throwing pointcut-ref="selectAll"
throwing="ex" method="afterThrowingAdvice" />
</aop:aspect>
</aop:config>
<bean id="man" class="com.aop.learn2.Man">
<property name="name" value="bianchengbang" />
<property name="age" value="12" />
</bean>
<bean id="logging" class="com.aop.learn2.Logging" />
</beans>
MainApp
package com;
import com.aop.learn2.Man;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Man man = (Man) context.getBean("man");
man.getName();
man.getAge();
man.throwException();
}
}
執行結果如下
前置通知
後置通知
返回值為:bianchengbang
前置通知
後置通知
返回值為:12
前置通知
丟擲異常
後置通知
這裡的異常為:java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
基於AspectJ註解開發
在 Spring 中,儘管使用 XML 配置檔案可以實現 AOP 開發,但是如果所有的相關配置都集中在配置檔案中,勢必會導致 XML 配置檔案過於臃腫,從而給維護和升級帶來一定的困難。
為此,AspectJ 框架為 AOP 開發提供了一套註解。AspectJ 允許使用註解定義切面、切入點和增強處理,Spring 框架可以根據這些註解生成 AOP 代理。
關於註解的介紹如下所示。
名稱 | 說明 |
---|---|
@Aspect | 用於定義一個切面。 |
@Pointcut | 用於定義一個切入點。 |
@Before | 用於定義前置通知,相當於 BeforeAdvice。 |
@AfterReturning | 用於定義後置通知,相當於 AfterReturningAdvice。 |
@Around | 用於定義環繞通知,相當於MethodInterceptor。 |
@AfterThrowing | 用於定義丟擲通知,相當於ThrowAdvice。 |
@After | 用於定義最終final通知,不管是否異常,該通知都會執行。 |
@DeclareParents | 用於定義引介通知,相當於IntroductionInterceptor(不要求掌握)。 |
啟用 @AspectJ 註解有以下兩種方法:
1、使用@Configuration和@EnableAspectJAutoProxy註解
@Configuration
@EnableAspectJAutoProxy
public class Appconfig {
}
2、基於XML配置
在 XML 檔案中新增以下內容啟用 @AspectJ。
<aop:aspectj-autoproxy>
定義切面@Aspect
AspectJ 類和其它普通的 Bean 一樣,可以有方法和欄位,不同的是 AspectJ 類需要使用 @Aspect 註解,如下所示。
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AspectModule {
}
AspectJ 類也可以像其它 Bean 一樣在 XML 中配置,如下。
<bean id = "myAspect" class = "com.aop.learn2.AspectModule">
...
</bean>
定義切入點@Pointcut
@Pointcut 註解用來定義一個切入點,如下。
// 要求:方法必須是private,返回值型別為void,名稱自定義,沒有引數
@Pointcut("execution(*com.aop..*.*(..))")
private void myPointCut() {
}
相當於以下程式碼
<aop:pointcut expression="execution(*com.aop..*.*(..))" id="myPointCut"/>
定義通知advice
@AspectJ 支援 5 種類型的 advice,以下為使用 @Before 的示例。
@Before("myPointCut()")
public void beforeAdvice(){
...
}
示例
使用xml配置
package com.aop.learn2;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Logging {
/**
* 定義切入點
*/
@Pointcut("execution(* com.aop..*.*(..))")
private void selectAll() {
}
/**
* 前置通知
*/
@Before("selectAll()")
public void beforeAdvice() {
System.out.println("前置通知");
}
/**
* 後置通知
*/
@After("selectAll()")
public void afterAdvice() {
System.out.println("後置通知");
}
/**
* 返回後通知
*/
@AfterReturning(pointcut = "selectAll()", returning = "retVal")
public void afterReturningAdvice(Object retVal) {
System.out.println("返回值為:" + retVal.toString());
}
/**
* 丟擲異常通知
*/
@AfterThrowing(pointcut = "selectAll()", throwing = "ex")
public void afterThrowingAdvice(IllegalArgumentException ex) {
System.out.println("這裡的異常為:" + ex.toString());
}
}
package com.aop.learn2;
public class Man {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void throwException() {
System.out.println("丟擲異常");
throw new IllegalArgumentException();
}
}
package com;
import com.aop.learn2.Man;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Man man = (Man) context.getBean("man");
man.getName();
man.getAge();
man.throwException();
}
}
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:aspectj-autoproxy />
<bean id="man" class="com.aop.learn2.Man">
<property name="name" value="bianchengbang" />
<property name="age" value="12" />
</bean>
<bean id="logging" class="com.aop.learn2.Logging" />
</beans>
執行如下
前置通知
後置通知
返回值為:bianchengbang
前置通知
後置通知
返回值為:12
前置通知
丟擲異常
後置通知
這裡的異常為:java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
使用註解
package com.aop.learn2;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Logging {
/**
* 定義切入點
*/
@Pointcut("execution(* com.aop..*.*(..))")
private void selectAll() {
}
/**
* 前置通知
*/
@Before("selectAll()")
public void beforeAdvice() {
System.out.println("前置通知");
}
/**
* 後置通知
*/
@After("selectAll()")
public void afterAdvice() {
System.out.println("後置通知");
}
/**
* 返回後通知
*/
@AfterReturning(pointcut = "selectAll()", returning = "retVal")
public void afterReturningAdvice(Object retVal) {
System.out.println("返回值為:" + retVal.toString());
}
/**
* 丟擲異常通知
*/
@AfterThrowing(pointcut = "selectAll()", throwing = "ex")
public void afterThrowingAdvice(IllegalArgumentException ex) {
System.out.println("這裡的異常為:" + ex.toString());
}
}
package com.aop.learn2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Man {
@Value("青杉")
private String name;
@Value("20")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void throwException() {
System.out.println("丟擲異常");
throw new IllegalArgumentException();
}
}
package com.aop.learn2;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"com.aop.learn2"})
@EnableAspectJAutoProxy(proxyTargetClass = true) //啟用AOP 解析AspectJ註解
public class SpringConifg {
}
package com;
import com.aop.learn2.Man;
import com.aop.learn2.SpringConifg;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConifg.class);
Man man = (Man) context.getBean("man");
man.getName();
man.getAge();
man.throwException();
}
}
切入點需要注入
結果如下
前置通知
後置通知
返回值為:青杉
前置通知
後置通知
返回值為:20
前置通知
丟擲異常
後置通知
這裡的異常為:java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException