AOP的基本概念
建議先閱讀文章
- Advice(通知、切面): 某個連接點所采用的處理邏輯,也就是向連接點注入的代碼, AOP在特定的切入點上執行的增強處理。
@Before
: 標識一個前置增強方法,相當於BeforeAdvice的功能.@After
: final增強,不管是拋出異常或者正常退出都會執行.@AfterReturning
: 后置增強,似於AfterReturningAdvice, 方法正常退出時執行.@AfterThrowing
: 異常拋出增強,相當於ThrowsAdvice.@Around
: 環繞增強,相當於MethodInterceptor.
- JointPoint(連接點):程序運行中的某個階段點,比如方法的調用、異常的拋出等。
- Pointcut(切入點): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么樣的條件下才能被觸發,在程序中主要體現為書寫切入點表達式。
- Advisor(增強): 是PointCut和Advice的綜合體,完整描述了一個advice將會在pointcut所定義的位置被觸發。
- @Aspect(切面): 通常是一個類的注解,里面可以定義切入點和通知
- AOP Proxy:AOP框架創建的對象,代理就是目標對象的加強。Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基於接口,后者基於子類。
<aop:aspectj-autoproxy/> // 開啟自動代理
<aop:config proxy-target-class="true">
<aop:pointcut id="servicePointcut" expression="execution(* com.cpic..*Service.*(..))" />
<aop:advisor pointcut-ref="servicePointcut" advice-ref="txAdvice" order="3" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="list*" read-only="true" />
<!-- log方法會啟動一個新事務 -->
<tx:method name="log*" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" />
</tx:attributes>
</tx:advice>
// OK所以一個Spring增強(advisor)=切面(advice)+切入點(PointCut)
Pointcut(切入點)
表示式(expression)和簽名(signature)
//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point簽名
private void log(){}
由下列方式來定義或者通過 &&、 ||、 !、 的方式進行組合:
- execution:用於匹配方法執行的連接點;
- within:用於匹配指定類型內的方法執行;
- this:用於匹配當前AOP代理對象類型的執行方法;注意是AOP代理對象的類型匹配,這樣就可能包括引入接口也類型匹配;
- target:用於匹配當前目標對象類型的執行方法;注意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配;
- args:用於匹配當前執行的方法傳入的參數為指定類型的執行方法;
- @within:用於匹配所以持有指定注解類型內的方法;
- @target:用於匹配當前目標對象類型的執行方法,其中目標對象持有指定的注解;
- @args:用於匹配當前執行的方法傳入的參數持有指定注解的執行;
- @annotation:用於匹配當前執行方法持有指定注解的方法;
execution格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
其中后面跟着“?”的是可選項
括號中各個pattern分別表示:
- 修飾符匹配(modifier-pattern?)
- 返回值匹配(ret-type-pattern): 可以為*表示任何返回值, 全路徑的類名等
- 類路徑匹配(declaring-type-pattern?)
- 方法名匹配(name-pattern):可以指定方法名 或者 代表所有, set 代表以set開頭的所有方法
- 參數匹配((param-pattern)):可以指定具體的參數類型,多個參數間用“,”隔開,各個參數也可以用"*" 來表示匹配任意類型的參數,".."表示零個或多個任意參數。
如(String)表示匹配一個String參數的方法;(*,String) 表示匹配有兩個參數的方法,第一個參數可以是任意類型,而第二個參數是String類型。 - 異常類型匹配(throws-pattern?)
execution例子
- 任意公共方法的執行:execution(public * *(..))
- 任何一個以“set”開始的方法的執行:execution(* set*(..))
- AccountService 接口的任意方法的執行:execution(* com.xyz.service.AccountService.*(..))
- 定義在service包里的任意方法的執行: execution(* com.xyz.service..(..))
- 定義在service包和所有子包里的任意類的任意方法的執行:execution(* com.xyz.service...(..))
第一個表示匹配任意的方法返回值, ..(兩個點)表示零個或多個,第一個..表示service包及其子包,第二個表示所有類, 第三個*表示所有方法,第二個..表示方法的任意參數個數 - 定義在pointcutexp包和所有子包里的JoinPointObjP2類的任意方法的執行:execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")
- pointcutexp包里的任意類: within(com.test.spring.aop.pointcutexp.*)
- pointcutexp包和所有子包里的任意類:within(com.test.spring.aop.pointcutexp..*)
- 實現了Intf接口的所有類,如果Intf不是接口,限定Intf單個類:this(com.test.spring.aop.pointcutexp.Intf)
當一個實現了接口的類被AOP的時候,用getBean方法必須cast為接口類型,不能為該類的類型 - 帶有@Transactional標注的所有類的任意方法:
- @within(org.springframework.transaction.annotation.Transactional)
- @target(org.springframework.transaction.annotation.Transactional)
- 帶有@Transactional標注的任意方法:@annotation(org.springframework.transaction.annotation.Transactional)
@within和@target針對類的注解,@annotation是針對方法的注解 - 參數帶有@Transactional標注的方法:@args(org.springframework.transaction.annotation.Transactional)
- 參數為String類型(運行是決定)的方法: args(String)
JoinPoint
常用的方法:
- Object[] getArgs:返回目標方法的參數
- Signature getSignature:返回目標方法的簽名
- Object getTarget:返回被織入增強處理的目標對象
- Object getThis:返回AOP框架為目標對象生成的代理對象
當使用@Around處理時,需要將第一個參數定義為ProceedingJoinPoint類型,該類是JoinPoint的子類。
織入
@AfterReturning(pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)",
returning="returnValue")
public void access(Date time, Object returnValue, String name) {
System.out.println("目標方法中的參數String = " + name);
System.out.println("目標方法中的參數Date = " + time);
System.out.println("目標方法的返回結果returnValue = " + returnValue);
}
表達式中增加了args(time, name)部分,意味着可以在增強處理的簽名方法(access方法)中定義"time"和"name"兩個屬性。這兩個形參的類型可以隨意指定(access方法中指定),但一旦指定了這兩個參數的類型,則這兩個形參類型將用於限制該切入點只匹配第一個參數類型為Date,第二個參數類型為String的方法(方法參數個數和類型若有不同均不匹配);access方法只需要滿足"time", "name"參數的順序和pointcut中args(param1, param2)的順序相同即可,"returnValue"位置順序無所謂。
//將被access方法匹配
public String accessAdvice(Date d, String n) {
System.out.println("方法:accessAdvice");
return "aa";
}
切面執行順序
一個方法只被一個Aspect類攔截
- 正常:
異常:
同一個方法被多個Aspect類攔截
優先級高的切面類里的增強處理的優先級總是比優先級低的切面類中的增強處理的優先級高。在“進入”連接點時,最高優先級的增強處理將先被織入(eg.給定的兩個不同切面類Before增強處理中,優先級高的那個會先執行);在“退出”連接點時,最高優先級的增強處理會最后被織入(eg.給定的兩個不同切面類After增強處理中,優先級高的那個會后執行)。
eg.優先級為1的切面類Bean1包含了@Before,優先級為2的切面類Bean2包含了@Around,雖然@Around優先級高於@Before,但由於Bean1的優先級高於Bean2的優先級,因此Bean1中的@Before先被織入。
Spring提供了如下兩種解決方案指定不同切面類里的增強處理的優先級:
- 讓切面類實現org.springframework.core.Ordered接口:實現該接口的int getOrder()方法,該方法返回值越小,優先級越高
- 直接使用@Order注解來修飾一個切面類:使用這個注解時可以配置一個int類型的value屬性,該屬性值越小,優先級越高
同一個切面類里的兩個相同類型的增強處理在同一個連接點被織入時,Spring AOP將以隨機的順序來織入這兩個增強處理,沒有辦法指定它們的織入順序。即使給這兩個 advice 添加了 @Order 這個注解,也不行!
eg.
spring的xml開啟AOP配置:
<aop:config proxy-target-class="false" />
<aop:aspectj-autoproxy />
<bean id="opLogAspectj" class="com.noob.aspectj.OpLogAspectj"></bean>
package com.noob.aspectj;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import com.noob.annotation.OpLog;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OpLog {
/**
* 操作類型
*/
int opModule();
/**
* 是否批量
*/
String batch();
/**
* 操作來源(頁面+操作)
*/
String source();
}
——————————————————————————————————————————————————————————————————————————————————————————————
@Aspect
@Slf4j
public class OpLogAspectj {
@Pointcut(value = "@annotation(com.noob.annotation.OpLog)")
public void methodPointcut(){}
/**
* 對帶注解@OpLog的方法進行切面,並獲取到注解的屬性值
*/
@Around(value= "methodPointcut() && @annotation(opLog)" , argNames="opLog")
public Object around(ProceedingJoinPoint point, OpLog opLog) throws Throwable{
Object obj = null;
Object[] args = point.getArgs();
try {
obj = point.proceed(args);
} catch (Throwable e) {
log.error("方法執行異常", e);
}
long endTime = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) point.getSignature();
String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
return obj;
}
@Pointcut(value = "@annotation(org.apache.shiro.authz.annotation.RequiresPermissions)")
public void methodPointcut2(){}
/**
* 配置指定位置指定類型的參數
*/
@Around(value= "methodPointcut2() && (args(request, ..) || args(.., request))")
public Object around2(ProceedingJoinPoint point, HttpServletRequest request) throws Throwable{
Object obj = null;
Object[] args = point.getArgs();
try {
SsoUser ssoUser = SsoSession.getCurrentUser(request);
obj = point.proceed(args);
} catch (Throwable e) {
log.error("方法執行異常", e);
}
return obj;
}
}
進入切面的兩種方法:
@OpLog(batch = YesOrNo.NO, source = FuncPointEnum.GROUP_ADD)
public Response<Integer> add(RuleGroupModifyReq addModel) {
// 業務邏輯
}
——————————————————————————————————————————————————————————————————————————————————————————————
@RequestMapping(value = "/delete")
@ResponseBody
@RequiresPermissions("rule:delete")
public Response<Integer> delete(
@RequestBody Map<String, String> params,
HttpServletRequest request) {
// 業務邏輯
}