1. 程式人生 > 其它 >Day55~56(spring註解高階AOP)56

Day55~56(spring註解高階AOP)56

第二部分:Spring的AOP

一、設計模式-代理模式

代理模式:給某一個物件提供一個代理物件,並由代理物件控制對源物件的引用。代理 就是一個人或一個機構代表另一個人或者一個機構採取行動。某些情況下,客戶不想或者不 能夠直接引用一個物件,代理物件可以在客戶和目標物件直接起到中介的作用。客戶端分辨 不出代理主題物件與真實主題物件。代理模式可以並不知道真正的被代理物件,而僅僅持有 一個被代理物件的介面,這時候代理物件不能夠建立被代理物件,被代理物件必須有系統的 其他角色代為建立並傳入。 為什麼要使用代理模式呢? 第一,它有間接的特點,可以起到中介隔離作用。就好比在租房的時候,房東可能不在 本地,而短期內又不能趕回來,此時中介的出場,就作為房東的代理實現和我們簽訂承租合 同。而我們和房東之間就沒有耦合了。 第二,它有增強的功能。還以租房為例,我們首先考慮的是找一個靠譜的中介,由中介 給我們提供房源資訊,並且告訴我們房屋的細節,合同的細節等等。當然我們也可以自己去 找一些個人出租的房屋,但是在這之中,我們要了解租賃合同,房屋驗收,租金監管等情 況,這無疑對我們是繁重的工作。而有了中介作為我們的代理中間人,他把了解到的資訊告 訴我們,我們一樣可以租到房子,而不必做那些繁重的工作。

二、AOP思想及實現原理

1、AOP思想

在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通 過預編譯方 式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中 的一個 熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對 業務邏輯 的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性, 同時提高 了開發的效率。

2、實現原理

在上面的概念中描述出aop的實現原理是基於動態代理技術實現的。下面是針對動態代 理的一些介紹:

特點: 位元組碼隨用隨建立,隨用隨載入

分類: 基於介面的動態代理,基於子類的動態代理

作用: 不修改原始碼的基礎上對方法增強 基於介面的動態代理:

提供者是:JDK官方

使用要求:被代理類最少實現一個介面。

涉及的類:Proxy

建立代理物件的方法:newProxyInstance

方法的引數: ClassLoader:類載入器。用於載入代理物件的位元組碼的。和被代理物件使 用相同的類載入器。固定寫法。

Class[]:位元組碼陣列。用於給代理物件提供方法。和被代理物件具有相同的 方法。 被代理類是一個普通類:

被代理類對 象.getClass().getInterfaces();

被代理類是一個介面:new Class[]{被代理了.class}

它也是固定寫法

InvocationHanlder:要增強的方法。此處是一個介面,我們需要提供它的 實現類。 通常寫的是匿名內部類。增強的程式碼誰用誰寫。 基於子類的動態代理

提供者是:第三方cglib包,在使用時需要先導包(maven工程匯入座標即可)

使用要求:被代理類不能是最終類,不能被final修飾

涉及的類:Enhancer

建立代理物件的方法:create

方法的引數: Class:位元組碼。被代理物件的位元組碼。可以建立被代理物件的子類,還可以 獲取被代理物件的類載入器。

Callback:增強的程式碼。誰用誰寫。通常都是寫一個介面的實現類或者匿名 內部類。

Callback中沒有任何方法,所以我們一般使用它的子介面: MethodInterceptor

3、Spring中AOP的術語

Joinpoint(連線點):

所謂連線點是指那些被攔截到的點。在spring中,指的是方法,因為spring只支援方 法型別的連線點。

Pointcut(切入點):

所謂切入點是指我們要對哪些Joinpoint進行攔截的定義。

Advice(通知/增強):

所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。 通知的型別:前置通知,後置通知,異常通知,最終通知,環繞通知。

Introduction(引介):

引介是一種特殊的通知在不修改類程式碼的前提下, 可以在執行期為類動態地新增一 些方法或Field。

