一.Spring--面向切面
在软件开发中,散布于应用中多处的功能被称为横切关注点(cross- cutting concern)。一般来说,这些横切关注点从概念上是与应用的业 务逻辑相分离的(可是每每会直接嵌入到应用的业务逻辑之中)。把 这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。
--什么是面向切面编程
切面能帮助咱们模块化横切关注点。简而言之,横切关注 点能够被描述为影响应用多处的功能。例如,安全就是一个横切关注 点,应用中的许多方法都会涉及到安全规则:java
--如上图展示的模块划分的典型应用.每一个模块的核心功能都是为特定业务领域提供服务,可是这些模块都须要相似的辅助功能,例如:安全及事务.若是要重用通用功能的话,那么最多见的面向对象的技术就是继承和委托,可是若是在整个应用中都是用相同的基类,继承每每会致使一个脆弱的对象体系;而使用委托则须要对委托对象进行复杂的调用.而使用切面编程的技术,则能够经过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类.横切关注点就能够被模块化为特殊的类,这些类被称为切面(sapect).这样作有两个好处:首先,如今每一个 关注点都集中于一个地方,而不是分散到多处代码中;其次,服务模 块更简洁,由于它们只包含主要关注点(或核心功能)的代码,而次 要关注点的代码被转移到切面中了。
--定义AOP术语
与大多数技术同样,AOP已经造成了本身的术语。描述切面的经常使用术 语有通知(advice)、切点(pointcut)和链接点(join point):spring
--通知(Advice):正如以前所提到的Knight的示例,骑士在接收了任务以后,不须要去强迫一个吟游诗人来完成对其功绩的歌颂,他只须要最好本身份内的事(拯救公主或者杀死恶龙),无需去关心吟游诗人是在何时.怎么样称赞他的.所以切面也有目标——它必需要完成的工做。在AOP术语中,切 面的工做被称为通知。通知定义了切面是什么以及什么时候使用。除了描述切面要完成的工做, 通知还解决了什么时候执行这个工做的问题。它应该应用在某个方法被调 用以前?以后?以前和以后都调用?仍是只在方法抛出异常时调用?在Spring之中,提供了5种通知的类型:
1.前置通知(Before):在目标方法被调用以前调用通知功能;
2.后置通知(After):在目标方法完成以后调用通知,此时不会关 心方法的输出是什么;
3.返回通知(After-returning):在目标方法成功执行以后调用通 知;
4.异常通知(After-throwing):在目标方法抛出异常后调用通知;
5.环绕通知(Around):通知包裹了被通知的方法,在被通知的方 法调用以前和调用以后执行自定义的行为。
--链接点(Join Point):对于骑士来讲,完成了他的任务以后,必然会在悬赏公告牌中告知你们这个骑士完成了怎样的壮举,而对于吟游诗人来讲这个发布骑士完成任务的点就是他所须要进行赞颂准备的点.对于切面来讲,这就是拯救点.事实上,在切面编程之中,这个链接点能够是调用方法时(骑士正在执行任务),抛出异常时(骑士执行任务遇到困难时),程序修改一个字段时(骑士在中途进行修整时),切面代码 能够利用这些点插入到应用的正常流程之中,并添加新的行为。
--切点(Pointcut):对于吟游诗人来讲,他不须要去关心这个骑士中途多有详细任务报告的公布(各个链接点),对于他来讲,他十分清楚本身的职责(专门负责歌颂骑士的凯旋归来又或者是专门歌颂骑士在中途遇到困难时的坚韧不拔),而这就是所谓的切点.相似地,一个切面并不须要通知应用的全部链接点。切点有助于 缩小切面所通知的链接点的范围。若是说通知定义了切面的“什么”和“什么时候”的话,那么切点就定义 了“何处”。切点的定义会匹配通知所要织入的一个或多个链接点。
--切面(Aspect):当一个吟游诗人准备歌颂一个骑士的时候,他须要明确的整理本身收集到的这个骑士他所须要称赞的点(执行过程仍是凯旋归来的结局)的全部细节.切面就是通知和切点的结合通知和切点共同定义了切面的所有内容 ——它是什么,在什么时候和何处完成其功能。
--引入(Introduction):引入容许咱们向现有的类添加新方法或属性。例如,咱们能够建立一 个Auditable通知类,该类记录了对象最后一次修改时的状态。这 很简单,只需一个方法,setLastModified(Date),和一个实例 变量来保存这个状态。而后,这个新方法和实例变量就能够被引入到 现有的类中,从而能够在无需修改这些现有的类的状况下,让它们具 有新的行为和状态。
--织入(Weaving):织入是把切面应用到目标对象并建立新的代理对象的过程。切面在指 定的链接点被织入到目标对象中。在目标对象的生命周期里有多个点 能够进行织入:
1.编译期:切面在目标类编译时被织入。这种方式须要特殊的编译 器。AspectJ的织入编译器就是以这种方式织入切面的。
2.类加载期:切面在目标类加载到JVM时被织入。这种方式须要特 殊的类加载器(ClassLoader),它能够在目标类被引入应用 以前加强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。
3.运行期:切面在应用运行的某个时刻被织入。通常状况下,在织 入切面时,AOP容器会为目标对象动态地建立一个代理对象。 Spring AOP就是以这种方式织入切面的。
--总结:通知包含了须要用于多个应用对象的横切行为;链接点是程序执行过程当中可以应用通知的全部点;切点定义了通知被应用的 具体位置(在哪些链接点)。其中关键的概念是切点定义了哪些链接 点会获得通知。express
二.Spring对AOP的支持
Spring提供了四种类型的AOP支持:
1.基于代理的经典Spring AOP;
2.纯POJO切面;
3.@AspectJ注解驱动的切面;
4.注入式AspectJ切面(适用于Spring各版本)。
--前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础 之上,所以,Spring对AOP的支持局限于方法拦截。借助Spring的aop命名空间,咱们能够将纯POJO转换为切面。实际 上,这些POJO只是提供了知足切点条件时所要调用的方法。这种技术须要XML配置.在深刻探讨Spring的AOP技术以前,咱们须要对Spring AOP框架的一些关键性技术进行了解:
1.Spring通知是Java编写的:Spring所建立的通知都是用标准的Java类编写的。这样的话,咱们就 可使用与普通Java开发同样的集成开发环境(IDE)来开发切面。 并且,定义通知所应用的切点一般会使用注解或在Spring配置文件里 采用XML来编写,这两种语法对于Java开发者来讲都是至关熟悉的。
2.Spring在运行时通知对象:经过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理 的bean中,代理类封装了目标类,并拦截被通知方法的 调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时, 在调用目标bean方法以前,会执行切面逻辑:编程
直到应用须要被代理的bean时,Spring才建立代理对象。若是使用的 是ApplicationContext的话,在ApplicationContext从 BeanFactory中加载全部bean的时候,Spring才会建立被代理的对 象。由于Spring运行时才建立代理对象,因此咱们不须要特殊的编译 器来织入Spring AOP的切面。
3.Spring只支持方法级别的链接点:由于Spring基于动态代理,因此Spring只支持方法链接点。Spring缺乏对字段链接点的 支持,没法让咱们建立细粒度的通知,例如拦截对象字段的修改。而 且它不支持构造器链接点,咱们就没法在bean建立时应用通知。安全
三,经过切点来选择链接点
在Spring AOP中,使用AspectJ的切点表达式语言来定义切点;Spring是基于代理的,而某些切点表达式是于基于代理的AOP无关的,给出SpringAOP支持的AspectJ切点指示器:app
--当咱们查看如上所展现的这些Spring支持的指示器时,注意只 有execution指示器是实际执行匹配的,而其余的指示器都是用来 限制匹配的。这说明execution指示器是咱们在编写切点定义时最 主要使用的指示器。在此基础上,咱们使用其余指示器来限制所匹配的切点。
--编写切点:定义Performance接口做为咱们的自定义切面的切点:框架
1 package 面向切面编程; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public interface Performance { 8 /**
9 * 触发方法 10 */
11 void perform(); 12 }
--观察切点表达式的构成:编程语言
--使用execution()指示器选择Performance的perform()方 法。方法表达式以“*”号开始,代表了咱们不关心方法返回值的类 型。而后,咱们指定了全限定类名和方法名。对于方法参数列表,我 们使用两个点号(..)代表切点要选择任意的perform()方法,无 论该方法的入参是什么,假设如今所须要的切点仅匹配concert包:ide
--使用"&&"操做符讲execution()和within()指示器链接在一块儿(切点必须匹配全部的指示器),类 似地,咱们可使用“||”操做符来标识或(or)关系,而使用“!”操 做符来标识非(not)操做(由于“&”在XML中有特殊含义,因此在Spring的XML配置里面描述切 点时,咱们可使用and来代替“&&”。一样,or和not能够分别用来 代替“||”和“!”).
--在切点中选择bean,Spring还引入了一个新的bean指示,他容许咱们在切点表达式中使用bean的ID做为标识来限定指示器:execution(* concert.Performance.perform()) and bean('student');这个表达式表名了咱们但愿执行performance的perform()方法时应用通知,可是限定了bean的ID为student,固然也能够限定在bean的ID不是'student'的状况下应用通知:execution(* concert.Performance.perform()) and !bean('student');
--使用注解建立切面模块化
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
8 /**
9 * @author : S K Y 10 * @version :0.0.1 11 */
12 @Aspect //使用@Aspect注解将该类设置为特殊的切面类
13 public class Audience { 14 private String name; 15
16 @Before("execution(* *.concert.Performance.perform(..))") 17 public void silenceCellPhone() { //在表演开始以前,观众须要将他的手机静音
18 System.out.println("表演即将开始,观众" + name + "将他的手机设置为了静音状态;"); 19 } 20
21 @Before("execution(* *.concert.Performance.perform(..))") 22 public void takeSeats() { 23 System.out.println("表演还有5分钟就要开始了,观众" + name + "入座等待;"); 24 } 25
26 @AfterReturning("execution(* *.concert.Performance.perform(..))") 27 public void applause() { 28 System.out.println("表演结束,观众" + name + "为演员们热烈的鼓掌;"); 29 } 30
31 @AfterThrowing("execution(* *.concert.Performance.perform(..))") 32 public void demandRefund() { 33 System.out.println("因为线程设备故障,观众" + name + "要求退款...."); 34 } 35 }
--在AspectJ中提供了五个注解来定义通知:
--在配置文件中,如我咱们想要开启切面支持,那么须要让Spring配置文件知道咱们切面类的存在,在JavaConfig中咱们可使用@EnableAspectJAutoProxy注解来开启AspectJ的自动代理,同时还须要开启@ComponentScan注解来实现咱们的包扫描,从而Spring才能识别咱们@AspectJ注解声明的类:
1 package 面向切面编程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public class DancingPerformance implements Performance { 8 @Override 9 public void perform() { 10 System.out.println("演员们表演精彩的舞蹈...."); 11 } 12 }
1 package 面向切面编程.concert; 2
3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.ComponentScan; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.context.annotation.EnableAspectJAutoProxy; 7
8 /**
9 * @author : S K Y 10 * @version :0.0.1 11 */
12 @Configuration 13 @EnableAspectJAutoProxy //启用AspectJ的自动代理
14 @ComponentScan(basePackages = {"面向切面编程.concert"}) 15 public class AudienceConfig { 16 @Bean 17 public Audience audienceA() { 18 return new Audience("观众A"); 19 } 20
21 @Bean 22 public Performance performance() { 23 return new DancingPerformance(); 24 } 25 }
1 package 面向切面编程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8
9 /**
10 * @author : S K Y 11 * @version :0.0.1 12 */
13 @RunWith(SpringJUnit4ClassRunner.class) 14 @ContextConfiguration(classes = AudienceConfig.class) 15 public class AudienceTest { 16
17 @Autowired 18 private Performance performance; 19
20 @Test 21 public void testAudienceA() { 22 performance.perform(); 23 } 24 }
--运行结果
表演即将开始,观众观众A将他的手机设置为了静音状态; 表演还有5分钟就要开始了,观众观众A入座等待; 演员们表演精彩的舞蹈.... 表演结束,观众观众A为演员们热烈的鼓掌; Process finished with exit code 0
--能够看到使用注解轻松的实现了咱们的AOP编程,可是,此时仍然存在问题,在Audience中咱们屡次书写了相同的切面表达式,事实上,咱们可使用@Pointcut注解来声明咱们的切点表达式
1 package 面向切面编程.concert; 2
3 import org.aspectj.lang.annotation.*; 4
5 /**
6 * @author : S K Y 7 * @version :0.0.1 8 */
9 @Aspect //使用@Aspect注解将该类设置为特殊的切面类
10 public class Audience { 11 private String name; 12
13 public Audience(String name) { 14 this.name = name; 15 } 16
17 @Pointcut("execution(* *.concert.Performance.perform(..))") 18 public void performance() { 19 } 20
21 @Before("performance()") 22 public void silenceCellPhone() { //在表演开始以前,观众须要将他的手机静音
23 System.out.println("表演即将开始,观众" + name + "将他的手机设置为了静音状态;"); 24 } 25
26 @Before("performance()") 27 public void takeSeats() { 28 System.out.println("表演还有5分钟就要开始了,观众" + name + "入座等待;"); 29 } 30
31 @AfterReturning("performance()") 32 public void applause() { 33 System.out.println("表演结束,观众" + name + "为演员们热烈的鼓掌;"); 34 } 35
36 @AfterThrowing("performance()") 37 public void demandRefund() { 38 System.out.println("因为线程设备故障,观众" + name + "要求退款...."); 39 } 40
41 }
--在Audience中,performance()方法使用了@Pointcut注解。 为@Pointcut注解设置的值是一个切点表达式,就像以前在通知注 解上所设置的那样。经过在performance()方法上添 加@Pointcut注解,咱们实际上扩展了切点表达式语言,这样就可 以在任何的切点表达式中使用performance()了,若是不这样作的 话,你须要在这些地方使用那个更长的切点表达式。performance()方法的实际内容并不重要,在这里它实际上应该是 空的。其实该方法自己只是一个标识,供@Pointcut注解依附。
--使用XML完成配置:若是想要在Spring的XML配置文件中来装配这个@AspectJ声明的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" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
6 <!--开启AspectJ动态代理-->
7 <aop:aspectj-autoproxy/>
8 <bean class="面向切面编程.concert.Audience" c:name="小明"/>
9 <bean class="面向切面编程.concert.DancingPerformance" id="performance"/>
10 </beans>
1 package 面向切面编程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8
9 /**
10 * @author : S K Y 11 * @version :0.0.1 12 */
13 @RunWith(SpringJUnit4ClassRunner.class) 14 @ContextConfiguration(locations = {"classpath:面向切面编程/concert/audienct-config.xml"}) 15 public class XMLConfigTest { 16 @Autowired 17 private Performance performance; 18
19 @Test 20 public void testAudience() { 21 performance.perform(); 22 } 23 }
--建立环绕通知
环绕通知是最为强大的通知类型,能够在一个通知方法中同时完成前置通知,后置通知以及异常通知,重写咱们的Audience类:
1 package 面向切面编程.concert; 2
3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.*; 5
6 /**
7 * @author : S K Y 8 * @version :0.0.1 9 */
10 @Aspect //使用@Aspect注解将该类设置为特殊的切面类
11 public class Audience { 12 private String name; 13
14 public Audience(String name) { 15 this.name = name; 16 } 17
18 @Pointcut("execution(* *.concert.Performance.perform(..))") 19 public void performance() { 20 } 21
22 @Around("performance()") 23 public void watchPerform(ProceedingJoinPoint point) { //观众观看演出
24 try { 25 System.out.println("表演即将开始,观众" + name + "将他的手机设置为了静音状态;"); 26 System.out.println("表演还有5分钟就要开始了,观众" + name + "入座等待;"); 27 point.proceed(); //执行方法
28 System.out.println("表演结束,观众" + name + "为演员们热烈的鼓掌;"); 29 } catch (Throwable throwable) { 30 System.out.println("因为线程设备故障,观众" + name + "要求退款...."); 31 } 32 } 33 }
--在这个新的通知方法中,接受一个ProceedingJoinPoint做为参数,而且这个对象是必需要有的,由于咱们须要在通知中经过他来调用被通知的方法,通知方法中能够作任何的事情,当要将控制权交给被通知的方法时,那么会调用ProceedingJoinPoint的proceed()方法.若是咱们不去调用这个方法,咱们的通知会阻塞咱们被通知方法的调用.有意思的是,你能够不调用proceed()方法,从而阻塞对被通知方 法的访问,与之相似,你也能够在通知中对它进行屡次调用。要这样 作的一个场景就是实现重试逻辑,也就是在被通知方法失败后,进行 重复尝试。
--处理通知中的参数
到目前为止,咱们的切面都很简单,没有任何参数。惟一的例外是我 们为环绕通知所编写的watchPerformance()示例方法中使用了 ProceedingJoinPoint做为参数。除了环绕通知,咱们编写的其 他通知不须要关注传递给被通知方法的任意参数。这很正常,由于我 们所通知的perform()方法自己没有任何参数。可是若是咱们进行切面通知的方法确实有参数存在,切面如何访问和使用传递给被通知的方法的参数呢?例如:对于一个晚会表演来讲,主持人也是必不可少的,对于Performance来讲,可能存在多个须要表演的节目,可是对于当前的表演来讲,须要通知观众进行表演的不该该是演员和表演自己(即不该该由Performance类来完成对于晚会的播报环节):
1 package 面向切面编程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public interface Performance { 8 /**
9 * 进行表演 10 */
11 void perform(); 12
13 /**
14 * 在拥有多个表演的节目的时候,表演当前指定的节目 15 * 16 * @param index 当前指定的节目的索引 17 * @param name 当前表演的节目的名称 18 */
19 void perform(int index, String name); 20 }
1 package 面向切面编程.concert; 2
3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.Before; 5 import org.aspectj.lang.annotation.Pointcut; 6
7 import java.util.List; 8
9 /**
10 * @author : S K Y 11 * @version :0.0.1 12 */
13 @Aspect 14 public class Host { 15 private List<String> dancingList; 16
17 @Pointcut("execution(* *.concert.Performance.perform(.. )) && args(index,name)") 18 private void performance(int index, String name) { 19 } 20
21 @Before("performance(index,name)") 22 public void host(int index, String name) { 23 System.out.println("[主持人]:当前为你们带来的是第" + (index + 1) + "个表演" + name +
24 ".请你们欣赏;"); 25 } 26
27 public List<String> getDancingList() { 28 return dancingList; 29 } 30
31 public void setDancingList(List<String> dancingList) { 32 this.dancingList = dancingList; 33 } 34 }
--只有主持人知道当前所须要进行的表演的序号和名称,而全部表演的演员只须要按照主持人所给出的清单进行表演:
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" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:util="http://www.springframework.org/schema/util"
6 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
7 <!--开启AspectJ动态代理-->
8 <aop:aspectj-autoproxy/>
9 <bean class="面向切面编程.concert.Audience" c:name="小明"/>
10 <util:list id="dancingList">
11 <value>芭蕾舞表演</value>
12 <value>孔雀舞表演</value>
13 <value>名族舞表演</value>
14 <value>花式敲代码表演</value>
15 </util:list>
16 <bean class="面向切面编程.concert.DancingPerformance" id="performance"/>
17 <bean class="面向切面编程.concert.Host">
18 <property name="dancingList" ref="dancingList"/>
19 </bean>
20
21 </beans>
1 package 面向切面编程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.test.context.ContextConfiguration; 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 8
9 import java.util.List; 10
11 /**
12 * @author : S K Y 13 * @version :0.0.1 14 */
15 @RunWith(SpringJUnit4ClassRunner.class) 16 @ContextConfiguration(locations = {"classpath:面向切面编程/concert/audienct-config.xml"}) 17 public class XMLConfigTest { 18 @Autowired 19 private Performance performance; 20 @Autowired 21 private Host host; 22
23 @Test 24 public void testAudienceWithInt() { 25 List<String> dancingList = host.getDancingList(); 26 for (int i = 0; i < dancingList.size(); i++) { 27 performance.perform(i, dancingList.get(i)); 28 } 29 } 30 }
--运行结果
表演即将开始,观众小明将他的手机设置为了静音状态; 表演还有5分钟就要开始了,观众小明入座等待; [主持人]:当前为你们带来的是第1个表演芭蕾舞表演.请你们欣赏; 演员们表演精彩的芭蕾舞表演... 表演结束,观众小明为演员们热烈的鼓掌; 表演即将开始,观众小明将他的手机设置为了静音状态; 表演还有5分钟就要开始了,观众小明入座等待; [主持人]:当前为你们带来的是第2个表演孔雀舞表演.请你们欣赏; 演员们表演精彩的孔雀舞表演... 表演结束,观众小明为演员们热烈的鼓掌; 表演即将开始,观众小明将他的手机设置为了静音状态; 表演还有5分钟就要开始了,观众小明入座等待; [主持人]:当前为你们带来的是第3个表演名族舞表演.请你们欣赏; 演员们表演精彩的名族舞表演... 表演结束,观众小明为演员们热烈的鼓掌; 表演即将开始,观众小明将他的手机设置为了静音状态; 表演还有5分钟就要开始了,观众小明入座等待; [主持人]:当前为你们带来的是第4个表演花式敲代码表演.请你们欣赏; 演员们表演精彩的花式敲代码表演... 表演结束,观众小明为演员们热烈的鼓掌; 九月 08, 2019 6:24:05 下午 org.springframework.context.support.GenericApplicationContext doClose 信息: Closing org.springframework.context.support.GenericApplicationContext@67b6d4ae: startup date [Sun Sep 08 18:24:05 CST 2019]; root of context hierarchy Process finished with exit code 0
--经过注解引入新功能
一些编程语言,例如Ruby和Groovy,有开放类的理念。它们能够不用 直接修改对象或类的定义就可以为对象或类增长新的方法。不过, Java并非动态语言。一旦类编译完成了,咱们就很难再为该类添加 新的功能了。可是事实上,咱们一直在使用AOP功能为已经编写完的方法来增长额外的功能,此外,其实咱们也能够为一个对象新增方法,利用被称为引入的AOP概念,切面能够为Spring bean添加新方法.回顾一下,在Spring中,切面只是实现了它们所包装bean相同接口的 代理。若是除了实现这些接口,代理也能暴露新接口的话,会怎么样 呢?那样的话,切面所通知的bean看起来像是实现了新的接口,即使 底层实现类并无实现这些接口也无所谓。
--为Performance实现引入MoreForPerformance接口
1 package 面向切面编程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public interface MoreForPerformance { 8 /**
9 * 在表演之中作更多的事 10 */
11 void moreForPerformance(); 12 }
--咱们须要一种方式将这个接口应用到Performance实现中.若是当前Performance的实现数量并很少,找到全部的实现,为他们都实现MoreForPerformance接口是没有什么问题的,可是若是当前具备上百个Performance的实现,这样作就显得特别的麻烦和不可取了,并且咱们须要知道的是并非全部的Performance的实现都须要MoreForPerformance支持的.同时若是有部分实现是由第三方jar来完成的,那么咱们也没法为全部的实现都添加MoreForPerformance接口.所以咱们能够借助AOP的引入功能,咱们能够没必要在设计上妥协或者侵入性的改变现有的实现.为此,咱们建立一个新的切面:
1 package 面向切面编程.concert; 2
3 /**
4 * @author : S K Y 5 * @version :0.0.1 6 */
7 public class MoreForPerformanceImpl implements MoreForPerformance { 8 @Override 9 public void moreForPerformance() { 10 System.out.println("[附加]舞蹈表演特别的棒,每个舞者脸上都流露着灿烂的笑容..."); 11 } 12 }
1 package 面向切面编程.concert; 2
3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.DeclareParents; 5
6 /**
7 * @author : S K Y 8 * @version :0.0.1 9 */
10 @Aspect 11 public class PointcutForMore { 12 @DeclareParents(value = "面向切面编程.concert.Performance+", defaultImpl = MoreForPerformanceImpl.class) 13 public static MoreForPerformance more; 14 }
--PointcutForMore是一个切面,可是其实现与其余所建立的切面都不一样,他没有提供前置,后置,环绕等通知,而是经过@DeclareParents注解,将MoreForPerformance接口引入到了Performance bean中.
--@DeclareParents注解由三部分组成:
1.value属性指定了哪一种类型的bean要引入该接口。在本例中,也 就是全部实现Performance的类型。(标记符后面的加号表示 是Performance的全部子类型,而不是Performance本 身。)
2.defaultImpl属性指定了为引入功能提供实现的类。在这里, 咱们指定的是MoreForPerformanceImpl提供实现。
3.@DeclareParents注解所标注的静态属性指明了要引入了接 口。在这里,咱们所引入的是MoreForPerformance 接口。
--在Spring中,注解和自动代理提供了一种很便利的方式来建立切面。 它很是简单,而且只涉及到最少的Spring配置。可是,面向注解的切 面声明有一个明显的劣势:你必须可以为通知类添加注解。为了作到 这一点,必需要有源码。 若是你没有源码的话,或者不想将AspectJ注解放到你的代码之中, Spring为切面提供了另一种可选方案:在Spring XML配置文件中声明切面.
--在XML中声明切面
在Spring的aop命名空间之中,提供了多个元素用来在XML中声明切面:
1 package 面向切面编程.concert; 2
3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.*; 5
6 /**
7 * @author : S K Y 8 * @version :0.0.1 9 */
10 @Aspect //使用@Aspect注解将该类设置为特殊的切面类
11 public class Audience { 12 private String name; 13
14 public Audience(String name) { 15 this.name = name; 16 } 17
18 @Pointcut("execution(* *.concert.Performance.perform(..))") 19 public void performance() { 20 } 21
22 public void silenceCellPhone() { //在表演开始以前,观众须要将他的手机静音
23 System.out.println("表演即将开始,观众" + name + "将他的手机设置为了静音状态;"); 24 } 25
26 public void takeSeats() { 27 System.out.println("表演还有5分钟就要开始了,观众" + name + "入座等待;"); 28 } 29
30 public void applause() { 31 System.out.println("表演结束,观众" + name + "为演员们热烈的鼓掌;"); 32 } 33
34 public void demandRefund() { 35 System.out.println("因为线程设备故障,观众" + name + "要求退款...."); 36 } 37
38 @Around("performance()") 39 public void watchPerform(ProceedingJoinPoint point) { //观众观看演出
40 try { 41 System.out.println("表演即将开始,观众" + name + "将他的手机设置为了静音状态;"); 42 System.out.println("表演还有5分钟就要开始了,观众" + name + "入座等待;"); 43 point.proceed(); 44 System.out.println("表演结束,观众" + name + "为演员们热烈的鼓掌;"); 45 } catch (Throwable throwable) { 46 System.out.println("因为线程设备故障,观众" + name + "要求退款...."); 47 } 48 } 49 }
--如今在Audience中,除了watchPerform为环绕通知外,其余的方法都不是通知,为了不测试结果混乱,咱们能够先将@Around注解注释掉,定义咱们的XML文件:
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" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:util="http://www.springframework.org/schema/util"
6 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
7 <!--开启AspectJ动态代理-->
8 <aop:aspectj-autoproxy/>
9 <bean class="面向切面编程.concert.Audience" c:name="小明" id="audience"/>
10 <util:list id="dancingList">
11 <value>芭蕾舞表演</value>
12 <value>孔雀舞表演</value>
13 <value>名族舞表演</value>
14 <value>花式敲代码表演</value>
15 </util:list>
16 <bean class="面向切面编程.concert.DancingPerformance" id="performance"/>
17 <bean class="面向切面编程.concert.Host">
18 <property name="dancingList" ref="dancingList"/>
19 </bean>
20 <bean class="面向切面编程.concert.PointcutForMore"/>
21
22 <aop:config>
23 <aop:aspect ref="audience">
24 <aop:pointcut id="pointcut" expression="execution(* *.concert.Performance.perform(..))"/>
25 <aop:before method="silenceCellPhone" pointcut-ref="pointcut"/>
26 <aop:before method="takeSeats" pointcut-ref="pointcut"/>
27 <aop:after-returning method="applause" pointcut-ref="pointcut"/>
28 <aop:after-throwing method="demandRefund" pointcut-ref="pointcut"/>
29 </aop:aspect>
30 </aop:config>
31
32 </beans>
--在<aop:config>元素内,咱们能够声明一个或多个通知器、切面 或者切点,给出AOP的总体业务逻辑:
--可是目前在前置通知和后置通知中有一些限制.若是不使用成员变量存储信息的话,在前置通知和后置通知之间共享信息特别的麻烦.假设除了进场关闭手机和表演结束后鼓掌,咱们还但愿观众确 保一直关注演出,并报告每一个参赛者表演了多长时间。使用前置通知 和后置通知实现该功能的惟一方式是在前置通知中记录开始时间并在 某个后置通知中报告表演耗费的时间。但这样的话咱们必须在一个成 员变量中保存开始时间。由于Audience是单例的,若是像这样保存 状态的话,将会存在线程安全问题。相对于前置通知和后置通知,环绕通知在这点上有明显的优点。使用 环绕通知,咱们能够完成前置通知和后置通知所实现的相同功能,而 且只须要在一个方法中 实现。由于整个通知逻辑是在一个方法内实 现的,因此不须要使用成员变量保存.
--在XML中实现环绕通知的方式和其余通知是同样的.一样,对于具备参数传递的通知,咱们也可使用XML配置文件的形式来实现:
1 <aop:config>
2 <aop:pointcut id="pointcut" expression="execution(* *.concert.Performance.perform(..))"/>
3 <aop:pointcut id="pointcutWithParam"
4 expression="execution(* *.concret.Performance.perform(..)) and args(index,name)"/>
5 <aop:aspect ref="audience">
6 <!-- <aop:before method="silenceCellPhone" pointcut-ref="pointcut"/> 7 <aop:before method="takeSeats" pointcut-ref="pointcut"/> 8 <aop:after-returning method="applause" pointcut-ref="pointcut"/> 9 <aop:after-throwing method="demandRefund" pointcut-ref="pointcut"/>-->
10 <aop:around method="watchPerform" pointcut-ref="pointcut"/>
11 </aop:aspect>
12 <aop:aspect ref="host">
13 <!--注意在XML中不该该使用&& 而应该使用关键字and-->
14 <aop:before method="host" pointcut-ref="pointcutWithParam" arg-names="index,name"/>
15
16 </aop:aspect>
17 </aop:config>
--一样咱们也能够在XML中使用Spring AOP命名空间中的<aop:declare-parents>标签完成引入功能:
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" xmlns:aop="http://www.springframework.org/schema/aop"
4 xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:util="http://www.springframework.org/schema/util"
6 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
7 <!--开启AspectJ动态代理-->
8 <aop:aspectj-autoproxy/>
9 <bean class="面向切面编程.concert.Audience" c:name="小明" id="audience"/>
10 <util:list id="dancingList">
11 <value>芭蕾舞表演</value>
12 <value>孔雀舞表演</value>
13 <value>名族舞表演</value>
14 <value>花式敲代码表演</value>
15 </util:list>
16 <bean class="面向切面编程.concert.DancingPerformance" id="performance"/>
17 <bean class="面向切面编程.concert.Host" id="host">
18 <property name="dancingList" ref="dancingList"/>
19 </bean>
20 <bean class="面向切面编程.concert.PointcutForMore" id="forMore"/>
21
22 <aop:config>
23 <aop:pointcut id="pointcut" expression="execution(* *.concert.Performance.perform(..))"/>
24 <aop:pointcut id="pointcutWithParam"
25 expression="execution(* *.concret.Performance.perform(..)) and args(index,name)"/>
26 <aop:aspect ref="audience">
27 <!-- <aop:before method="silenceCellPhone" pointcut-ref="pointcut"/> 28 <aop:before method="takeSeats" pointcut-ref="pointcut"/> 29 <aop:after-returning method="applause" pointcut-ref="pointcut"/> 30 <aop:after-throwing method="demandRefund" pointcut-ref="pointcut"/>-->
31 <aop:around method="watchPerform" pointcut-ref="pointcut"/>
32 </aop:aspect>
33 <aop:aspect ref="host">
34 <!--注意在XML中不该该使用&& 而应该使用关键字and-->
35 <aop:before method="host" pointcut-ref="pointcutWithParam" arg-names="index,name"/>
36
37 </aop:aspect>
38 <aop:aspect ref="forMore">
39 <aop:declare-parents types-matching="面向切面编程.concert.Performance+"
40 implement-interface="面向切面编程.concert.MoreForPerformance"
41 default-impl="面向切面编程.concert.MoreForPerformanceImpl"/>
42 </aop:aspect>
43 </aop:config>
44
45 </beans>
--顾名思义,<aop:declare-parents>声明了此切面所通知的bean 要在它的对象层次结构中拥有新的父类型。具体到本例中,类型匹 配Performance接口(由types-matching属性指定)的那些bean 在父类结构中会增长MoreForPerformance接口(由implement- interface属性指定)。最后要解决的问题是MoreForPerformance接口中 的方法实现要来自于何处。
--咱们能够进行测试,查看代理是否成功
1 package 面向切面编程.concert; 2
3 import org.junit.Test; 4 import org.junit.runner.RunWith; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.context.ApplicationContext; 7 import org.springframework.test.context.ContextConfiguration; 8 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9
10 import java.util.List; 11
12 /**
13 * @author : S K Y 14 * @version :0.0.1 15 */
16 @RunWith(SpringJUnit4ClassRunner.class) 17 @ContextConfiguration(locations = {"classpath:面向切面编程/concert/audienct-config.xml"}) 18 public class XMLConfigTest { 19 @Autowired 20 private Performance performance; 21 @Autowired 22 private Host host; 23 @Autowired 24 private PointcutForMore more; 25
26 @Autowired 27 private ApplicationContext context; 28 @Autowired 29 private MoreForPerformance moreForPerformance; 30
31 @Test 32 public void testMoreForPerformance() { 33 MoreForPerformance performanceMore = context.getBean("performance", MoreForPerformance.class); 34 performanceMore.moreForPerformance(); 35 Performance performance = context.getBean("performance", Performance.class); 36 performance.perform(); 37 } 38
39 }
--运行结果
[附加]舞蹈表演特别的棒,每个舞者脸上都流露着灿烂的笑容... 表演即将开始,观众小明将他的手机设置为了静音状态; 表演还有5分钟就要开始了,观众小明入座等待; 演员们表演精彩的舞蹈... 表演结束,观众小明为演员们热烈的鼓掌; Process finished with exit code 0
--咱们能够发现,成功的为performance bean添加了新的方法完成了咱们的实现
--虽然Spring AOP可以知足许多应用的切面需求,可是与AspectJ相比, Spring AOP 是一个功能比较弱的AOP解决方案。AspectJ提供了Spring AOP所不能支持的许多类型的切点。当咱们须要在建立对象时应用通知,构造器切点就很是方便。 不像某些其余面向对象语言中的构造器,Java构造器不一样于其余的正 常方法。这使得Spring基于代理的AOP没法把通知应用于对象的建立 过程。
附:AspectJ语法:
execution()表达内部写方法名,方法名加全类名,内部参数也须要表名全类名(两个方法用execution() or execution()链接):
public boolean addStudent(com.sky.model.Student):全部返回类型为Boolean而且返回参数为此的方法
public boolean com.sky.model.StudentService.addStudent(com.sky.model.Student): 只有该类下的addStudent方法
public * addStudent(com.sky.model.Student):返回值任意
public void * (com.sky.model.Student):方法名任意
public void addStudent(..) :任意参数列表
*com.sky.service.*.*(..):com.sky.service.StudentServic中的全部方法,不包含子包中的
*com.sky.service..*.*(..):com.sky.service.StudentServic中的全部方法,包含子包中的