相關文章:
一、簡介
面向切面編程英文全稱:Aspect-Oriented Programming (AOP)是面向對象編程(OOP)的補充。具體概念就不贅述。
1.1 AOP概念
我們首先學習一些AOP的核心概念和專業(yè)術語。這些并不是Spring特有的,不幸的是AOP的術語不是特別直觀,如果Spring用它自己的術語會更讓人難以理解。
- Aspect 切面。事務管理是一個很好的例子。在Spring里切面實現特殊類或者使用
@Aspect
注解的類。 - Join point 在Spring AOP中join point代表一個方法的執(zhí)行。
- Advice 特定Join point產生的通知。例如:"around","before","after",就像攔截器。
- Pointcut 一種匹配Join point的描述。Advice將會作用于所有符合Pointcut描述的Join point
- Introduction 聲明一些方法或者成員變量在某方面為一個類型。
Advice
的類型:
- Before advice:在一個Join point(以后都說方法)前執(zhí)行,但是它沒有能力阻止不執(zhí)行這個方法,除非它發(fā)生異常。
- After returning advice:當一個方法正常完成后執(zhí)行。
- After throwing advice:當一個方法拋出異常后執(zhí)行。
- After (finally) advice:當一個方法完成后執(zhí)行,不管正常還是異常。
- Around advice:在一個方法執(zhí)行前和執(zhí)行后調用。
二、@AspectJ支持
Spring的注解和AspectJ 5的一樣,但是沒有任何依賴AspectJ編譯器。
2.1 啟用@AspectJ支持
通過Java配置
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
通過XML配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<aop:aspectj-autoproxy />
2.2 聲明一個切面
首先創(chuàng)建一個類:
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
將它聲明為一個bean:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>
如果配置了自動掃描發(fā)現bean的方式也可以這樣:
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
@Component
public class NotVeryUsefulAspect {
}
切面可以有其他的方法和成員變量,和其他的類一樣,只是多了@Aspect
注解。
2.3 聲明一個切點
Spring AOP只支持在Spring beans的方法執(zhí)行聲明一個切點。有一個切點的聲明包含兩部分:一個方法簽名和一個切點表達式,準確的匹配哪些方法是我們感興趣的。在@AspectJ
注解風格的切面編程里,@Pointcut
注解表示切點(這個注解相關的方法必須使用void
返回類型)。
下面是一個例子:
@Pointcut("execution(* transfer(..))")// 切點表達式
private void anyOldTransfer() {}// 切點方法簽名
這個切點將會匹配所有名字為transfer
的方法(當然是在Spring beans里)。完整的切點語法規(guī)則參見AspectJ Programming Guide。
支持的切點標識符
切點標識符就是上面例子中:execution
部分,下面是支持的列表:
- execution 用來匹配方法執(zhí)行,這個將會是你在Spring AOP里主要用到的標識符
- within 只匹配指定類型(不太好理解,后面有例子,就是根據包名匹配)
- this 只匹配引用是給定類型的一個實例的方法(還是看后面的例子吧)
- target 只匹配目標對象是給定類型的一個實例的方法
- args 只匹配參數是給定類型實例的方法
@target
、@args
、@within
、@annotation
這些都是根據注解來匹配的。
注意,Spring AOP是一個基于代理的框架,被保護的方法不會被攔截,所以上面聲明的切點只會對public
方法起作用。
聯合切點表達式
可以使用&&
、||
、!
這些符號來聯合切點表達式。還可以通過切點的名字來引用切點。請看下面的例子:
@Pointcut("execution(public * (..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.someapp.trading..)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
anyPublicOperation
切點匹配所有public
的方法。
inTrading
切點匹配所有com.xyz.someapp.trading
包下的方法。
tradingOperation
切點是上面兩個切點的交集。
最佳實踐是用小的切點表達式來構成復雜的切點表達式,就像上面的例子。
一些通用的切點定義
在企業(yè)環(huán)境中,你會經常引用應用程序的各個模塊。我們推薦定義一個"系統架構"切面,捕獲通用的切點。一個典型的切面看起來像下面這個類:
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/
* 一個web層的切點,匹配任何在com.xyz.someapp.web包下的方法
*/
@Pointcut("within(com.xyz.someapp.web..)")
public void inWebLayer() {}
/
* 一個service層的切點,匹配任何在com.xyz.someapp.service包下的方法
*/
@Pointcut("within(com.xyz.someapp.service..)")
public void inServiceLayer() {}
/
* 一個數據訪問層的切點,匹配任何在com.xyz.someapp.dao包下的方法
*/
@Pointcut("within(com.xyz.someapp.dao..)")
public void inDataAccessLayer() {}
/
* 匹配在service包下的方法,例如com.xyz.someapp.user.service.addUser()這個方法
*/
@Pointcut("execution( com.xyz.someapp..service..(..))")
public void businessService() {}
/*
* 匹配在dao包下的方法
*/
@Pointcut("execution( com.xyz.someapp.dao..(..))")
public void dataAccessOperation() {}
}
2.4 一些列子
Spring AOP的用戶可能和切點表達式交往最頻繁。一個方法執(zhí)行表達式的格式為:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern) throws-pattern?)
?表示是可選的。
- modifiers-pattern? 修飾符部分
- ret-type-pattern 返回類型部分
- declaring-type-pattern? 聲明部分
- name-pattern 名稱部分
- param-pattern 參數部分
- throws-pattern? 異常拋出部分
*
表示匹配某任意部分。(..)
表示任意數量的參數。(*,String)
表示匹配參數數量是2,第一個參數是任意類型,第二個參數必須是String
的方法。
下面是一些常見的切點表達式:
- 任意公開方法的執(zhí)行
execution(public * *(..))
- 以set開頭的任意方法
execution(* set*(..))
- 定義在service包下的任意方法
execution(* com.xyz.service..(..))
- 定義在service包以及子包下的任意方法
execution(* com.xyz.service...(..))
- service包里的任意切點 (method execution only in Spring AOP)
within(com.xyz.service.*)
- 一個參數是Serializable的任意切點 (method execution only in Spring AOP)
args(java.io.Serializable)
- 切點匹配的方法上有@Transactional的注解
@annotation(org.springframework.transaction.annotation.Transactional)
譯者注:很多我也沒實踐過,以后實踐了再加些更通俗的解釋吧。
2.5 聲明通知
通知(Advice)是和切點表達式關聯的。在切點表達式匹配的方法之前、之后或者前后執(zhí)行。
前置通知
前置通知在一個切面里使用@Before
注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
com.xyz.myapp.SystemArchitecture
是上面的一個例子:
@Pointcut("execution( com.xyz.someapp.dao..(..))")
public void dataAccessOperation() {}
或者直接用切點的表達式:
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() {
// ...
}
}
方法返回后通知
如其字面意思,當一個匹配的方法執(zhí)行并正常返回后執(zhí)行。它使用@AfterReturning
注解聲明:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
注意:通知和切點完全可以寫在一個切面里。
有時你需要訪問方法返回的值。你可以使用下面這種形式綁定返回值:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
returning
屬性的名字必須與通知方法的參數名字保持一致。returning
項目還會約束方法返回值的類型(Object
在這里將會匹配任何返回值,如果是String
,將只會匹配String
類型的返回值)
方法拋出異常后通知
當切點匹配的方法拋出異常后執(zhí)行的通知。使用@AfterThrowing
注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
如果想只獲得指定類型的異常,可以使用throwing
屬性,和上面的returning
類似:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
后置通知
只要是一個切點匹配的方法退出了就會執(zhí)行方法后通知,不管是否成功返回。它使用@After
注解。你必須同時考慮到正常和異常的情況。一個典型的應用就是釋放資源等:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
環(huán)繞通知
環(huán)繞通知經常被用在需要在一個線程安全的方法執(zhí)行前后共享狀態(tài)。總是使用能滿足你需求的能力最弱的切面(例如,如果前置通知就可以完成的不要使用環(huán)繞通知)。
環(huán)繞通知使用@Around
注解來聲明。通知方法的第一個參數必須是ProceedingJoinPoint
類型。在通知方法體內調用ProceedingJoinPoint
的proceed()
方法會執(zhí)行切點所匹配的方法。proceed()
方法也可以接收一個Object[]
類型的參數給切點所匹配的方法。
譯者注:proceed()
方法之前相當于前置通知處理的范圍,proceed()
方法之后相當于后置通知處理的范圍。
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.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// retVal是方法的返回值
Object retVal = pjp.proceed();
return retVal;
}
}
訪問當前的切點
任何通知的方法都可以聲明一個org.aspectj.lang.JoinPoint
類型的參數作為它的第一個參數(環(huán)繞通知中的ProceedingJoinPoint
是JoinPoint
的子類,所以可以作為環(huán)繞通知的第一個參數)。JoinPoint
接口提供了很多實用的方法例如:
- getArgs() 返回切點匹配方法的參數
- getThis() 返回代理對象
- getTarget() 返回目標對象
- getSignature() 返回通知關聯的切點方法的簽名
未完,待續(xù)...