Target(目標物件): 代理的目標物件。

Weaving(織入):

是指把增強應用到目標物件來建立新的代理物件的過程。 spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。

Proxy(代理): 一個類被AOP織入增強後,就產生一個結果代理類。

Aspect(切面):

是切入點和通知(引介)的結合。

三、Spring註解驅動AOP開發入門

1、寫在最前

a.Spring的aop是基於ioc的。所以需要有spring的ioc基礎。(本篇內容不對ioc進行講 解)

b.本章節我們只是對aop的使用做基本功能展示,目的是為了以此講解aop中的註解和執行原 理分析。

2、註解驅動入門案例介紹

需求: 實現在執行service方法時輸出執行日誌。(除了業務層外,表現層和持久層也可 以實現)

3、案例實現

實體類:(在本案例中沒有實際作用)
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
public class User implements Serializable {
private String id;
private String username;
private String password;
private String email;
private Date birthday;
private String gender;
private String mobile;
private String nickname;
}

業務層介面:
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
public interface UserService {
/**
* 儲存使用者
* @param user
*/
void save(User user);
}
業務層實現類:
/**
* 使用者的業務層實現類
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Service("userService")
public class UserServiceImpl implements UserService{
@Override
public void save(User user) {
System.out.println("儲存用:"+user);
}
}

日誌工具類:
/**
* 記錄日誌的工具類
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Component
@Aspect
public class LogUtil {
/**
* 通用切入點表示式
*/
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforeLog(){
System.out.println("執行切入點方法前記錄日誌");
}
/**
* 後置通知
*/
@AfterReturning("pt1()")
public void afterReturningLog(){
System.out.println("正常執行切入點方法後記錄日誌");
}
/**
* 異常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingLog(){
System.out.println("執行切入點方法產生異常後記錄日誌");
}
/**
* 最終通知
*/

@After("pt1()")
public void afterLog(){
System.out.println("無論切入點方法執行是否有異常都記錄日誌");
}
/**
* 環繞通知
*/
@Around("pt1()")
public Object arountPrintLog(ProceedingJoinPoint pjp){
//1.定義返回值
Object rtValue = null;
try{
//前置通知
System.out.println("執行切入點方法前記錄日誌");
//2.獲取方法執行所需的引數
Object[] args = pjp.getArgs();
//3.執行切入點方法
rtValue = pjp.proceed(args);
//後置通知
System.out.println("正常執行切入點方法後記錄日誌");
}catch (Throwable t){
//異常通知
System.out.println("執行切入點方法產生異常後記錄日誌");
}finally {
//最終通知
System.out.println("無論切入點方法執行是否有異常都記錄日誌");
}
return rtValue;
}
}

配置類:
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

測試類:
/**
* spring的aop環境準備
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
public class SpringAOPTest {
public static void main(String[] args) {
//1.獲取容器
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.獲取bean物件
UserService userService =
ac.getBean("userService",UserService.class);
//3.準備資料
User user = new User();
user.setId("1");
user.setUsername("test");
user.setNickname("泰斯特");
//4.執行方法
userService.save(user);
}
}

四、AOP常用註解分析

1、用於開啟註解AOP支援的

1.1、@EnableAspectJAutoProxy

1.1.1、原始碼

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass‐based (CGLIB) proxies are to be created
as opposed
* to standard Java interface‐based proxies. The default is {@code
false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as
a {@code ThreadLocal}
* for retrieval via the {@link
org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access
will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}

1.1.2、說明

作用:

表示開啟spring對註解aop的支援。它有兩個屬性,分別是指定採用的代理方式和 是否暴露代理物件,通過AopContext可以進行訪問。從定義可以看得出,它引入 AspectJAutoProxyRegister.class物件,該物件是基於註解@EnableAspectJAutoProxy 註冊一個AnnotationAwareAspectJAutoProxyCreator,該物件通過呼叫 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(regist ry);註冊一個aop代理物件生成器。關於AnnotationAwareAspectJAutoProxyCreator請參 考第五章第二小節《AnnotationAwareAspectJAutoProxyCreator物件的分析》 屬性:

proxyTargetClass: 指定是否採用cglib進行代理。預設值是false,表示使用jdk的代理。

exposeProxy: 指定是否暴露代理物件,通過AopContext可以進行訪問。 使用場景: 當我們註解驅動開發時,在需要使用aop實現某些功能的情況下,都需要用到此注 解。

1.1.3、示例

/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

2、用於配置切面的

2.1、@Aspect

2.1.1、原始碼

/**
* Aspect declaration
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
/**
* Per clause expression, defaults to singleton aspect
* <p/>
* Valid values are "" (singleton), "perthis(...)", etc
*/
public String value() default "";
}

