Aspect Oriented Programming with Springjava
AOP是與OOP不一樣的一種程序結構。在OOP編程中,模塊的單位是class(類);然而,在AOP編程中模塊的單位是aspect(切面)。也就是說,OOP關注的是類,而AOP關注的是切面。web
Spring AOP是用純Java實現的。目前,只支持方法執行級別的鏈接點。spring
Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.
Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.express
對於AOP代理,Spring AOP默認使用JDK動態代理。這意味着任意接口均可以被代理。編程
Spring AOP也能夠用CGLIB代理。CGLIB代理的是類,而不是接口。若是一個業務對象沒有實現一個接口,那麼默認用CGLIB代理。這是一種很好的實踐,面向接口編程,而不是面向類;業務類一般會實現一個或者多個業務接口。能夠強制使用CGLIB代理,這種狀況下你須要通知一個方法而不是一個接口,你須要傳遞一個代理對象而不是一個具體的類型給一個方法。數組
@AspectJ是一種聲明切面的方式(或者說風格),它用註解來標註標準的Java類。架構
autoproxying(自動代理)意味着若是Spring檢測到一個Bean被一個或者多個切面通知,那麼它將自動爲這個Bean生成一個代理以攔截其上的方法調用,而且確保通知被執行。app
可使用XML或者Java方式來配置以支持@AspectJ。爲此,你須要aspectjweaver.jarthis
爲了使@AspectJ生效,須要用@Configuration和@EnableAspectJAutoProxy註解spa
1 @Configuration 2 @EnableAspectJAutoProxy 3 public class AppConfig { 4 5 }
爲了使@AspectJ生效,須要用到aop:aspectj-autoproxy元素
1 <aop:aspectj-autoproxy/>
下面的例子顯示了定義一個最小的切面:
首先,定義一個標準的Java Bean
1 <bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"> 2 <!-- configure properties of aspect here as normal --> 3 </bean>
其次,用org.aspectj.lang.annotation.Aspect註解標註它
1 package org.xyz; 2 import org.aspectj.lang.annotation.Aspect; 3 4 @Aspect 5 public class NotVeryUsefulAspect { 6 7 }
一個切面(PS:被@Aspect註解標註的類)能夠向其它的類同樣有方法和字段。這些方法可能包含切點、通知等等。
經過組件掃描的方式自動偵測切面
你可能在XML配置文件中註冊一個標準的Bean做爲切面,或者經過classpath掃描的方式自動偵測它,就像其它被Spring管理起來的Bean那樣。爲了可以在classpath下自動偵測,你須要在在切面上加@Component註解。
In Spring AOP, it is not possible to have aspects themselves be the target of advice from other aspects. The @Aspect annotation on a class marks it as an aspect, and hence excludes it from auto-proxying.
在Spring AOP中,不可能有切面本身自己還被其它的切面做爲目標通知。用@Aspect註解標註一個類做爲切面,所以須要將它本身自己從自動代理中排除。
什麼意思呢?舉個例子,好比
package com.cjs.aspect
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.cjs..*(..))")
public void pointcut() {}
}
在這個例子中,切面LogAspect所在的位置是com.cjs.aspect,而它的切入點是com.cjs下的全部的包的所類的全部方法,這其中就包含LogAspect,這是不對的,會形成循環依賴。在SpringBoot中這樣寫的話啓動的時候就會報錯,會告訴你檢測到循環依賴。
切入點是用來控制何時執行通知的,簡單地來說,就是什麼樣的方法會被攔截。Spring AOP目前只支持方法級別的鏈接點。
一個切入點聲明由兩部分組成:第一部分、由一個名稱和任意參數組成的一個簽名;第二部分、一個切入點表達式。
在@AspectJ註解方式的AOP中,一個切入點簽名就是一個標準的方法定義,而切入點表達式則是由@Pointcut註解來指明的。
(做爲切入點簽名的方法的返回值類型必須是void)
下面是一個簡單的例子:
1 @Pointcut("execution(* transfer(..))")// the pointcut expression 2 private void anyOldTransfer() {}// the pointcut signature
對於JDK代理,只有public的接口方法調用的時候纔會被攔截。對於CGLIB,public和protected的方法調用將會被攔截。
Pointcut expressions can be combined using '&&', '||' and '!'
請看下面的例子:
1 @Pointcut("execution(public * *(..))") 2 private void anyPublicOperation() {} 3 4 @Pointcut("within(com.xyz.someapp.trading..*)") 5 private void inTrading() {} 6 7 @Pointcut("anyPublicOperation() && inTrading()") 8 private void tradingOperation() {}
在企業應用開發過程當中,你可能想要常常對一下模塊應用一系列特殊的操做。咱們推薦你定義一個「系統架構」層面的切面用來捕獲公共的切入點。下面是一個例子:
1 package com.xyz.someapp; 2 3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.Pointcut; 5 6 @Aspect 7 public class SystemArchitecture { 8 9 /** 10 * 匹配定義在com.xyz.someapp.web包或者子包下的方法 11 */ 12 @Pointcut("within(com.xyz.someapp.web..*)") 13 public void inWebLayer() {} 14 15 /** 16 * 匹配定義在com.xyz.someapp.service包或者子包下的方法 17 */ 18 @Pointcut("within(com.xyz.someapp.service..*)") 19 public void inServiceLayer() {} 20 21 /** 22 * 匹配定義在com.xyz.someapp.dao包或者子包下的方法 23 */ 24 @Pointcut("within(com.xyz.someapp.dao..*)") 25 public void inDataAccessLayer() {} 26 27 /** 28 * 匹配定義在com.xyz.someapp下任意層級的service包下的任意類的任意方法 29 */ 30 @Pointcut("execution(* com.xyz.someapp..service.*.*(..))") 31 public void businessService() {} 32 33 /** 34 * 匹配定義在com.xyz.someapp.dao包下的全部類的全部方法 35 */ 36 @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") 37 public void dataAccessOperation() {} 38 39 }
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
通配符*表示任意字符,(..)表示任意參數
下面是一些例子
execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
within(com.xyz.service.*)
within(com.xyz.service..*)
this(com.xyz.service.AccountService)
args(java.io.Serializable)
@target(org.springframework.transaction.annotation.Transactional)
@within(org.springframework.transaction.annotation.Transactional)
@annotation(org.springframework.transaction.annotation.Transactional)
@args(com.xyz.security.Classified)
bean(tradeService)
bean(*Service)
1 import org.aspectj.lang.annotation.Aspect; 2 import org.aspectj.lang.annotation.Before; 3 4 @Aspect 5 public class BeforeExample { 6 7 @Before("execution(* com.xyz.myapp.dao.*.*(..))") 8 public void doAccessCheck() { 9 // ... 10 } 11 12 }
1 import org.aspectj.lang.annotation.Aspect; 2 import org.aspectj.lang.annotation.AfterReturning; 3 4 @Aspect 5 public class AfterReturningExample { 6 7 @AfterReturning( 8 pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", 9 returning="retVal") 10 public void doAccessCheck(Object retVal) { 11 // ... 12 } 13 14 }
1 import org.aspectj.lang.annotation.Aspect; 2 import org.aspectj.lang.annotation.AfterThrowing; 3 4 @Aspect 5 public class AfterThrowingExample { 6 7 @AfterThrowing( 8 pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", 9 throwing="ex") 10 public void doRecoveryActions(DataAccessException ex) { 11 // ... 12 } 13 14 }
1 import org.aspectj.lang.annotation.Aspect; 2 import org.aspectj.lang.annotation.After; 3 4 @Aspect 5 public class AfterFinallyExample { 6 7 @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") 8 public void doReleaseLock() { 9 // ... 10 } 11 12 }
環繞通知用@Around註解來聲明,第一個參數必須是ProceedingJoinPoint類型的。在通知內部,調用ProceedingJoinPoint的proceed()方法形成方法執行。
1 import org.aspectj.lang.annotation.Aspect; 2 import org.aspectj.lang.annotation.Around; 3 import org.aspectj.lang.ProceedingJoinPoint; 4 5 @Aspect 6 public class AroundExample { 7 8 @Around("com.xyz.myapp.SystemArchitecture.businessService()") 9 public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { 10 // start stopwatch 11 Object retVal = pjp.proceed(); 12 // stop stopwatch 13 return retVal; 14 } 15 16 }
若是用戶明確指定了參數名稱,那麼這個指定的名稱能夠用在通知中,經過argNames參數來指定:
@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 }
Spring AOP用JDK動態代理或者CGLIB來爲給定的目標對象建立代理。(不管你怎麼選擇,JDK動態代理都是首選)
若是目標對象實現了至少一個接口,那麼JDK動態代理將會被使用。
目標對象實現的全部接口都會被代理。
若是目標對象沒有實現任何接口,那麼使用CGLIB代理。
若是你強制用CGLIB代理,那麼下面這些問題你須要注意:
爲了強制使用CGLIB代理,須要在<aop:config>中的proxy-target-class屬性設置爲true
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
當使用@AspectJ自動代理的時候強制使用CGLIB代理,須要將<aop:aspectj-autoproxy>的proxy-target-class屬性設置爲true
<aop:aspectj-autoproxy proxy-target-class="true"/>
Spring AOP是基於代理的。這一點極其重要。
考慮下面的代碼片斷
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) { Pojo pojo = new SimplePojo(); // this is a direct method call on the 'pojo' reference pojo.foo(); } }
若是引用對象有一個代理,那麼事情變得不同了。請考慮下面的代碼片斷。
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(); } }
上面的代碼中,爲引用對象生成了一個代理。這就意味着在引用對象上的方法調用會傳到代理上的調用。
有一點須要注意,調用同一個類中的方法時,被調用的那個方法不會被代理。也就是說調用foo()的時候是攔截不到bar()的。
package foo; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Pointcut; import org.springframework.util.StopWatch; import org.springframework.core.annotation.Order; @Aspect public class ProfilingAspect { @Around("methodsToBeProfiled()") public Object profile(ProceedingJoinPoint pjp) throws Throwable { StopWatch sw = new StopWatch(getClass().getSimpleName()); try { sw.start(pjp.getSignature().getName()); return pjp.proceed(); } finally { sw.stop(); System.out.println(sw.prettyPrint()); } } @Pointcut("execution(public * foo..*.*(..))") public void methodsToBeProfiled(){} }
1 @Pointcut("@within(com.cjs.log.annotation.SystemControllerLog) " + 2 "|| @within(com.cjs.log.annotation.SystemRpcLog) " + 3 "|| @within(com.cjs.log.annotation.SystemServiceLog)") 4 public void pointcut() { 5 6 }