AOP
AOP 提供一種新的思考程式結構的方法來補充 OOP。OOP 中子產品的關鍵是類,而 AOP 中子產品的關鍵是切面。切面支援跨多個類型和對象的子產品化(如事務管理)。
AOP 概念
- 切面 —— 在切入點進行通知操作的過程(包含通知和切人點的類 @Aspect)
- 連接配接點 —— 所有可能被織入通知的候選點(具體業務邏輯方法)
- 切入點 —— 滿足比對規則的連接配接點(@Pointcut)
- 目标對象 —— 被一到多個切面通知的對象
- AOP 代理 —— AOP 架構基于切面規則建立的對象
- 織入 —— 建立通知對象關聯切面和其他應用
- 通知 —— 對切入點進行的操作
- 前置通知 —— 在連接配接點之前執行的通知(@Before)
- 後置通知 —— 在連接配接點正常完成後執行的通知(@AfterReturning)
- 環繞通知 —— 在方法調用前後執行的通知(@Around)
- 異常通知 —— 方法抛出異常時執行的通知(@AfterThrowing)
- 最終通知 —— 從連接配接點退出後執行的通知(@After)
Spring AOP 目标
- 純 Java 實作。不需要特殊的編譯過程。不需要控制類加載器層次結構,适合在 Servlet 容器或應用程式伺服器中使用。
- 隻支援方法執行連接配接點。如果需要字段攔截和更新連接配接點,考慮 AspectJ 之類的語言。
- Spring AOP 不是提供最完整的 AOP 實作。其目标是提供 AOP 實作和 Spring IoC 之間的緊密內建。
AOP 代理
- Spring AOP 預設使用标準 JDK 動态代理實作 AOP 代理。
- Spring AOP 還可以使用CGLIB代理。
@Aspect 風格
- 激活 @AspectJ 支援 —— 通過自動代理,Spring 自動為被通知的 Bean 生成代理來攔截方法調用,確定按需執行通知
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
- 定義一個切面 —— 在應用程式上下文中使用 @AspectJ 類定義 Bean 會被 Spring 自動檢測到,并用于配置 Spring AOP
@Aspect
public class NotVeryUsefulAspect {
}
- 定義一個切入點 —— 确定感興趣的連接配接點,進而使我們能夠控制何時執行通知。
@Pointcut("execution(* transfer(..))")// 切入點表達式
private void anyOldTransfer() {}
- execution —— 用于比對方法執行連接配接點
- within —— 将比對限制為特定類型中的連接配接點
- this —— 将比對限制為 Bean 引用(AOP 代理)是給定類型執行個體的連接配接點
- target —— 将比對限制為目标對象是給定類型執行個體的連接配接點
- args —— 将比對限制為參數是給定類型執行個體的連接配接點
- @target —— 将比對限制為執行對象的類具有給定類型注解的連接配接點
- @args —— 将比對限制為傳遞的實際參數的運作時類型具有給定類型注解的連接配接點
- @within —— 将比對限制為與具有給定注解類型的連接配接點
- @annotation —— 将比對限制為連接配接點的主題具有給定類型注釋的連接配接點
@Aspect
public class SystemArchitecture {
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
execution(修飾符? 傳回類型 包名?方法名稱(參數類型) 異常類型?)
//攔截任何公共方法的執行
execution(public * *(..))
//攔截任何以 set 開頭的方法的執行
execution(* set*(..))
//攔截任何定義在 AccountService 接口中的方法的執行
execution(* com.xyz.service.AccountService.*(..))
//攔截任何定義在 service 包中的方法的執行
execution(* com.xyz.service.*.*(..))
//攔截任何定義在 service 包或者其一個子包中的方法的執行
execution(* com.xyz.service..*.*(..))
//攔截 service 包中的連接配接點
within(com.xyz.service.*)
//攔截 service 包或者其一個子包中的連接配接點
within(com.xyz.service..*)
//攔截實作 AccountService 接口的代理的連接配接點
this(com.xyz.service.AccountService)
//攔截實作 AccountService 接口的目标對象的連接配接點
target(com.xyz.service.AccountService)
//攔截參數隻有一個 Serializable 的連接配接點
args(java.io.Serializable)
//攔截目标對象有 @Transactional 注解的連接配接點
@target(org.springframework.transaction.annotation.Transactional)
//攔截目标對象的聲明類型有 @Transactional 注解的連接配接點
@within(org.springframework.transaction.annotation.Transactional)
//攔截執行方法有 @Transactional 注解的連接配接點
@annotation(org.springframework.transaction.annotation.Transactional)
//攔截參數隻有一個 @Classified 注解的連接配接點
@args(com.xyz.security.Classified)
- 定義通知 —— 與切入點表達式相關聯,并在比對的方法執行之前、之後或前後運作
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
- JoinPoint 參數 —— ProceedingJoinPoint 是 JoinPoint 的子類
- getArgs(): 傳回方法參數
- getThis(): 傳回代理對象
- getTarget(): 傳回目标對象
- getSignature(): 傳回被通知方法的描述
- toString(): 列印被通知方法的描述
- 傳遞參數給通知
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
//第一參數是 JoinPoint, ProceedingJoinPoint, 或者 JoinPoint.StaticPart 類型,argNames 中可以省略
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
代理機制
- Spring AOP 使用 JDK 動态代理或 CGLIB 為給定的目标對象建立代理。
- 如目标對象實作至少一個接口,則使用 JDK 動态代理。如目标對象沒有實作任何接口,則建立一個 CGLIB 代理。
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
API
Pointcut
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
public interface ClassFilter {
boolean matches(Class clazz);
}
public interface MethodMatcher {
boolean matches(Method m, Class targetClass);
boolean isRuntime();
boolean matches(Method m, Class targetClass, Object[] args);
}