2.1.2、說明

作用:

聲明當前類是一個切面類。

屬性: value: 預設我們的切面類應該為單例的。但是當切面類為一個多例類時,指定預 處理的切入點表示式。

用法是perthis(切入點表示式)。

它支援指定切入點表示式,或者是用@Pointcut修飾的方法名稱(要求全 限定方法名)

使用場景:

此註解也是一個註解驅動開發aop的必備註解。

2.1.3、示例

/**
* 記錄日誌的工具類
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Component
@Scope("prototype")//注意:通常情況下我們的切面類是不需要多例的。
@Aspect(value="execution(* com.itheima.service.impl.*.*(..))")
public class LogUtil {
/**
* 用於配置當前方法是一個前置通知
*/
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void printLog(){
System.out.println("執行列印日誌的功能");
}
}

3、用於配置切入點表示式的

3.1、@Pointcut

3.1.1、原始碼

/**
* Pointcut declaration
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
/**
* The pointcut expression
* We allow "" as default for abstract pointcut
*/
String value() default "";
/**
* When compiling without debug info, or when interpreting pointcuts
at runtime,
* the names of any arguments used in the pointcut are not available.
* Under these circumstances only, it is necessary to provide the arg
names in
* the annotation ‐ these MUST duplicate the names used in the
annotated method.
* Format is a simple comma‐separated list.
*/
String argNames() default "";
}

3.1.2、說明

作用:

此註解是用於指定切入點表示式的。

屬性: value: 用於指定切入點表示式。表示式的配置詳解請參考第五章節第三小節《切 入點表示式的寫法》

argNames: 用於指定切入點表示式的引數。引數可以是execution中的,也可以是 args中的。通常情況下不使用此屬性也可以獲得切入點方法引數。

使用場景: 在實際開發中,當我們的多個通知需要執行,同時增強的規則確定的情況下,就可 以把切入點表示式通用化。此註解就是代替xml中的標籤,實現切入點表達 式的通用化。

3.1.3、示例

@Component
@Aspect
public class LogUtil {
/**
* 通用切入點表示式
* 在value屬性的中使用了&&符號,表示並且的關係。
* &&符號後面的args和execution一樣,都是切入點表示式支援的關鍵字,表示匹配
引數。指定的內容
* 可以是全限定類名,或者是名稱。當指定引數名稱時,要求與方法中形參名稱相
同。
* argNames屬性,是定義引數的名稱,該名稱必須和args關鍵字中的名稱一致。
*/
@Pointcut(value = "execution(* com.itheima.service.impl.*.*
(com.itheima.domain.User))&& args(user)",argNames = "user")
private void pt1(User user){}
}

4、用於配置通知的

4.1、@Before

4.1.1、原始碼

/**
* Before advice
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
/**
* The pointcut expression where to bind the advice
*/
String value();
/**
* When compiling without debug info, or when interpreting pointcuts
at runtime,
* the names of any arguments used in the advice declaration are not
available.
* Under these circumstances only, it is necessary to provide the arg
names in
* the annotation ‐ these MUST duplicate the names used in the
annotated method.
* Format is a simple comma‐separated list.
*/
String argNames() default "";
}

4.1.2、說明

作用:

被此註解修飾的方法為前置通知。前置通知的執行時間點是在切入點方法執行之 前。 屬性:

