Spring framework(Version 5.0.8)摘要 5,Aspect Oriented Programming With Spring
喜歡的話,還可以打賞哦,以資鼓勵
(支付寶) (微信)
5.1 Introduction
AOP框架是Spring的核心元件。但是Spring IoC容器並不依賴於AOP。AOP是Spirng IoC的補充,如果你不需要的話,完全可以不用。
Spring 2.0開始提供schema-base(XML schema)和@AspectJ註解兩種自定義aspects的方式。
AOP在Spring Framework中用於:
- 提供宣告式的企業級服務,特別是用於替代EJB服務。最重要的應用場景是事務管理。
- 允許使用者自定義aspects,作為OOP的補充。
5.1.1 AOP 概念
- Aspect:是對一個橫跨了多個Classes的問題的模組化,比如事物處理(事物處理的操作以切面的形式插入到業務處理的流程中,這些切面的集合可以抽象成一個模組,代表一個完整的事務處理邏輯)。
- Join point:程式執行過程中的一個切入點,它可以是一個方法的執行或一個異常的處理(我們找到一個切入點,並在此處加入我們需要的處理邏輯)。
- Advice:即我們在切入點所加入的處理邏輯(比如在一個方法執行後列印一段日誌)。它有幾種型別“before”,“after”(分別代表在切入點的前面,後面)。很多AOP框架把advice抽象成interceptor(攔截器),圍繞在切入點前後,形成一個攔截器鏈。
- Pointcut:一個申明,用來匹配哪些才是你要找的切入點。(比如,我們宣告“所有已Service結尾的類它的do方法”都需要執行日誌列印,引號內的申明就是Pointcut)。Spring預設使用的是AspectJ pointcut expression。
- Introduction:Introduction允許你為adviced物件引入新的interface,併為其提供該interface的一個實現(相當於為其添加了一個父類,使它具有父類的功能介面)。這樣依賴這個adviced物件就具有了新的功能。
- Target object:被aspect植入操作的物件。由於Spring AOP是使用執行時代理實現的,所以也可以認為是被代理物件。
- AOP proxy:被AOP框架建立的用於植入程式碼的物件。在Spring中,AOP代理指JDK動態代理或CGLIB代理。
- Weaving:程式碼織入。把aspect與類或物件聯絡起來,得到一個植入了新操作的物件。程式碼織入可以在編譯,載入或執行時進行。Spring AOP和其他純Java實現的AOP框架一樣,都是在執行時織入程式碼的。
advice的型別
- Before advice:在join point之前執行。不過這種方式無法制止程式碼繼續執行,除非丟擲異常。
- After returning advice:在join point不拋錯,並且正常執行完成之後執行。
- After throwing advice:在join point執行拋錯時執行。
- After (finally) advice:無論正常執行亦或拋錯,都執行。
- Around advice:與前面的advice型別不同。在Around advice中,join point的執行由使用者自己控制。你可以在呼叫join point之前,之後加入自己的程式碼邏輯。也可以選擇呼叫或不呼叫join point。Around advice相當於對join point進行了封裝,為使用者提供了很大的自主權。
在advice的選擇上,應該遵循最小化影響原則(夠用就行)。
5.1.2 Spring AOP的能力和目標
Spring AOP是純Java實現的。
Spring AOP當前只支援方法執行這種join points,不支援屬性攔截。如果你需要的話,可以使用AspectJ。
Spring AOP的目標不是提供完整的AOP功能,它更傾向於通過與Spring IoC的緊密整合,來提供企業級應用中的問題。
5.1.3 AOP 代理
Spring AOP預設使用JDK動態代理機制,動態代理需要業務程式碼繼承於某個介面。Spring AOP也支援CGLIB代理,如果業務程式碼不繼承任何介面的情況下可以選擇CGLIB。
5.2 @AspectJ支援
@AspectJ指的是一種使用註解宣告aspects的方式。@AspectJ是由AspectJ專案引入的。Spring使用AspectJ提供的jar包解析@AspectJ註解。不過儘管使用了相同的註解,AOP仍然是純Spring AOP,並不依賴於AspectJ編譯器或程式碼織入。
5.2.1 啟用@AspectJ支援
使用Java Configuration啟用@AspectJ註解
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
使用XML Configuration啟用@AspectJ註解
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<aop:aspectj-autoproxy/>
</beans>
不過,無論使用哪種引入方式,都需要依賴於aspectjweaver.jar包。
5.2.2 宣告一個aspect
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
也可以不配置xml,而使用註解自動掃描的方式,不過因為@Aspect無法被自動掃描到,所以需要加上@Component註解。
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
@Component
public class NotVeryUsefulAspect {
}
注意:被@Aspect標註的類,不能再作為AOP的target。
5.2.3 宣告一個pointcut
一個pointcut包含兩部分:一個由name和引數構成的簽名(由於Spring AOP只支援基於方法執行的pointcut,所以這裡的簽名可以理解為方法簽名),一個pointcut表示式用來標識作用於哪些方法。
@Pointcut("execution(* transfer(..))")// the pointcut expression 表示式
private void anyOldTransfer() {}// the pointcut signature 方法簽名
支援的pointcut指示符(AspectJ pointcut designators ,PCD)
PCD就是pointcut expression中的關鍵字,Spring支援如下關鍵字。
- execution - 匹配方法執行join points
- within - 限定匹配的類型範圍
- this - 限定AOP代理是一個指定型別的例項
- target - 限定AOP被代理物件是一個指定型別的例項
- args - 限定引數是指定型別
- @target - 限定執行物件的類有一個指定型別的註解
- @args - 限定傳入的引數類有指定型別的註解
- @within - 限定匹配的類有指定型別的註解
- @annotation - 限定join point 方法有指定型別註解
另外Spring添加了一個bean關鍵字,用於限定join points為指定名稱的spring bean。針對例項的PCD是Spring特有的。
pointcut表示式的結合使用
pointcut表示式可以使用“&&”,“||”,“!”。
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
上面這個例子中我們先定義了anyPublicOperation和inTrading兩個pointcut,然後把anyPublicOperation和inTrading作為元件,定義了新的pointcut。
共享通用的pointcut定義
上面介紹瞭如何把pointcut作為元件來使用,spring推薦預定義通用的ponitcut表示式,作為共享元件。
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
完整的pointcut表示式包含以下幾個部分(就像正真的方法簽名),除了returning type pattern,name pattern,parameters-pattern其他都是可選的。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
表示式中可以使用萬用字元表示。比較複雜的部分是方法的引數,()代表沒有引數,(..)代表任意數量的引數,(*)代表一個不限定型別的引數,(*,String)代表兩個引數一個不限定型別,另一個是字串型別。
pointcut表示式例子
表示任何public方法的執行
execution(public * *(..))
表示任何名字以set開頭的方法的執行
execution(* set*(..))
表示任何AccountService介面中定義的方法的執行
execution(* com.xyz.service.AccountService.*(..))
表示包中定義的任何方法的執行
execution(* com.xyz.service.*.*(..))
表示包中的任何join point
within(com.xyz.service.*)
表示此包或其子包中的任何join point
within(com.xyz.service..*)
表示任何join point其代理實現了AccountService介面。this只適用於在Spring AOP中執行的情況。
this(com.xyz.service.AccountService)
表示任何join point其target物件實現了AccountService介面。target只適用於在Spring AOP中執行的情況。
target(com.xyz.service.AccountService)
表示任何join point其在執行時,傳入的引數只有一個,且是Serializble。
注意:這個例子與execution(* *(java.io.Serializable))是不同的,execution關注的是方法簽名,args關注的是執行時傳入的實際引數。
args(java.io.Serializable)
表示任何join point其target物件帶有@Transactional註解
@target(org.springframework.transaction.annotation.Transactional)
表示任何join point其target的類宣告上帶有@Transactional註解
@within(org.springframework.transaction.annotation.Transactional)
表示任何join point其執行的方法上帶有@Transactional註解
@annotation(org.springframework.transaction.annotation.Transactional)
表示任何join point其只有一個引數,且執行時傳入的引數型別帶有@Classified註解
@args(com.xyz.security.Classified)
表示任何join point其spring bean name是tradeService
bean(tradeService)
也可以帶有萬用字元
bean(*Service)
PCD的三種類型
PCD可以總結為三種類型:Kinded,Scoping,Contextual。
Kinded(指向特定型別的join point):execution
Scoping:within
Contextual:this,target,@annotation
5.2.4 宣告advice
advice由一個pointcut表示式,和before、after、around等組成。
Before advice
這裡的com.xyz.myapp.SystemArchitecture.dataAccessOperation()指的是宣告好的pointcut(這種預先宣告的方式在前面已經講到過),不是實際的切入點。
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() {
// ...
}
}
當然也可以直接使用pointcut表示式
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() {
// ...
}
}
After ruterning advice
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() {
// ...
}
}
如果要對返回值進行處理,可以加上returning屬性
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) {
// ...
}
}
After throwing advice
當匹配到的方法,丟擲異常時執行。
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() {
// ...
}
}
可以傳入ex
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) {
// ...
}
}
After(finally)advice
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() {
// ...
}
}
Around advice
我們可以看到這裡傳入的引數就是原本要執行的業務邏輯,這樣一來我們就有了更大的自由發揮的空間,可以選擇是否執行它,或者在其周圍加上自定義的程式碼。
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 {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
advice parameters
獲取當前的JointPoint
任何advice方法,都可以宣告其第一個引數為org.aspectj.lang.JoinPoint型別的。在around advice中必須是ProceedingJoinPoint型別,它是JoinPoint的子類。JoinPoint提供了很多方法,getArgs、getThis、getTarget、getSignature、toString等等。
傳參到advice方法中
我們可以使用args PCD來傳入引數,下例中傳入了一個名為account的引數,這樣在advice方法中就可以接受到了。在這裡args(account,..)有兩個作用,一個是限定執行的方法必須至少有一個引數,且型別是Account;另一個是為advice傳參。
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
以上的例子,也可以用如下方式表示。
事先宣告好一個帶引數的pointcut,然後直接在advice中使用。
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
更多有趣的用法可以參考AspectJ的文件。
一個使用@annotation PCD傳參的例子
先定義一個Auditable標籤。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
然後使用@annotation去匹配
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
advice引數和泛型
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
假設有如上泛型介面,那麼對應的advice可以這樣寫
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
不過需要指出的是,advice不支援泛型集合Collection<T>。
決定引數名
我們可以使用argNames屬性來指定pointcut表示式和advice方法之間引數的對應關係。argNames屬性中的引數順序與advice方法的引數順序一致,argNames屬性中的引數名稱需要與pointcut表示式中的引數名稱一致。
@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
}
如果advice的第一個引數,其型別是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
}
如果不指定argNames屬性,Spring AOP會查詢class的debug information,根據本地變量表決定引數名。如果是在AspectJ編輯器中編譯則不需要debug information。
advice執行順序
當多個advice都作用於同一個join point時,如果不指定順序,那麼執行順序是不確定的。你可以通過優先順序來控制執行順序,advice根據優先順序決定執行順序。在進入join point時,優先順序最高的先執行;在離開join point時,優先順序最高的後執行。控制優先順序可以通過讓aspect class實現org.springframwork.core.Ordered介面或使用@Order註解,order值越小,優先順序越高。
注意:如果同一個aspect下面的兩個advice都作用於同一個join point,這種情況下是沒辦法控制優先順序的。所以最好考慮分拆到不同的aspect中。
5.2.5 Introductions
Introduction,允許aspect為advised物件新增一個父interface,併為advised物件提供了該interface的一個實現。
例子:
先定義一個Student類,等一下我們會為該類的例項加入新邏輯。
package com.zjc.spring.person;
import org.springframework.stereotype.Component;
@Component
public class Student {
}
準備加入的新邏輯介面
package com.zjc.spring;
public interface Driver {
void drive();
}
介面的實現
package com.zjc.spring;
public class CarDriver implements Driver{
@Override
public void drive() {
System.out.println("driving car");
}
}
一個aspect,用於給Student加入Dirver功能
package com.zjc.spring;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@DeclareParents(value = "com.zjc.spring.person.*", defaultImpl = com.zjc.spring.CarDriver.class)
public Driver driver;
}
執行
package com.zjc.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
@SpringBootApplication
public class Test {
public static void main(String args[]){
ApplicationContext context = SpringApplication.run(Test.class, args);
Driver driver = (Driver)context.getBean("student");
driver.drive();
}
}
我們可以看到student可以轉換成Driver型別了,具有了Driver介面的功能。
5.2.6 Aspect 例項化模型
預設情況下,aspect都是單例模式的,即在application context中每個aspect只會存在一個例項。不過Spring也支援AspectJ的perthis,pertarget模式。
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {
private int someState;
@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}
}
perthis表示spring會為每個符合條件的代理物件建立一個aspect例項。
5.3 Schema-based AOP支援
要使用aop namespace標籤,你需要引入spring-aop schema,就像下面這樣。
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
</beans>
注意:在使用<aop:config>配置模式的情況下,不要啟用<aop:aspectj-autoproxy/>,否則會出問題。
5.3.1 宣告一個aspect
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
5.3.2 宣告一個pointcut
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
也可以把宣告指向一個預先宣告好的pointcut
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.SystemArchitecture.businessService()"/>
</aop:config>
也可以在aspect中宣告pointcut
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
結合before advice使用
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
結合多個pointcut表示式,在這裡&&、||、!是不適用的,需要用and、or、not替代。
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service..(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
5.3.3 宣告advice
Before advice
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
After returning advice
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
可以帶上返回值
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
public void doAccessCheck(Object retVal) {...
After throw advice
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
也可以把異常作為引數傳入advice方法
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
public void doRecoveryActions(DataAccessException dataAccessEx) {...
After (finally) advice
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
Around advice
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
Advice parameters
業務類
package x.y.service;
public interface FooService {
Foo getFoo(String fooName, int age);
}
public class DefaultFooService implements FooService {
public Foo getFoo(String name, int age) {
return new Foo(name, age);
}
}
aspect類
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- this is the actual advice itself -->
<bean id="profiler" class="x.y.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomeFooServiceMethod"
expression="execution(* x.y.service.FooService.getFoo(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
啟動類
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.FooService;
public final class Boot {
public static void main(final String[] args) throws Exception {
BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
FooService foo = (FooService) ctx.getBean("fooService");
foo.getFoo("Pengo", 12);
}
}
執行結果
StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms % Task name
-----------------------------------------
00000 ? execution(getFoo)
5.3.4 Introductions
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+"
implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.SystemArchitecture.businessService()
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
5.3.5 Aspect例項化模型
在schema定義的aspects中唯一支援的例項化模式是單例模型,其他的展示不支援。
5.3.6 Advisors
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
5.8 在Spring應用中使用AspectJ
在Spring中使用aspects,你需要引入spring-aspects.jar。
5.8.1 在Spring中使用AspectJ做領域物件的依賴注入
Spring容器可以例項化和配置那些定義在application context中的beans。也可以配置一個已經存在的物件。
Spring提供了對那些在容器之外建立的物件的支援,比如領域物件(Domain objects),他們通常由ORM工具等通過new操作建立。
@Configurable註解使一個類適用於Spring的配置,被標註的類的新例項會被Spring配置,注入依賴。
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
當用@Configurable標識類時,Spring會以全限定名來命名配置這個類的新例項。
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
你也可以通過以上方式指定例項的名稱。
另外,你可以使用@Configurable(autowire=Autowire.BY_TYPE)或@Configurable(autowire=Autowire.BY_NAME)來指定依賴注入到這個類的新例項的方式。使用@Configurable(dependencyCheck=true)來對新例項中的依賴進行檢查(當所有屬性配置完成後,spring會進行校驗)。
註解本身並沒有做什麼,真正起作用的是spring-aspects.jar中的AnnotationBeanConfigurerAspect。本質上,他做了這麼一件事——“當被@Configurable註解標註的類的新例項被初始化後,根據註解的屬性使用Spirng來配置這個新生成的例項”。
注意:以上的表述暗示了依賴注入發生在建構函式執行完成後。如果你希望在建構函式方法體執行前注入依賴,你可以做如下配置。
@Configurable(preConstruction=true)
以上這些必須使用AspectJ的程式碼織入(可以通過Ant或Maven任務,也可以使用類載入期織入)。另外,你需要通過spring配置來啟用AnnotationBeanConfigurerAspect,如果你使用Java based配置,那麼可以通過在任一@Configuration class上新增@EnableSpringConfigured註解來啟用AnnotationBeanConfigurerAspect。
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
如果是xml based配置,則可以如下配置
<context:spring-configured/>
如果一個類確定要用到@Configurable的功能,為了防止它在AnnotationBeanConfigurerAspect之前建立。可以使用depends-on
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
注意:如果一個類是在Spring的控制下,那麼前往不要在上面加@Configurable註解,否則會有兩次初始化,一次是容器發起的,一次是aspect發起的。
@Configurable物件的單元測試
@Configurable的一個目標就是使對領域物件的單元測試更簡單。
多個application contexts的情況
AnnotationBeanConfigurerAspect是個AspectJ單例aspect,這意味一個classloader上只能有一個這樣的aspect例項。如果你有多個applictaion context在同一個classloader上,你就需要考慮在哪裡定義@EnableSpringConfigured bean,哪裡放置spring-aspects.jar。
在擁有共享的parent application context的情況下,建議把@EnableSpringConfigured bean定義在parent application context上。
當servlet容器上部署了多個web-apps時,要確保每個web-app都使用自己的classloader載入spring-aspects.jar(比如,把spring-aspect.jar放到WEB-INF/lib目錄下),如果放置在servlet容器的classpath下,則所有的web-app都會共用同一個aspect例項。
5.8.2 其它的AspectJ aspects
除了@Configurable aspect,spring-aspects.jar包含一個AspectJ aspect,對那些使用@Transactional標註的類和方法執行Spring的事務管理。它主要用於在Spring容器控制之外使用Spring的事務支援。
@Transactional註解由AnnotationTransactionAspect解釋。(注意,你必須把註解放置在具體的實現類上,而不是父介面上。因為介面上的註解是不會被繼承的)
類上的@Transactional註解為該類的所有public方法提供了事務支援。
你可以在方法上使用@Transactional註解,即使該方法是private的。
注意:Spring Framework4.2以來,spring-aspects提供了一個小的aspect支援同樣的javax.transaction.Transactional註解特性。詳見JtaAnnotationTransactionAspect。
如果你希望使用Spring configuration和事務管理支援,但不想用註解,spring-aspects.jar提供了抽象的aspects來支援你定義自己的pointcut定義。詳見AbstractBeanConfigurerAspect
和AbstractTransactionAspect
。
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
public DomainObjectConfiguration() {
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
}
// the creation of a new bean (any object in the domain model)
protected pointcut beanCreation(Object beanInstance) :
initialization(new(..)) &&
SystemArchitecture.inDomainModel() &&
this(beanInstance);
}