Spring容器包含兩個重要的特性:面向切面編程(AOP)和控制反轉(IOC),面向切面編程是面向物件(OOP)的一種補充,在面向物件編程的程序中編程針對的目標是一個個物件,而面向切面編程中編程針對的目標是一個個切面,切面支持跨型別跨物件(如事務的切面可以加在任何地方)進行模塊化,
前言
AOP是Spring的關鍵特性之一,雖然Spring的IOC特性并不依賴于AOP(意味著你可以只使用Spring的IOC特性而不使用AOP特性),但是二者結合起來可以靈活的實作很多中間件解決方案,比如我們經常使用的事務(@Transaction),就是通過AOP方案實作的,本文重點介紹AOP編程中的一些術語,這些術語不僅僅局限于Spring,它適用于所有的AOP編程,

- 切面(Aspect):面向切面編程可以跨類跨物件進行切面編程,一個切面就是對一類橫切關注點的模塊化,
- 切入點(JoinPoint):程式執行程序中的一個點,如方法呼叫、欄位訪問和例外拋出等,
- 增強(Advice):用于對切面增強,包含前增強、后增強和環繞增強,大多數AOP框架會對切入點進行攔截,并在切入點維護一個攔截器鏈,
- 目標物件(TargetObject):包含一個或者多個切面的物件,
- AOP代理(AOPProxy):通過Java動態代理或者CGLib增強得到的代理物件,
- 織入(Weaving):將切面整合到完整的流執行流程,
Spring的AOP的功能和目標
Spring的AOP使用純Java語言實作(如AspectJ就不是Java語言),不需要任何額外的編譯流程,不需要修改類加載器,適用于任何Servlet容器和應用服務,Spring的AOP只支持方法攔截,不支持欄位攔截,如果用戶需要使用欄位攔截,可以考慮引入AspectJ等類似的框架,
Spring的AOP框架和其它的框架有些不同,Spring的Aop框架不僅僅是為了提供一個AOP功能,它更重要的功能是和Spring的IOC容器結合,提供一些企業應用服務的解決方案(如事務等),我們可以和定義一個普通Bean一樣的方式去定以一個切面,Spring的AOP不支持非常細粒度的AOP,只支持對容器中的Bean進行AOP,如果需要更細粒度的AOP,可以考慮使用AspectJ,Spring容器的一個優秀的特性就是非侵入性,所以你可以靈活的選擇自己的AOP方案,不一定非要使用Spring的方案,
代理方式
Spring對實作介面的方法默認使用Java動態代理實作AOP攔截,對于非介面方法則會使用CGLIB位元組碼工具實作代理,
@AspectJ的支持
@AspectJ注解可以把一個普通的Java類宣告為切面,@AspectJ注解是AspectJ5引入的注解,Spring雖然可以讀取AspectJ5的注解用于切面元資料的配置,但是在運行的時候使用的仍然是Spring AOP進行代理,而沒有使用AspectJ的編譯器或者織入邏輯,我們會在后續討論如何在Spring中使用AspectJ的編譯器和織入邏輯,
啟用@AspectJ
Spring默認沒有啟用AspectJ,如果你需要Spring支持@AspectJ注解對應的切面,可以通過在配置類上添加注解或者使用XML啟用配置(AspectJ所在的包是:aspectjweaver.jar),
通過Java注解啟用AspectJ注解支持:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
通過XML配置啟用AspectJ注解
<aop:aspectj-autoproxy/>
定義一個切面
當啟用AspectJ支持之后,開發者定義的任何Aspect切面會自動地被檢測到,然后Spring AOP會對切面進行攔截,下面兩個例子展示了如何配置AspectJ切面,你可以通過添加@Component注解把切面Bean注冊到Spring容器中,
<bean id="myAspect" >
<!-- configure properties of the aspect here -->
</bean>
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
宣告一個切入點
切入點程式運行程序中我們感興趣的一個點,Spring的AOP框架只支持發現對Spring Bean方法上的切入點,因此你可以簡單的把切入點理解為SpringBean的方法,
切入點確定感興趣的連接點,從而使我們能夠控制何時運行通知,springaop只支持springbean的方法執行連接點,因此可以將切入點看作與springbean上方法的執行相匹配,切入點宣告由兩部分組成:一部分是由名稱和任何引陣列成的簽名,另一部分是確定我們感興趣的方法執行的切入點運算式,在AOP的@AspectJ注釋樣式中,切入點簽名由常規方法定義提供,切入點運算式由@pointcut注釋指示(用作切入點簽名的方法必須具有void回傳型別),切入點由兩部分組成,一部分是用于區別不同切入點的標識(下面例子中的private void anyOldTransfer() {})),另外一部分是確定我們感興趣的Bean方法的運算式(下面例子中的@Pointcut("execution(* transfer(..))")), 下面的例子展示了一個切入點的定義:
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
Spring匹配切入點的語法使用了AspectJ5中的運算式語法,我們可以參考AspectJ檔案相關的語法.
常見的切入點匹配表達
Spring支持下面常見的AspectJ切面定義語法:
- execution:用于匹配方法的連接點,
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
- within:用于匹配型別內的方法,
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}
- this:匹配當前AOP代理物件的執行方法
@target(org.springframework.transaction.annotation.Transactional)
- target:target匹配目標物件的型別,即被代理物件的型別,例如A繼承了B介面,則使用target("B"),target("A")均可以匹配到A
// 當前AOP物件實作了 IPointcutService介面的任何方法
@Pointcut("target(cn.javass.spring.chapter6.service.IPointcutService)")
private void anyPublicOperation() {}
- args:用于限定切點方法的引數型別,
args(java.io.Serializable)
- @target:被代理物件應該包含指定的注解,
@target(org.springframework.transaction.annotation.Transactional)
- @args: 被代理物件的引數包含指定的注解,
@args(com.xyz.security.Classified)
- @within: 被代理的物件應包含指定注解
@within(org.springframework.transaction.annotation.Transactional)
- @annotation:切入點包含指定的注解,
@annotation(org.springframework.transaction.annotation.Transactional)
我們可以通過“&&”和“||”對多個條件進行組合,AspectJ還有很多其它的運算式,但是Spring不支持除上述運算式以外的其它運算式,AspectJ其它運算式包含: call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this, @withincode等,
我們在使用Spring的代理方法之前,應該知道其代理原理,Java動態代理只能攔截public介面方法上的呼叫,CGLib只能攔截public、protected和defult方法,如果你需要更深層次的攔截,可以考慮使用底層的Aspectj,
切面的增強
我們在上面的步驟定義好了一個切入點,我們現在就可以對這個切入點進行額外操作,這些額外操作被稱為增強,Spring支持四種增強方式:前增強、后增強、例外增強和環繞增強,Spring支持在增強方法的定義上直接定義切入點,
前增強BeforeAdvice
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
后增強
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
例外增強
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
環繞增強
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
代理機制
我們前面說過,Spring AOP通過動態代理和CGLIB實作AOP物件的代理,我們可以通過如下配置設定動態代理全部走CGLIB,
<aop:config proxy-target->
<!-- other beans defined here... -->
</aop:config>
代理工廠的使用
Spring AOP實作代理的核心類是AspectJProxyFactory,我們可以使用這個類編程式生成代理物件:
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy()
本文最先發布至微信公眾號,著作權所有,禁止轉載!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/488705.html
標籤:其他
上一篇:C 語言變數