value:

用於指定切入點表示式。可以是表示式,也可以是表示式的引用。

argNames:

用於指定切入點表示式引數的名稱。它要求和切入點表示式中的引數名稱 一致。通常不指定也可以獲取切入點方法的引數內容。

使用場景:

在實際開發中,我們需要對切入點方法執行之前進行增強, 此時就用到了前置通 知。在通知(增強的方法)中需要獲取切入點方法中的引數進行處理時,就要配合切入點表達 式引數來使用。

4.1.3、示例

/**
* 前置通知
*/
@Before(value = "pt1(user)",argNames = "user")
public void beforeLog(User user){
System.out.println("執行切入點方法前記錄日誌"+user);
}

4.2、@AfterReturning

4.2.1、原始碼

/**
* After returning advice
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {
/**
* The pointcut expression where to bind the advice
*/
String value() default "";
/**
* The pointcut expression where to bind the advice, overrides
"value" when specified
*/
String pointcut() default "";
/**
* The name of the argument in the advice signature to bind the
returned value to
*/
String returning() default "";
/**
* When compiling without debug info, or when interpreting pointcuts
at runtime,
* the names of any arguments used in the advice declaration are not
available.
* Under these circumstances only, it is necessary to provide the arg
names in
* the annotation ‐ these MUST duplicate the names used in the
annotated method.
* Format is a simple comma‐separated list.
*/
String argNames() default "";
}

4.2.2、說明

作用:

用於配置後置通知。後置通知的執行是在切入點方法正常執行之後執行。 需要注意的是,由於基於註解的配置時,spring建立通知方法的攔截器鏈時,後置 通知在最終通知之後,所以會先執行@After註解修飾的方法。

屬性:

value: 用於指定切入點表示式,可以是表示式,也可以是表示式的引用。 pointcut: 它的作用和value是一樣的。

returning: 指定切入點方法返回值的變數名稱。它必須和切入點方法返回值名稱一 致。

argNames: 用於指定切入點表示式引數的名稱。它要求和切入點表示式中的引數名稱 一致。通常不指定也可以獲取切入點方法的引數內容。

使用場景:

此註解是用於配置後置增強切入點方法的。被此註解修飾方法會在切入點方法正常 執行之後執行。在我們實際開發中,像提交事務,記錄訪問日誌,統計方法執行效率等等都可 以利用後置通知實現。

4.2.3、示例

