Spring筆記 - 面向切面編程AOP

1. 面向切面編程AOP

1.1 概述

- AOP是解耦的重要手段,讓業務實體專一於業務邏輯,其餘諸如安全驗證、日誌記錄等輔助功能經過切面的方式,切入到業務實體的執行過程。java

- 主流的AOP框架有:AspectJ、JBoss AOP、Spring AOPgweb

- Spring有本身實現的AOP,能夠覆蓋不少場景;也能夠和AspectJ集成,得到更強的功能。Spring號稱永遠不會提供一個強大的解決方案和AspectJ競爭。spring

[參考]
express


1.2 AOP術語

- Aspect: 切面,是切入目標對象後執行的方法以及所屬的對象,一般是非業務的輔助功能,能夠說是切點、通知、織入對象的組合
編程

- Joint Point: 鏈接點,是被織入目標能夠被織入的地方,一般是業務對象的方法、字段、異常等安全

- Advice: 通知,是切面應用在鏈接點的時機,包括around、before、afterapp

- Pointcut: 切點,符合AspectJ切點表達式的鏈接點集合,一般一個切面能夠做用於多個鏈接點
框架

- Weaving: 織入,把切面應用在鏈接點的過程,能夠是:函數

  • 編譯期compile time,例如AspectJ的織入編譯器測試

  • 加載器load time,例如AspectJ5的LTW

  • 運行時run time,例如Spring AOP等純Java AOP框架

- Introduction: 引入,爲被織入對象定義新的方法或字段


1.3 Spring AOP

- Spring提供了4種方式的AOP支持:

  • 基於代理的經典AOP,因爲繁瑣不多使用

  • 純POJO做爲切面,經過xml的spring aop schema定義

  • @AspectJ註解驅動,功能基本等同於aop schema;Spring支持部分AspectJ註解;注意:@Aspect註解加上component-scan機制,纔會被Spring容器發現並註冊

  • 注入式AspectJ切面,將AspectJ本地方法加載到Spring容器

- 若是織入的目標對象是接口,Spring AOP基於JDK動態代理進行運行時織入;不然使用CGLIB。

- Spring AOP的鏈接點只能是public方法;而經過Spring集成AspectJ能夠是private/protected方法或構造函數等。

1.3.1 切點

1.3.1.1 切點類型

- Spring AOP支持部分AspectJ的切點類型,且有本身擴展的類型

- Spring自動優化切點順序,但定義時儘可能縮小Spring的搜索範圍

- 切點類型[參考1][參考2][參考3]

  • execution:用於匹配符合表達式的鏈接點;execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

  • within:用於匹配指定類型(指定類型或類型模式)內的方法執行;

  • target:用於匹配目標對象(指定類型)的方法;注意是目標對象的類型匹配,這樣就不包括類型匹配的引入接口;

  • this:用於匹配AOP代理對象的方法;注意是AOP代理對象的類型匹配,這樣就能夠包括類型匹配的引入接口;

  • args:用於匹配當前執行的方法傳入的參數爲指定類型的方法;

  • @within:用於匹配因此持有指定註解類型內的方法,靜態匹配

  • @target:用於匹配當前目標對象類型的執行方法,其中目標對象持有指定的註解,運行時匹配,在Spring容器中,和@within同樣,在AspectJ中,區別很大;

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

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

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

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

- 例子

  • execution(public * *(..)) 全部公共方法

  • execution(* set*(..)) 全部名字以set開頭的方法

  • execution(* com.xyz.service.AccountService.*(..)) 全部AccountService接口方法的實現

  • execution(* com.xyz.service.*.*(..)) 全部service包裏的方法

  • execution(* com.xyz.service..*.*(..)) 全部service包及子包裏的方法

  • execution(* *(java.io.Serializable)) 參數類型可序列號,與args不一樣

  • within(com.xyz.service.*) 全部service包的鏈接點

  • this(com.xyz.service.AccountService) 鏈接點的代理實現了AccountService接口

  • target(com.xyz.service.AccountService) 目標對象實現了AccountService接口

  • args(java.io.Serializable) 運行時傳遞的參數可序列化

  • @target(org.springframework.transaction.annotation.Transactional) 目標對象加了@Transactional註解

  • @within(org.springframework.transaction.annotation.Transactional) 目標對象的聲明類型加了@Transactional註解

  • @annotation(org.springframework.transaction.annotation.Transactional) 方法加了@Transactional註解

  • @args(com.xyz.security.Classified) 運行時傳入的參數加了@Classified註解

  • bean(*Service)  Bean Id需以Service結尾

1.3.1.2 切點組合

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(x.y.z.trading..*)")
private void inTrading() {}
//and方式組合切點,還能夠用||、!
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
<!-- 基於schema的定義沒法實現切點組合,只能組合表達式 -->
<aop:pointcut id="tradingOperation" expression="execution(public * *(..)) **and** within(x.y.z.trading..*)"/>

