- 建立annotated aspects
- AspectJ5的特性
- 簡便的通過少量的annotation把任意class轉變為aspect
- 定義aspect
- 一場表演,觀眾很重要,但對演出功能本身而言,觀眾並不是必要。因此,在Performance上,觀眾是aspect,並將其應用到Performance
@Aspect public class Audience { @Before("execution(** concert.Performance.perform(..))") //perform()之前 public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("execution(** concert.Performance.perform(..))") //perform()之前 public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("execution(** concert.Performance.perform(..))") //perform()成功執行後 public void applause() { System.out.println("CLAP CLAP CLAP!!!"); } @AfterThrowing("execution(** concert.Performance.perform(..))") //表演失敗之後 public void demandRefund() { System.out.println("Demanding a refund"); } }
- 以上的方法表示Audience不只是一個POJO,也是一個aspect
- 使用annotation定義aspect的具體行為
- 上例有個缺點,"execution(** concert.Performance.perform(..))"使用了四次。因此可以使用@Pointcut避免頻繁地使用aspect expression
- 上面的Audience除了annotation及空的method,依然是一個一般的POJO。也就是Audience只是一個Java class,只不過透過annotation表明會作為一個aspect使用而已
- 也可以像是bean一樣被wired
- 使用JavaConfig開啟auto-proxy
- 使用XML開啟auto-proxy
- 不論使用JavaConfig或是XML,AspectJ auto-proxy都會為使用@Aspect annotation的bean建立proxy
- 在Spring內使用@AspectJ annotation,如果想要使用AspectJ的所有功能,則需要在runtime的時候使用AspectJ而且不依賴於Spring create 基於agent的aspect
- Around advice
- 就像是同時寫了before advice&after advice
- 使用一個@Around來代替上述的多個不同before advice&after advice
- 可以注意到,此@Around method接受ProceedingJoinPoint作為parameter
- 在執行被advice的method的時候,可以invoke proceed() method
- 可以多次invoke proceed(),也可以不invoke
- 在advice中處理parameters
- 問題:如果aspect所通知的method有parameters時,是否能夠取得此parameter?
- 範例:假設需要紀錄track被播放的次數如下:
- args(trackNumber): 指定parameters,這個parameter將傳到method中
- 表示傳至playTrack()的parameter也會傳到advice中
- 透過annotation引入新的功能
- 使用AOP觀念(稱為introduction,引入),aspect可以為Spring bean添加新的功能
- 假設現有的interface: Performance
- 以及現有一新的interface: Encoreable
- 假設能夠visit所有implements Performance的class並且能夠修改他們讓他們也能夠implements Encoreable。但這不是個好方法,因為不是所有implements Performance的class都具有Encoreable的特性。也有可能並無法修改所有Performance的 implementation classes(比如使用第三方package)
- 解法:使用AOP的intrudoction,並不需要在設計上使用新入性的改變現有implementation classes,而只需要create一個新的aspect
- 透過@DeclareParents能夠將Encoreable interface introduce to Performance bean
- @DeclareParents
- value=哪種bean要introduce至此interface (此為concert.Performance的所有subclass(+),不含Performance本身)
- defualtImpl: 為了introduce新功能提供implementation,此為DefaultEncoreable
- static property指明了要introduce的interface
- 與其他aspect相同,EncoreableIntroducer也需要被宣告為bean
- 使用這種方法的缺點是,必須要能夠在原本的class修改,增加annotatino。若不能則可以考慮使用XML
- 在XML中宣告aspects
- Spring的aop namespace提供了很多element在XML中宣告aspect
- aspect namespace能夠直接在Spring config中聲明@aspect,而不需要使用annotation
- <aop:advisor>
- <aop:after>
- <aop:after-returning>
- <aop:after-throwing>
- <aop:around>
- <aop:aspectj-autoporxy>
- <aop:before>
- <aop:config>
- <aop:declare-parents>
- <aop:pointcut>
- 假設要使用XML配置,則原本的Audience可以改成如下:
- 使用Spring aop namespace將沒有annotation的Audience class轉回aspect
- 將audience先註冊為bean,之後..
- 必須在<aop:config>之間
- <aop:config>中可以配置多個advice、pointcut、aspects
- Audience aspect包含四個advices,將advice logic weave(織入)匹配aspect's pointcut的method中
- pointcut定義advice什麼時候會被執行
- pointcut內的值就是使用AspectJ的expression syntax
- 也可以使用<aop:pointcut>將重複的pointcut提出,這樣就可以在其他advice element中使用
- <aop:pointcut>定義一個id為performance的pointcut
- 下面即可以使用pointcut-ref引用這個named pointcut
- 宣告around-advice
- Passing parameters to advice
- 以下範例,可看到多了args(trackNumber),這個參數會傳到advice method中,和上面使用annotation使用的expression幾乎相等。除了XML使用的是and而不是&&
- 以下為搭配的POJO
- Introducing new functionality to aspects
- 可以使用@DeclareParents annotation introduce新的方法
- 若要在XML中,可以使用<aop:declare-parents> element
- introduce interface的implemtation除了使用default-imp指定implement Encoreable的class(要寫完整的class名字),也可以使用delegate-ref attribute
- delegate-ref使用了Spring bean的ref,這需要在Spring上的context存在一個ID是encoreableDelegate的bean
- Injecting AspectJ aspects
- 和AspectJ相比,Spring AOP是一個功能比較弱的AOP解決方案
- 如果在執行advice時,aspcet依賴一個或多個class,可以在aspect內部時體會這些對象。但更好的方式是,使用Spring的DI把bean wire進入AspectJ的aspect中
- 範例:使用AspectJ implement a performance評論員
- 主要功能:在表演結束後為表演發表評論
- aspect也需要injects。就像其他bean依樣,Spring可以為AspectJ aspect injects dependency
- criticismEngine的implementation:
- XML:
- AspectJ不需要Spring就可以weave到程式中。如果想要使用Spring配置將依賴注入於AspectJ aspect中,則需要將aspect declare為bean
- 一般來說,Spring bean由Spring container initialize,但是AspectJ aspect是由AspectJ在runtime created。等到Spring有機會為CriticAspect injects CriticismEngine時,CriticAspect 已經被initial了
- 因為Spring不是那個負責建立CriticAspect的人,也因此不能夠把CriticAspect宣告為一個bean。相反的,我們需要一個方式替Spring獲得已經由AspectJ建立的CriticAspect instance,從而injects CriticismEngine 。
- 所有的AspectJ aspect其實都提供一個static aspectOf() method,可以return aspect的singleton
- 因此為了取得aspect的instance,必須使用factory-method="aspectOf",而不是試著呼叫CriticAspect的constructor
- 簡而言之,Spring不能像之前使用<bean>來創造一個CriticAspect 的instance,因為在runtime的時候已經被AspectJ建立完成了。Spring只需要透過factory-method="aspectOf"來取得aspect ref,然後像<bean>規定的那樣在object上執行DI
@Aspect public class Audience { @Pointcut("execution(** concert.Performance.perform(..))") //使用@Pointcut定義重複的aspect expression public void performance() {} //接著就可以使用這個縮寫,簡略那些更長的aspect expression,method內容通常為空 @Before("performance()") // declare advice method public void silenceCellPhones() { System.out.println("Silencing cell phones"); @Before("performance()") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("performance()") public void applause() { System.out.println("CLAP CLAP CLAP!!!"); } @AfterThrowing("performance()") public void demandRefund() { System.out.println("Demanding a refund"); } }
@Bean public Audience audience() { return new Audience(); }
@Configuration @EnableAspectJAutoProxy @ComponentScan public class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }
<context:component-scan base-package="concert" /> <aop:aspectj-autoproxy /> <bean class="concert.Audience" />
@Aspect public class Audience { @Pointcut("execution(** concert.Performance.perform(..))") public void performance() {} @Around("performance()") public void watchPerformance(ProceedingJoinPoint jp) { try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); jp.proceed(); System.out.println("CLAP CLAP CLAP!!!"); } catch (Throwable e) { System.out.println("Demanding a refund"); } } }
@Aspect public class TrackCounter { private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>(); @Pointcut("execution(* soundsystem.CompactDisc.playTrack(int)) " + "&& args(trackNumber)") public void trackPlayed(int trackNumber) {} @Before("trackPlayed(trackNumber)") // 播放之前紀錄次數 public void countTrack(int trackNumber) { // 擷取傳至trackPlayed的trackNumber int currentCount = getPlayCount(trackNumber); trackCounts.put(trackNumber, currentCount + 1); } public int getPlayCount(int trackNumber) { return trackCounts.containsKey(trackNumber) } }
public interface Performance { public void perform(); }
package concert; public interface Encoreable { void performEncore(); }
@Aspect public class EncoreableIntroducer { @DeclareParents(value="concert.Performance+", defaultImpl=DefaultEncoreable.class) public static Encoreable encoreable; }
public class Audience { public void silenceCellPhones() { System.out.println("Silencing cell phones"); } public void takeSeats() { System.out.println("Taking seats"); } public void applause() { System.out.println("CLAP CLAP CLAP!!!"); } public void demandRefund() { System.out.println("Demanding a refund"); } }
<aop:config> <aop:aspect ref="audience"> // 使用了audience Bean,declare一個aspect <aop:before pointcut="execution(** concert.Performance.perform(..))" method="silenceCellPhones" /> <aop:before pointcut="execution(** concert.Performance.perform(..))" method="takeSeats" /> <aop:after-returning pointcut="execution(** concert.Performance.perform(..))" method="applause" /> <aop:after-throwing pointcut="execution(** concert.Performance.perform(..))" method="demandRefund" /> </aop:aspect> </aop:config>
<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(** concert.Performance.perform(..))" /> // 定義pointcut <aop:before pointcut-ref="performance" method="silenceCellPhones" /> <aop:before pointcut-ref="performance" method="takeSeats" /> <aop:after-returning pointcut-ref="performance" method="applause" /> <aop:after-throwing pointcut-ref="performance" method="demandRefund" /> </aop:aspect> </aop:config>
<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(** concert.Performance.perform(..))" /> <aop:around pointcut-ref="performance" method="watchPerformance" /> // 在watchPerformance內定義around-advice </aop:aspect> </aop:config>
<bean id="trackCounter" class="soundsystem.TrackCounter" /> <bean id="cd" class="soundsystem.BlankDisc"> <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" /> <property name="artist" value="The Beatles" /> <property name="tracks"> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> <value>Lucy in the Sky with Diamonds</value> <value>Getting Better</value> <value>Fixing a Hole</value> <!-- ...other tracks omitted for brevity... --> </list> </property> </bean> <aop:config> <aop:aspect ref="trackCounter"> <aop:pointcut id="trackPlayed" expression="execution(* soundsystem.CompactDisc.playTrack(int)) and args(trackNumber)" /> <aop:before pointcut-ref="trackPlayed" method="countTrack" /> </aop:aspect> </aop:config>
public class TrackCounter { private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>(); public void countTrack(int trackNumber) { int currentCount = getPlayCount(trackNumber); trackCounts.put(trackNumber, currentCount + 1); } public int getPlayCount(int trackNumber) { return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }
<aop:aspect> <aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" // 增加concert.Encoreable interface功能 default-impl="concert.DefaultEncoreable" /> </aop:aspect>
<aop:aspect> <aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" delegate-ref="encoreableDelegate" /> </aop:aspect>
<bean id="encoreableDelegate" class="concert.DefaultEncoreable" />
package concert; public aspect CriticAspect { public CriticAspect() {} pointcut performance(): execution( * perform(..)); //定義pointcut afterReturning(): performance() { //表演結束後執行getCriticism() System.out.println(criticismEngine.getCriticism()); } private CriticismEngine criticismEngine; //injects criticismEngine,為了避免CriticAspect與CriticismEngine有coupling public void setCriticismEngine(CriticismEngine criticismEngine) { this.criticismEngine = criticismEngine; } }
package com.springinaction.springidol; public class CriticismEngineImpl implements CriticismEngine { public CriticismEngineImpl() {} public String getCriticism() { //隨機從criticismPool選擇一個評論並傳回 int i = (int) (Math.random() * criticismPool.length); return criticismPool[i]; } // 從XML injected private String[] criticismPool; public void setCriticismPool(String[] criticismPool) { this.criticismPool = criticismPool; } }
<bean id="criticismEngine" class="com.springinaction.springidol.CriticismEngineImpl"> <property name="criticisms"> <list> <value>Worst performance ever!</value> <value>I laughed, I cried, then I realized I was at the wrong show. </value> <value>A must see show!</value> </list> </property> </bean>
<bean class="com.springinaction.springidol.CriticAspect" factory-method="aspectOf"> <property name="criticismEngine" ref="criticismEngine" /> </bean>
沒有留言:
張貼留言