切入點方法:
@Override
public User findById(String id) {
System.out.println("切入點方法開始執行。。。");
User user = new User();
user.setId(id);
user.setUsername("heima");
user.setNickname("黑馬小王子");
return user;
}
後置通知:
/**
* 後置通知
*/
@AfterReturning(value = "execution(* com.itheima.service.impl.*.*
(..))&&args(param)",returning = "user")
public void afterReturningLog(String param,Object user){
System.out.println("正常執行切入點方法後記錄日誌,切入點方法的引數
是:"+param);
System.out.println("正常執行切入點方法後記錄日誌,切入點方法的返回值
是:"+user);
}

4.3、@AfterThrowing

4.3.1、原始碼

/**
* After throwing advice
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
/**
* The pointcut expression where to bind the advice
*/
String value() default "";
/**
* The pointcut expression where to bind the advice, overrides
"value" when specified
*/
String pointcut() default "";
/**
* The name of the argument in the advice signature to bind the
thrown exception to
*/
String throwing() default "";
/**
* When compiling without debug info, or when interpreting pointcuts
at runtime,
* the names of any arguments used in the advice declaration are not
available.
* Under these circumstances only, it is necessary to provide the arg
names in
* the annotation ‐ these MUST duplicate the names used in the
annotated method.
* Format is a simple comma‐separated list.
*/
String argNames() default "";
}

4.3.2、說明

作用:

用於配置異常通知。

屬性:

value: 用於指定切入點表示式,可以是表示式,也可以是表示式的引用。

pointcut: 它的作用和value是一樣的。

throwing: 指定切入點方法執行產生異常時的異常物件變數名稱。它必須和異常變數 名稱一致。

argNames: 用於指定切入點表示式引數的名稱。它要求和切入點表示式中的引數名稱 一致。通常不指定也可以獲取切入點方法的引數內容。

使用場景:

用此註解修飾的方法執行時機是在切入點方法執行產生異常之後執行。

4.3.3、示例

切入點方法:
@Override
public User findById(String id) {
System.out.println("切入點方法開始執行。。。");
User user = new User();
user.setId(id);
user.setUsername("heima");
user.setNickname("黑馬小王子");
int i=1/0;
return user;
}
通知方法:
/**
* 異常通知
*/
@AfterThrowing(value = "execution(* com.itheima.service.impl.*.*
(..))&&args(param)",throwing = "e")
public void afterThrowingLog(String param,Throwable e){
System.out.println("執行切入點方法產生異常後記錄日誌,切入點方法的引數
是:"+param);
System.out.println("執行切入點方法產生異常後記錄日誌,切入點方法的異常
是:"+e);
}

4.4、@After

4.4.1、原始碼

/**
* After finally advice
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
/**
* The pointcut expression where to bind the advice
*/
String value();
/**
* When compiling without debug info, or when interpreting pointcuts
at runtime,
* the names of any arguments used in the advice declaration are not
available.
* Under these circumstances only, it is necessary to provide the arg
names in
* the annotation ‐ these MUST duplicate the names used in the
annotated method.
* Format is a simple comma‐separated list.
*/
String argNames() default "";
}

4.4.2、說明

作用:

用於指定最終通知。

屬性:

value: 用於指定切入點表示式,可以是表示式,也可以是表示式的引用。

argNames: 用於指定切入點表示式引數的名稱。它要求和切入點表示式中的引數名稱 一致。通常不指定也可以獲取切入點方法的引數內容。

使用場景:

最終通知的執行時機,是在切入點方法執行完成之後執行,無論切入點方法執行是 否產生異常最終通知都會執行。所以被此註解修飾的方法,通常都是做一些清理操作。

4.4.3、示例

/**
* 最終通知
*/
@After(value = "execution(* com.itheima.service.impl.*.*(..))")
public void afterLog(){
System.out.println("無論切入點方法執行是否有異常都記錄日誌");
}

4.5、@Around

4.5.1、原始碼

/**
* Around advice
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Around {
/**
* The pointcut expression where to bind the advice
*/
String value();
/**
* When compiling without debug info, or when interpreting pointcuts
at runtime,
* the names of any arguments used in the advice declaration are not
available.
* Under these circumstances only, it is necessary to provide the arg
names in
* the annotation ‐ these MUST duplicate the names used in the
annotated method.
* Format is a simple comma‐separated list.
*/
String argNames() default "";
}

4.5.2、說明

作用:

用於指定環繞通知。

屬性:

value: 用於指定切入點表示式,可以是表示式,也可以是表示式的引用。

argNames: 用於指定切入點表示式引數的名稱。它要求和切入點表示式中的引數名稱 一致。通常不指定也可以獲取切入點方法的引數內容。

使用場景:

環繞通知有別於前面介紹的四種通知型別。它不是指定增強方法執行時機的,而是 spring為我們提供的一種可以通過編碼的方式手動控制增強方法何時執行的機制。

4.5.3、示例

/**
* 環繞通知
*/
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object arountPrintLog(ProceedingJoinPoint pjp){
//1.定義返回值
Object rtValue = null;
try{
//前置通知
System.out.println("執行切入點方法前記錄日誌");
//2.獲取方法執行所需的引數
Object[] args = pjp.getArgs();
//3.執行切入點方法
rtValue = pjp.proceed(args);
//後置通知
System.out.println("正常執行切入點方法後記錄日誌");
}catch (Throwable t){
//異常通知
System.out.println("執行切入點方法產生異常後記錄日誌");
}finally {
//最終通知
System.out.println("無論切入點方法執行是否有異常都記錄日誌");
}
return rtValue;
}

5、用於擴充套件目標類的

5.1、@DeclareParents

5.1.1、原始碼

/**
* Declare parents mixin annotation
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DeclareParents {
/**
* The target types expression
*/
String value();
/**
* Optional class defining default implementation
* of interface members (equivalent to defining
* a set of interface member ITDs for the
* public methods of the interface).
*/
Class defaultImpl() default DeclareParents.class;
// note ‐ a default of "null" is not allowed,
// hence the strange default given above.
}

