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: 織入,把切面應用在鏈接點的過程,能夠是:函數
|
- Introduction: 引入,爲被織入對象定義新的方法或字段
1.3 Spring AOP
- Spring提供了4種方式的AOP支持:
|
- 若是織入的目標對象是接口,Spring AOP基於JDK動態代理進行運行時織入;不然使用CGLIB。
- Spring AOP的鏈接點只能是public方法;而經過Spring集成AspectJ能夠是private/protected方法或構造函數等。
1.3.1 切點
1.3.1.1 切點類型
- Spring AOP支持部分AspectJ的切點類型,且有本身擴展的類型
- Spring自動優化切點順序,但定義時儘可能縮小Spring的搜索範圍
|
- 例子
|
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>