搶佔先機
VMware 提供培訓和認證,助你加速進步。
瞭解更多雖然本文的內容相當簡單,但它實際上會讓你對 Spring 2.0 中一些相當重要的新特性有所瞭解。我希望透過一點點想象力,你能夠將此處看到的內容應用到你自己的那些遠不那麼簡單的用例中去。
我實際上會展示兩個示例。第一個將使用一個相當簡單的日誌記錄器(logger)
package example;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class SimpleLogger {
private static Log log = LogFactory.getLog(SimpleLogger.class);
public void logOneString(String s) {
log.info("string=" + s);
}
public void logTwoStrings(String s1, String s2) {
log.info("string1=" + s1 + ",string2=" + s2);
}
}
我將使用 AOP 將日誌記錄應用到一個字串拼接服務。這是它的介面
package example;
public interface ConcatService {
public String concat(String s1, String s2);
}
以及一個實現類(Implementing Class)
package example;
public class ConcatServiceImpl implements ConcatService {
public String concat(String s1, String s2) {
return s1 + s2;
}
}
好的——到目前為止,這些都沒什麼令人興奮的,但最需要注意的是,到目前為止我只處理 POJO。
現在,看看這些 bean 定義。特別注意新的 Spring 2.0 XML Schema 和 ìaopî 名稱空間的用法
<?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">
<aop:config>
<aop:aspect id="loggingAspect" ref="simpleLogger">
<aop:before
method="logTwoStrings"
pointcut="execution(* example..*Service.*(..)) and args(s1,s2)"/>
<aop:after-returning
method="logOneString"
returning="s"
pointcut="execution(* example..*Service.*(..))"/>
</aop:aspect>
</aop:config>
<bean id="simpleLogger" class="example.SimpleLogger"/>
<bean id="concatService" class="example.ConcatServiceImpl"/>
</beans>
ìloggingAspectî 使用對 ìsimpleLoggerî 的引用來定義,你在上面的第一個程式碼片段中看到了它。同樣,有趣的是它是一個簡單的 POJO——它不實現任何介面或遵循任何契約即可用作切面(aspect)。事實上,你很可能已經有這樣的程式碼了。;)
ìloggingAspectî 包含兩種通知(advice)。一種是 ìbeforeî 通知,另一種是 ìafterReturningî 通知。接下來,你會看到通知實際對映到SimpleLoggerPOJO 的方法——logTwoStrings()用於 before 通知,以及logOneString()用於 afterReturning 通知。這種宣告性對映到 POJO 方法的選項是實現通知介面的一個有用替代方案。
最後,簡單介紹一下繫結(binding)和切入點(pointcuts)。在 ìbeforeî 通知中,args(s1,s2) 指定當有兩個引數可以繫結到這兩個引數時,該切入點將應用到對應方法,這些引數是String引數,這些引數屬於該logTwoStrings()方法——正如你稍後就會看到的,這裡正是如此。在 ìafterReturningî 的情況下,返回值將繫結到該單個String引數,該引數屬於該logOneString()方法。
現在,關於切入點... 上面 ìpointcutî 屬性中的值實際上是標準的 AspectJ 切入點表示式。在這種情況下,它們定義了哪些方法會被通知。ì*î 是一個萬用字元,第一個 ì..î 表示任何後代包,而第二個 ì..î 表示任意數量和型別的引數。本質上,只要是 ìexampleî 包的後代,這個切入點將應用於任何名稱以 ìServiceî 結尾的類的任何方法,無論其引數型別、引數數量或返回值如何。好的,也許這聽起來不那麼簡單——但如果至少聽起來有趣的話,你可以在 AspectJ 網站上閱讀更多關於 AspectJ 表示式語言的內容。
注意:雖然這裡使用了 AspectJ 表示式,但通知(advice)仍然是透過 Spring 的基於代理的 AOP(Proxy-based AOP) 應用的,而不是 AspectJ 織入(weaving)。這意味著攔截器只能在方法執行連線點(joinpoints)處新增行為。方法執行攔截很可能滿足你的大多數 AOP 用例。但是,要在其他連線點(例如欄位訪問)處應用通知,你可以使用 AspectJ 的全部功能(這超出了本文的範圍)。
所以,不再猶豫了... 這是一個簡單的main()方法來嘗試一下
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("example/simpleLoggerContext.xml");
ConcatService concatService = (ConcatService)context.getBean("concatService");
concatService.concat("some", "thing");
}
結果如下!
string1=some,string2=thing
string=something
現在,來看第二個示例...
當然,你可能想記錄更多資訊,例如方法引數、呼叫方法本身等等。為了展示如何實現這一點,我將稍微修改一下這個SimpleLogger。秘訣在於JoinPoint類(以及StaticPart類),它們現在將提供給我新類的方法,這個類是MethodLogger:
package example;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
public class MethodLogger {
private static Log log = LogFactory.getLog(MethodLogger.class);
public void logMethodEntry(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().toLongString();
StringBuffer sb = new StringBuffer(name + " called with: [");
for(int i = 0; i < args.length; i++) {
Object o = args[i];
sb.append(o);
sb.append((i == args.length - 1) ? "]" : ", ");
}
log.info(sb);
}
public void logMethodExit(StaticPart staticPart, Object result) {
String name = staticPart.getSignature().toLongString();
log.info(name + " returning: [" + result + "]");
}
}
如你所見,JoinPoint提供了我需要的執行時資訊。在logMethodExit()方法中,只需要型別資訊,因此StaticPart就足夠了(它實際上是JoinPoint的一部分,JoinPoint提供了一個getStaticPart()方法)。通常來說,只要能在不訪問執行時資訊的情況下完成你需要做的事情,就應該這樣做。
這是使用MethodLogger:
<?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">
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* example..*Service+.*(..))"/>
<aop:aspect id="loggingAspect" ref="methodLogger">
<aop:before
method="logMethodEntry"
pointcut-ref="servicePointcut"/>
<aop:after-returning
method="logMethodExit"
returning="result"
pointcut-ref="servicePointcut"/>
</aop:aspect>
</aop:config>
<bean id="methodLogger" class="example.MethodLogger"/>
<bean id="concatService" class="example.ConcatServiceImpl"/>
</beans>
的 bean 定義。再次,你看到了切面(aspects)和通知(advice)。這次 ìpointcutî 被單獨定義並重用於兩種通知型別。這裡最有趣的一點可能是,方法引數沒有顯式繫結,也不需要配置任何東西來識別JoinPoint或StaticPart引數。事實上,你總是可以將其中一個指定為方法的第一個引數,以便訪問有關方法執行上下文的更多資訊。
為了執行這個示例,我將使用相同的main(),但這次將新的 bean 定義檔案的路徑傳遞給ClassPathXmlApplicationContext建構函式。結果如下
public abstract java.lang.String example.ConcatService.concat(java.lang.String,java.lang.String) called with: [some, thing]
public abstract java.lang.String example.ConcatService.concat(java.lang.String,java.lang.String) returning: [something]
這個簡單的示例就介紹到這裡了。要理解的重點是,POJO 服務可以透過 POJO 切面(aspects)來增加額外的行為。事實上,在某些情況下,讓它們成為切面的唯一因素是配置。在其他情況下,當你需要更多的執行時資訊時,JoinPoint和StaticPart會非常有用。
如果你對這個主題更全面的介紹感興趣,請訪問 Adrian Colyer 的這篇部落格。
注意:在那篇文章中,你會看到使用 <aop:advice> 元素的示例。在 Spring 2.0 M3 中,這些元素已被本示例中使用的更具體的元素所取代,例如:<aop:before>。