5.1.2、說明

作用:

用於給被增強的類提供新的方法。(實現新的介面)

屬性:

value: 用於指定目標型別的表示式。當在全限定類名後面跟上+時,表示當前類 及其子類

defaultImpl: 指定提供方法或者欄位的預設實現類。

使用場景:

當我們已經完成了一個專案的某個階段開發,此時需要對已完成的某個類加入一些 新的方法,我們首先想到的是寫一個介面,然後讓這些需要方法的類實現此介面,但是如果目 標類非常複雜,牽一髮而動全身,改動的話可能非常麻煩。此時就可以使用此註解,然後建一 個代理類,同時代理該類和目標類。

5.1.3、示例

已有業務層介面和實現類
/**
* 業務層介面
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
public interface UserService {
/**
* 模擬儲存使用者
* @param user
*/
void saveUser(User user);
}
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void saveUser(User user) {
System.out.println("執行了儲存使用者" + user);
}
}
需要加入的新方法:
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
public interface ValidateService {
boolean checkUser(User user);
}
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
public class ValidateServiceImpl implements ValidateService {
@Override
public boolean checkUser(User user) {
if(user.getNickname().contains("孫子")){
return false;
}
return true;
}
}
AOP切面類的配置
/**
* 記錄日誌的工具類
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Component
@Aspect
public class LogUtil {
@DeclareParents(value =
"com.itheima.service.UserService+",defaultImpl =
ValidateServiceImpl.class)
private ValidateService validateService;
/**
* 用於配置當前方法是一個前置通知
*/
@Before(value = "com.itheima.pointcuts.MyPointcut.pointcut1() &&
args(user) && this(validateService)")
public void printLog(User user,ValidateService validateService){
//第二種觸發方式
boolean check = validateService.checkUser(user);
if(check) {
System.out.println("執行列印日誌的功能");
}else {
throw new IllegalStateException("名稱非法");
}
}
}
spring核心配置類:
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
測試類:
/**
* 有兩種觸發方式:
* 第一種觸發方式:在使用時自行強轉新引入介面型別,然後呼叫方法。例如:測試
類中的程式碼
* 第二種觸發方式:在通知類中,使用this關鍵字,引入新目標類物件,呼叫方法
觸發。例如:切面類
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
public class SpringPointcutTest {
public static void main(String[] args) {
//1.建立容器
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.獲取物件
UserService userService =
ac.getBean("userService",UserService.class);
//3.執行方法
User user = new User();
user.setId("1");
user.setUsername("test");
user.setNickname("孫子1");
//第一種觸發方式
// ValidateService validateService = (ValidateService)userService;
// boolean check = validateService.checkUser(user);
// if(check) {
userService.saveUser(user);
// }
}
}

5.2、@EnableLoadTimeWeaving

5.2.1、原始碼

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LoadTimeWeavingConfiguration.class)
public @interface EnableLoadTimeWeaving {
/**
* Whether AspectJ weaving should be enabled.
*/
AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT;
/**
* AspectJ weaving enablement options.
*/
enum AspectJWeaving {
/**
* Switches on Spring‐based AspectJ load‐time weaving.
*/
ENABLED,
/**
* Switches off Spring‐based AspectJ load‐time weaving (even if a
* "META‐INF/aop.xml" resource is present on the classpath).
*/
DISABLED,
/**
* Switches on AspectJ load‐time weaving if a "META‐INF/aop.xml"
resource
* is present in the classpath. If there is no such resource,
then AspectJ
* load‐time weaving will be switched off.
*/
AUTODETECT;
}
}

