笔记7 AOP练习<有疑问>
场景描述:
核心业务:举行一场古典音乐会。
周边功能:观众入场,关闭手机、落座,觉得音乐好听时鼓掌,觉都不好听则退票。(切面)
1.编写切点(切点用于准确定位应该在什么地方应用切面的通 知)————即核心业务
首先定义一个Performance接口:
1 package concert; 2 3 public interface Performance { 4 public void perform(); 5 }
2.定义切面,即编写Audience.java。
Audience类使用@Aspect注解进行了标注。该注解表明Audience不仅仅是一个POJO,还是一个切面。
@Pointcut注解能够在一 个切面内定义可重用的切点。
也可以在通知注解中直接使用切点表达式。
1 package concert; 2 3 import org.aspectj.lang.annotation.AfterReturning; 4 import org.aspectj.lang.annotation.AfterThrowing; 5 import org.aspectj.lang.annotation.Aspect; 6 import org.aspectj.lang.annotation.Before; 7 import org.aspectj.lang.annotation.Pointcut; 8 import org.springframework.stereotype.Component; 9 10 @Aspect 11 @Component 12 public class Audience { 13 @Pointcut("execution(** concert.Performance.perform(..))") 14 public void perform() { 15 16 } 17 18 // @Before("execution(** concert.Performance.perform(..))") 19 @Before("perform()") 20 public void silenceCellPhones() { 21 System.out.println("Silencing cell phone"); 22 } 23 24 // @Before("execution(** concert.Performance.perform(..))") 25 @Before("perform()") 26 public void takeSeats() { 27 System.out.println("Taking seats"); 28 } 29 30 // @AfterReturning("execution(** concert.Performance.perform(..))") 31 @AfterReturning("perform()") 32 public void applause() { 33 System.out.println("CLAP CLAP CLAP"); 34 } 35 36 // @AfterThrowing("execution(** concert.Performance.perform(..))") 37 @AfterThrowing("perform()") 38 public void demandRefund() { 39 System.out.println("Demanding a refund"); 40 } 41 }
Audience有四个方法,定义了一个观众在观看演出时可能会做的事 情。在演出之前,观众要就坐(takeSeats())并将手机调至静音 状态(silenceCellPhones())。如果演出很精彩的话,观众应 该会鼓掌喝彩(applause())。不过,如果演出没有达到观众预期 的话,观众会要求退款(demandRefund())。 这些方法都使用了通知注解来表明它们应该在什么时候调用。
在这里也可以使用环绕通知,环绕通知是最为强大的通知类型。它能够让你所编写的逻辑将被通知 的目标方法完全包装起来。实际上就像在一个通知方法中同时编写前 置通知和后置通知,代码如下:
1 @Around("performs()") // 环绕通知方法 2 public void watchPerformance(ProceedingJoinPoint jp) { 3 try { 4 System.out.println("Silencing cell phone"); 5 System.out.println("Taking seats"); 6 jp.proceed(); 7 System.out.println("CLAP CLAP CLAP"); 8 } catch (Throwable e) { 9 System.out.println("Demanding a refund"); 10 } 11 }
在这里,@Around注解表明watchPerformance()方法会作 为performance()切点的环绕通知。ProceedingJoinPoint作为参数。这个对象是必须要有的,因为要在通知中通过它来调用被通知的方法。通知方法中可以做任何的 事情,当要将控制权交给被通知的方法时,它需要调 用ProceedingJoinPoint的proceed()方法。
<可以不调用proceed()方法,从而阻塞对被通知方 法的访问,与之类似,也可以在通知中对它进行多次调用。要这样 做的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行 重复尝试。>
3.定义Java配置文件ConcertConfig.java,使用Spring自动装配。在Java配置文件中启用AspectJ注解的自动代理。
AspectJ自动代理都会为使 用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面 的切点所匹配的bean。
1 package concert; 2 3 import org.springframework.context.annotation.ComponentScan; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.context.annotation.EnableAspectJAutoProxy; 6 7 @Configuration 8 @EnableAspectJAutoProxy // 启用AspectJ自动代理 9 @ComponentScan 10 public class ConcertConfig { 11 12 }
也可以定义XML配置文件
在Spring中要使用XML来装配bean的话,那么需要使用Spring aop命名空间中的<aop:aspectj-autoproxy>元素启用自动代理。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:tx="http://www.springframework.org/schema/tx" 6 xmlns:context="http://www.springframework.org/schema/context" 7 xmlns:c="http://www.springframework.org/schema/c" 8 xsi:schemaLocation=" 9 http://www.springframework.org/schema/beans 10 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 11 http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 13 http://www.springframework.org/schema/tx 14 http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 15 http://www.springframework.org/schema/context 16 http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 17 <context:component-scan base-package="concert"></context:component-scan> 18 <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!--启用自动代理--> 19 <!-- <bean class="concert.Audience"></bean> 20 <bean class="concert.Classcial"></bean> --> 21 </beans>
注:在使用XML进行装配的时候,如果在XML声明了bean后,一定要去掉两个bean原来的@Component注解,且不用使用自动代理;如果不在XML文件中声明bean,在Audience和Classcial中添加@Component注解,则可以启用自动代理。
4.编写测试文件ConcertTest.java
package concert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = concert.ConcertConfig.class) public class ConcertTest { @Autowired private Performance perform; @Test public void test() { perform.perform(); } }
疑问:在进行测试时,只能定义接口的对象来进行测试,定义接口的实现类对象时就会报错。
5.结果
两种配置方式结果一样