1.3.1.3 切點重用

// 定義切點
@Aspect
@Component
public class SystemArchitecture {
    @Pointcut("within(x.y.z.web..*)")
    public void inWebLayer() {}
    @Pointcut("within(x.y.z.service..*)")
    public void inServiceLayer() {}
}
// 引用切點
@Aspect
@Component
public class WebProcessor{
    @Before(pointcut="x.y.z.SystemArchitecture.inWebLayer()")
    public void inWebLayer() {}
    @Pointcut("within(x.y.z.service..*)")
    public void doSth() { /* ... */ }
}
<aop:config>
    <aop:pointcut id="inWebLayer" expression="within(x.y.z.web..*)"/>
    <aop:aspect id="webProcessorAspect" ref="webProcessor">
        <aop:before method="doSth" pointcut-ref="inWebLayer" />        
    </aop:aspect>
</aop:config>

1.3.2 通知

1.3.2.1 通知類型

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")

@AfterReturning(
    pointcut="x.y.z.SystemArchitecture.dataAccessOperation()",
    returning="retVal")
public void doAccessCheck(Object retVal) { /* ... */ }
    
@AfterThrowing(
    pointcut="x.y.z.SystemArchitecture.dataAccessOperation()",
    throwing="ex")
public void doRecoveryActions(DataAccessException ex) { /* ... */ }

//After (finally)
@After("x.y.z.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() { /* ... */ }

@Around("x.y.z.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // 開始計時 ...
    Object retVal = pjp.proceed();
    // 中止計時 ...
    return retVal;
}
<aop:after-throwing pointcut="..." throwing="ex" method="doRecoveryActions"/>

13.2.2 傳遞參數到通知

// 前面AfterReturning能夠returning參數、AfterThrowing能夠throwing參數,此外,還能夠運用args爲切點、通知和切面指定參數

@Pointcut("x.y.z.SystemArchitecture.dataAccessOperation() && args(Account,int)")
private void accountDataAccessOperation(Account account, int age) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) { /* ... */}
<aop:pointcut id="accountDataAccessOperation" expression=""x.y.z.SystemArchitecture.dataAccessOperation(Account,int) and args(account,age)" />
<aop:before pointcut="accountDataAccessOperation" method="validateAccount" arg-names="account,age"/>

13.2.3 傳遞參數到鏈接點

@Around("x.y.z.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    Object retVal = pjp.proceed(new Object[] {"some arg"});
    return retVal;
}

1.3.3 引入Introduction

引入是利用代理爲被代理的Bean加入新的方法或字段

// 前提:被代理的Bean
public class UserService {
    public void saveUser(User user) { /* ... */ }
    // 其餘方法和字段略
}
// 任務:要在saveUser以前檢查user是否知足條件
// a. 定義Checker接口
public interface Checker {
    public boolean check(User user);
}
// b. 實現Checker接口
public class UserChecker implements Checker {
    public boolean check(User user) { /* ... */ }
}
// c. 定義切面,進行引入
@Aspect
@Component
public class CheckerAspect {
    @DeclareParents(
        value="x.y.z.UserService", 
        defaultImpl=x.y.z.UserChecker.class)
    public UserChecker userChecker;
}
// d. 測試
UserService userService = (UserService)ctx.getBean("userService");
UserChecker userChecker = (UserChecker)userService;
if(userChecker.check(user)
    userService.saveUser(user);
<aop:aspect id="checkerAspect" ref="checkerAspect">
    <aop:declare-parents 
        type-matching="x.y.z.UserService"
        implement-interface="x.y.z.Checker"
        default-impl="x.y.z.UserChecker" />
    <aop:before pointcut="x.y.z.UserService.saveUser()" method="check" />
</aop:aspect>

1.3.4 打開AspectJ auto-proxying自動代理

讓Spring容器自動爲被代理類建立代理並織入切面

/JavaConfig方式
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=false) //默認爲false,爲true時使用CGLib動態代理技術織入加強。不過即便proxy-target-class設置爲false,若是目標類沒有聲明接口,則spring將自動使用CGLib動態代理
@ComponentScan
public class ConcertConfig {
    @Bean
    public Audience audience() {
        return new Audience();
    }
}

@Aspect
public class Audience {
    // 定義pointcut和advice
}
<!-- xml方式 -->
<beans ...>
    <context:component-scan base-package="x.y.z" />
    <aop:aspectj-autoproxy poxy-target-class="false"/>
    <bean class="Audience />
</bean>

1.3.5 Advisors

Advisor是advice和pointcut的組合,只包含一個advice,advice實現了各類Advice接口,典型應用例如tx-advice、cache-advice

<aop:config>
    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>
    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
相關文章
相關標籤/搜索