5.2.2、說明

作用:

用於切換不同場景下實現增強。

屬性:

aspectjWeaving:是否開啟LTW的支援。

ENABLED 開啟LTW

DISABLED 不開啟LTW

AUTODETECT 如果類路徑下能讀取到META‐INF/aop.xml檔案,則開啟LTW,否則關 閉

使用場景:

在Java 語言中,從織入切面的方式上來看,存在三種織入方式:編譯期織入、類 載入期織入和執行期織入。編譯期織入是指在Java編譯期,採用特殊的編譯器,將切面織入 到Java類中;而類載入期織入則指通過特殊的類載入器,在類位元組碼載入到JVM時,織入切 面;執行期織入則是採用CGLib工具或JDK動態代理進行切面的織入。

AspectJ提供了兩種切面織入方式,第一種通過特殊編譯器,在編譯期,將AspectJ 語言編寫的切面類織入到Java類中,可以通過一個Ant或Maven任務來完成這個操作;第二種 方式是類載入期織入,也簡稱為LTW(Load Time Weaving)

5.2.3、示例

切面類:
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
//@Component
@Aspect
public class LoadTimeWeavingAspect {
/**
* 增強方法
* @param pjp
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
//1.建立秒錶物件
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
//2.記錄執行
sw.start(pjp.getSignature().getName());
//3.呼叫切入點方法並返回
return pjp.proceed();
} finally {
//4.停止計時
sw.stop();
//5.輸出
System.out.println(sw.prettyPrint());
}
}
/**
* 切入點表示式
*/
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void pointcut() {
}
}
配置類
/**
* @author 黑馬程式設計師
* @Company http://www.itheima.com
*/
@Configuration
@ComponentScan("com.itheima")
//@EnableAspectJAutoProxy
@EnableLoadTimeWeaving(aspectjWeaving =
EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
public class SpringConfiguration {
}

5.2.4、執行設定

五、AOP註解執行過程及核心物件的分析

3、切入點表示式總結

3.1、切入點表示式概念及作用

概念: 指的是遵循特定的語法用於捕獲每一個種類的可使用連線點的語法。

作用: 用於對符合語法格式的連線點進行增強。

3.2、按照用途分類

主要的種類:

方法執行:execution(MethodSignature)

方法呼叫:call(MethodSignature)

構造器執行:execution(ConstructorSignature)

構造器呼叫:call(ConstructorSignature)

類初始化:staticinitialization(TypeSignature)

屬性讀操作:get(FieldSignature)

屬性寫操作:set(FieldSignature)

例外處理執行:handler(TypeSignature)

物件初始化:initialization(ConstructorSignature)

物件預先初始化:preinitialization(ConstructorSignature)

支援的AspectJ切入點指示符如下:

execution:用於匹配方法執行的連線點;

within:用於匹配指定型別內的方法執行;

this:用於匹配當前AOP代理物件型別的執行方法;注意是AOP代理物件的型別匹配,這 樣就可能包括引入介面也型別匹配;

target:用於匹配當前目標物件型別的執行方法;注意是目標物件的型別匹配,這樣就 不包括引入介面也型別匹配;

args:用於匹配當前執行的方法傳入的引數為指定型別的執行方法;

@within:用於匹配所以持有指定註解型別內的方法;

@target:用於匹配當前目標物件型別的執行方法,其中目標物件持有指定的註解;

@args:用於匹配當前執行的方法傳入的引數持有指定註解的執行;

@annotation:用於匹配當前執行方法持有指定註解的方法;

bean:Spring AOP擴充套件的,AspectJ沒有對於指示符,用於匹配特定名稱的Bean物件的 執行方法;

reference pointcut:表示引用其他命名切入點,只有@ApectJ風格支援,Schema風 格不支援。

3.3、切入點表示式的萬用字元

3.4、切入點表示式的邏輯條件

&& and

|| or

! not