Spring 面向切面实例讲解

2016-03-28 20:08:01   最后更新: 2016-03-28 20:08:01   访问数量:549




上一篇日志中,我们介绍了 spring aop 的基本概念,本篇日志你将看到全面的 AOP 用法和实例

 

首先,我们需要在 pom 中引入依赖的包:

<dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>

 

 

还记得我们的家庭演出吗?在每一个表演之前和之后,都需要处理相同的事务:

package com.techlog.test.service; /** * Created by techlog on 16/3/25. */ public class Audience { // befor perform public void takeSteats() { System.out.println("taking seats"); } // before perform public void turnOffCellPhones() { System.out.println("turning off phones"); } // after perform public void applaud() { System.out.println("clap clap"); } // after failed perform public void demandRefund() { System.out.println("refunding money"); } }

 

声明 bean:

<bean id="audience" class="com.techlog.test.service.Audience"/>

 

 

观众入座、关机、鼓掌或是失望的退票,这些通用型的事件就应该作为通知切入切点

 

接下来,我们把 audience 变成一个切面:

<aop:config> <aop:aspect ref="audience"> <aop:before pointcut="execution(* com.techlog.test.service.Performer.perform(..))" method="takeSteats" /> <aop:before pointcut="execution(* com.techlog.test.service.Performer.perform(..))" method="turnOffCellPhones"/> <aop:after-returning pointcut="execution(* com.techlog.test.service.Performer.perform(..))" method="applaud"/> <aop:after-throwing pointcut="execution(* com.techlog.test.service.Performer.perform(..))" method="demandRefund"/> </aop:aspect> </aop:config>

 

 

运行起来,我们看到标准输出中打出了:

taking seats

turning off phones

juggling perform

clap clap

 

可以看到当我们调用 Juggling 实例的 perform 方法前后,Spring 按照我们的声明,调用了指定的方法

 

然而,由于 pointcut 的值是相同的,我们可以使用 <aop:pointcut> 标签统一定义他们,并使用 pointcut-ref 属性来引用:

<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(* com.techlog.test.service.Performer.perform(..))"/> <aop:before pointcut-ref="performance" method="takeSteats" /> <aop:before pointcut-ref="performance" method="turnOffCellPhones"/> <aop:after-returning pointcut-ref="performance" method="applaud"/> <aop:after-throwing pointcut-ref="performance" method="demandRefund"/> </aop:aspect> </aop:config>

 

 

如果我们需要知道整个表演的用时,那么使用前置通知和后置通知就受到了限制,因为需要实现这个功能则必须保存启动时间,而 Audience 类实例是单例的,所以如果让他在成员变量中保存表演开始的时间,就会存在线程安全问题

这时,环绕通知就具有了明显的优势,我们可以使用一个方法实现上面前置、后置通知的全部功能,并计算演出的耗时:

package com.techlog.test.service; import org.aspectj.lang.ProceedingJoinPoint; /** * Created by techlog on 16/3/25. */ public class Audience { // around perform public void watchPerformance(ProceedingJoinPoint joinPoint) { System.out.println("taking seats"); System.out.println("turning off phones"); long start = System.currentTimeMillis(); try { joinPoint.proceed(); } catch (Throwable throwable) { System.out.println("refunding money"); return; } System.out.println("clap clap"); System.out.println("The performance took " + (System.currentTimeMillis() - start) + " milliseconds"); } }

 

 

在这个方法中,我们使用 ProceedingJoinPoint 类对象作为传入参数,通过 ProceedingJoinPoint 的 proceed 方法能够让我们在通知里调用被通知方法

ProceedingJoinPoint 参数让环绕通知具有了很大的灵活性,你可以控制不调用或在任何时刻调用被通知方法甚至可以调用多次

 

接下来,我们需要为环绕通知声明切面配置:

<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(* com.techlog.test.service.Performer.perform(..))"/> <aop:around pointcut-ref="performance" method="watchPerformance"/> </aop:aspect> </aop:config>

 

 

有的时候,我们需要实现一个功能,每当调用某个方法时,将传递给这个方法的某个参数传递给我们的通知对象

下面的代码实现了这个功能:

 

我们首先创建一个通知类:

package com.techlog.test.service.implement; /** * Created by techlog on 16/3/28. */ public class Magician { private String thoughts; public void interceptThoughts(String thoughts) { System.out.println(thoughts); this.thoughts = thoughts; } public String getThoughts() { return thoughts; } }

 

 

再创建一个被切面“窃听”的接口和实现类:

package com.techlog.test.service; /** * Created by techlog on 16/3/28. */ public interface Thinker { void think(String thoughs); }

 

 

package com.techlog.test.service.implement; import com.techlog.test.service.Thinker; /** * Created by techlog on 16/3/28. */ public class Volunteer implements Thinker { private String thoughts; public String getThoughts() { return thoughts; } @Override public void think(String thoughts) { this.thoughts = thoughts; } }

 

 

然后我们创建 bean:

<bean id="magician" class="com.techlog.test.service.implement.Magician"/> <aop:config> <aop:aspect ref="magician"> <aop:before method="interceptThoughts" pointcut-ref="thinking" arg-names="thoughts"/> <aop:pointcut id="thinking" expression="execution(* com.techlog.test.service.Thinker.think(String)) and args(thoughts)"/> </aop:aspect> </aop:config>

 

 

这样,每当调用 think 方法时,他的 thoughts 参数都会传递给代理类 Magician 的对象

 

正如前面介绍中所说,Spring 的面向切面是通过代理类实现的,那么,代理类在调用被代理类方法的同时,同样可以通过这种委托的机制模拟类中不存在的方法,如下图所示:

 

 

有时,我们需要为所有的某个类的实现引入新的接口,这样的场景下这个功能就很有用了

apo 提供了 <aop:declare-parents> 元素实现这个功能:

 

首先我们先创建一个被引入的接口和实现类:

package com.techlog.test.service; /** * Created by techlog on 16/3/28. */ public interface Contestant { void receiveAward(); }

 

 

package com.techlog.test.service.implement; import com.techlog.test.service.Contestant; /** * Created by techlog on 16/3/28. */ public class GraciousContestant implements Contestant { @Override public void receiveAward() { System.out.println("receiveAward"); } }

 

 

然后我们创建一个 aop 配置:

<aop:config> <aop:aspect> <aop:declare-parents types-matching="com.techlog.test.service.Performer+" implement-interface="com.techlog.test.service.Contestant" default-impl="com.techlog.test.service.implement.GraciousContestant"/> </aop:aspect> </aop:config>

 

这样,我们就把 GraciousContestant 类对象引入了通知的代理类

 

同样,如果我们为 GraciousContestant 类创建一个 bean,可以将 default-impl 换成 delegate-ref 来引入这个 bean,这样的好处是,bean 本身可以设置创建参数、被注入或被代理,很大程度上增加了他的灵活性

 






技术帖      web      xml      龙潭书斋      framework      spring      ioc      aop      面向切面      设计模式      aspect      aspectj      切面      切点      通知      aop:config     


京ICP备15018585号