Spring In Action<四>
2017-05-18 15:56
375 查看
四、面向切面的Spring
从生活的边边角角我们也许能更加感性的认知切面的应用环境:我们在日常生活中用水用电,供电局和自来水厂为了能够监控用水用电量给挨家挨户装了电表水表。从用户角度来讲这对我们来说是没有作用的,可能还会需要我们支出一笔费用,我们想着直接和供电局和自来水厂直接交互缴费,这个做法对于我们用户来讲没有损失,但是这么做就会给供电局带来一定的麻烦。软件系统中的某些功能就像我们的水表电表,这些功能需要在应用的多个地方使用到,日志、安全、事务管理的确很重要但是又不是业务逻辑功能。业务领域的问题由业务对象处理,而其他问题由其他对象处理。
这些功能就是横切关注点,这些横切关注点和业务逻辑相分离是面向切面编程所要解决的问题。今天我们的着重点就是如何把普通类声明为切面和如何通过注解创建切面,同时学习如何通过另一个技术AspectJ实现AOP同时补充Spring AOP框架的功能。
一.什么叫面向切面编程?
CourseService、StudentService、MiscService这三个模块都需要辅助功能。如果要重用这些辅助功能最常用的方法就是继承和委托。但是通过委托会导致复杂的委托对象的方法调用,所有类继承同一个基类的话往往会造成一个脆弱的对象体系。横切关注点可以模块化成特殊的类,这些类被称为切面,这样做的第一点好处就是每个关注点都集中在一个地方而不是分散在多处代码处,第二个好处就是业务逻辑代码块更加简洁。
二.AOP术语
描述切面常用的术语:通知、切点、连接点。
在一个或者多个连接点上可以把切面的功能织入到程序的执行过程中。
通知:通知定义了切面是什么以及何时用,在AOP术语中切面的工作叫通知。Spring定义了五种类型的通知:1)前置通知,在目标方法被调用之前调用通知功能;2)后置通知,在目标方法完成之后调用通知功能,该类型的通知不会考虑目标方法执行成功否;3)返回通知,在目标方法执行返回成功后调用通知功能;4)异常通知,在目标方法抛出异常后调用通知;5)环绕通知,通知抱过目标方法,在目标方法执行前和执行后执行自定义的行为。
连接点:是应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常的时候、可以是修改某个字段的时候。切面利用这些点来进行新的行为。
切点:通知定义了切面的“什么”和“何时”,切点定义了切面的“何处”。我们通常使用明确的类、方法、以及正则表达式定义所匹配的类和方法来指定这些切点。有些AOP框架允许创建动态切点从而根据运行时的状态决策是否通知。
切面:通知和切点定义了切面的所有内容-它是什么,在何时何处完成其功能
引入:允许我们向现有的类引入属性和方法。比如一个通知类有一个方法和一个实例变量又来保存状态,这个方法和实例变量就可以引入到现有类中。
织入:是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的周期里有几个点可以进行织入
1.编译期间:切面在目标类编译时织入,这个点织入切面对编译器有要求。AspectJ的织入编译器就是这种方式。
2.类加载器:切面在目标类加载到JVM时被织入
3.运行期:切面在应用运行的某个时刻被织入,在织入切面的时候一般AOP容器会为目标对象织入一个代理对象,Spring AOP就是这种方式。
三.Spring对AOP的支持
Spring提供了四种类型的对AOP的支持:1)基于代理的经典Spring AOP;2)纯POJO切面;3)@AspectJ注解驱动的切面;4)注入式AspectJ切面。
Spring AOP基于动态代理基础之上,所以Spring对AOP的支持局限于方法之上。Spring经典AOP时分笨重复杂,直接使用proxyfactory bean让人厌烦。借助aop命名空间我们可以将纯POJO转为切面,这些POJO只是提供了满足切点条件的方法,但是这种方法需要XML配置。Spring借鉴了AspectJ切面,提供了注解驱动的AOP,本质上依然是基于代理的AOP,与AspectJ的编程模型几乎一致。
四.学习Spring AOP技术准备
1)Spring通知是java编写的,定义通知应用的切点一般都是注解或者XML,所以方便,IDE也可以用java IDE。AspectJ是以java扩展的方式实现的,通过特有的AOP语言,我们可以加强对切面细微度的控制,但是学习起来肯定没有Spring简单。
2)Spring在运行时通知对象
代理类包裹切面,Spring在运行期就将切面织入到Spring管理的bean中。代理类封装了目标bean,并拦截被通知方法的调用,并把调用转发给真正的目标bean,当代理拦截到方法调用时,在调用目标bean方法之前执行切面逻辑。知道应用需要被代理的bean的时候Spring才会去创建代理对象。
3)Spring只能实现基于方法级别的连接点
AspectJ和JBoss可以实现字段和构造器级别的连接点。
五.通过切点来选择连接点
在Spring AOP中要用到AspectJ的切点表达式语言来定义切点,但是因为Spring是基于代理的,所以Spring仅支持AspectJ切点指示器的一个子集。下面具体展示一下Spring仅支持的AspectJ切点指示器的一个子集的具体内容:
arg():限制连接点匹配参数为指定类型的执行方法
@args:限制连接点匹配参数由指定注解标注的执行方法
execution():用于匹配是连接点的方法
this():限制连接点匹配AOP代理的bean引用为指定类型的类
target():限制连接点匹配目标对象为指定类型的类
@target:限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within():限制连接点匹配指定的类型
@within:限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在有指定的注解所标注的类里)
@annotation:限制匹配带有指定注解的连接点
六.编写切点
定义Performance接口作为切点:
我们编写Performance类的perform()触发的通知,切点表达式:execution(* concert.Performance.perform(..))。execution()指示器在方法执行时触发,*表示返回任意类型,concert.Performance.perform表示包名.类名.方法,(..)表示选择任意的perform方法,无论该方法是什么参数。
我们可以使用within()指示器来限制切点范围,比如:execution(* concert.Performance.perform(..))&&within(concert.*),但是XML中&有特殊含义,所以我们分别用and、or、not代表&&、||、!。
Spring还引入了一个新的指示器:bean(),它允许我们使用bean的ID来表示bean。bean()使用bean ID或者bean名称作为参数来限制切点只匹配特定的bean:execution(* concert.Perf
c68d
ormance.perform(..)) and bean('woodstock')。
七.使用注解创建切面
1)定义切面
Audience类使用@Aspect注解,说明Audience不仅仅是一个POJO类还是一个切面。Audience中的方法都可以使用注解来定义具体的行为。@Before通知方法会在目标方法调用之前调用,@After会在目标方法返回或者抛出异常的时候执行通知方法,@AfterReturning会在目标方法返回后执行通知方法,@AfterThrowing会在目标方法抛出异常后返回通知方法@Around环绕通知方法。
相同的切点表达式我们重复写了四遍,@Pointcut可以在一个@Aspect切面内定义重用的切点。
Audience只是一个java类,只是声明了它可以作为切面使用,我们依然可以像调用普通方法一下使用它。但是我们如果就此止步的话即使我们使用了@Aspect它也不会被视为切面。如果使用JavaConfig的话可以在配置类上通过使用@EnableAspectJAutoProxy注解启动自动代理功能。代码:
如果是适用XML进行显式bean装配的时候可以使用aop命名空间,代码如下:
环绕通知可以将被通知的目标方法包围起来,感觉就像一个通知方法中同时写了前置和后置方法。代码如下:
ProceedingJoinPoint jp这个参数是一定要有的,因为当要将控制权交给被通知的方法时需要调用它的proceed()方法,如果不调用就会阻塞对被通知方法的调用。
3)处理通知中的参数
如果切面通知的方法有参数怎么办,分析如下代码:
args(trackNumber)指定参数,perform(int)指定参数为int类型。
4)通过注解引入新功能
java不是动态语言,一旦编译完成就很难为其添加新方法。但是利用被称为引入的AOP概念就可以为其添加新功能。通过@DeclareParents注解实现。@DeclareParents(value="concert.Performance+",defaultImpl=DefaultEncoreable.class)它由三部分组成:value指定了那种类型的bean要引入该接口,concert.Performance+的+是指实Perfromance的所有子类型而不是其本身。defaultImpl指定引入功能提供实现的类。@DeclareParents注解所标注的静态属性指明了要引入了接口-Encoreable。
八.在XML中声明切面
在Spring的aop命名空间中提供了多个元素用来在XML中声明切面。如下:
<aop:advisor>:定义AOP通知器
<aop:after>:定义AOP后置通知,不管方法是否执行成功
<aop:after-returning>:定义返回通知
<aop:after-throwing>:定义异常通知
<aop:before>:定义AOP前置通知
<aop:around>:定义一个环绕通知
<aop:aspect>:定义一个切面
<aop:aspect-autoproxy>:启动@AspectJ注解驱动的切面
<aop:config>:顶层的aop标签,大多数aop标签都要包含在这个标签下
<aop:pointcut>:定义一个切点
1)定义前后置通知
2)声明环绕通知
3)为通知传递参数
4)通过切面引入新功能
<aop:around pointcut-ref="proformance" method="watchPerformance"/>
从生活的边边角角我们也许能更加感性的认知切面的应用环境:我们在日常生活中用水用电,供电局和自来水厂为了能够监控用水用电量给挨家挨户装了电表水表。从用户角度来讲这对我们来说是没有作用的,可能还会需要我们支出一笔费用,我们想着直接和供电局和自来水厂直接交互缴费,这个做法对于我们用户来讲没有损失,但是这么做就会给供电局带来一定的麻烦。软件系统中的某些功能就像我们的水表电表,这些功能需要在应用的多个地方使用到,日志、安全、事务管理的确很重要但是又不是业务逻辑功能。业务领域的问题由业务对象处理,而其他问题由其他对象处理。
这些功能就是横切关注点,这些横切关注点和业务逻辑相分离是面向切面编程所要解决的问题。今天我们的着重点就是如何把普通类声明为切面和如何通过注解创建切面,同时学习如何通过另一个技术AspectJ实现AOP同时补充Spring AOP框架的功能。
一.什么叫面向切面编程?
CourseService、StudentService、MiscService这三个模块都需要辅助功能。如果要重用这些辅助功能最常用的方法就是继承和委托。但是通过委托会导致复杂的委托对象的方法调用,所有类继承同一个基类的话往往会造成一个脆弱的对象体系。横切关注点可以模块化成特殊的类,这些类被称为切面,这样做的第一点好处就是每个关注点都集中在一个地方而不是分散在多处代码处,第二个好处就是业务逻辑代码块更加简洁。
二.AOP术语
描述切面常用的术语:通知、切点、连接点。
在一个或者多个连接点上可以把切面的功能织入到程序的执行过程中。
通知:通知定义了切面是什么以及何时用,在AOP术语中切面的工作叫通知。Spring定义了五种类型的通知:1)前置通知,在目标方法被调用之前调用通知功能;2)后置通知,在目标方法完成之后调用通知功能,该类型的通知不会考虑目标方法执行成功否;3)返回通知,在目标方法执行返回成功后调用通知功能;4)异常通知,在目标方法抛出异常后调用通知;5)环绕通知,通知抱过目标方法,在目标方法执行前和执行后执行自定义的行为。
连接点:是应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常的时候、可以是修改某个字段的时候。切面利用这些点来进行新的行为。
切点:通知定义了切面的“什么”和“何时”,切点定义了切面的“何处”。我们通常使用明确的类、方法、以及正则表达式定义所匹配的类和方法来指定这些切点。有些AOP框架允许创建动态切点从而根据运行时的状态决策是否通知。
切面:通知和切点定义了切面的所有内容-它是什么,在何时何处完成其功能
引入:允许我们向现有的类引入属性和方法。比如一个通知类有一个方法和一个实例变量又来保存状态,这个方法和实例变量就可以引入到现有类中。
织入:是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的周期里有几个点可以进行织入
1.编译期间:切面在目标类编译时织入,这个点织入切面对编译器有要求。AspectJ的织入编译器就是这种方式。
2.类加载器:切面在目标类加载到JVM时被织入
3.运行期:切面在应用运行的某个时刻被织入,在织入切面的时候一般AOP容器会为目标对象织入一个代理对象,Spring AOP就是这种方式。
三.Spring对AOP的支持
Spring提供了四种类型的对AOP的支持:1)基于代理的经典Spring AOP;2)纯POJO切面;3)@AspectJ注解驱动的切面;4)注入式AspectJ切面。
Spring AOP基于动态代理基础之上,所以Spring对AOP的支持局限于方法之上。Spring经典AOP时分笨重复杂,直接使用proxyfactory bean让人厌烦。借助aop命名空间我们可以将纯POJO转为切面,这些POJO只是提供了满足切点条件的方法,但是这种方法需要XML配置。Spring借鉴了AspectJ切面,提供了注解驱动的AOP,本质上依然是基于代理的AOP,与AspectJ的编程模型几乎一致。
四.学习Spring AOP技术准备
1)Spring通知是java编写的,定义通知应用的切点一般都是注解或者XML,所以方便,IDE也可以用java IDE。AspectJ是以java扩展的方式实现的,通过特有的AOP语言,我们可以加强对切面细微度的控制,但是学习起来肯定没有Spring简单。
2)Spring在运行时通知对象
代理类包裹切面,Spring在运行期就将切面织入到Spring管理的bean中。代理类封装了目标bean,并拦截被通知方法的调用,并把调用转发给真正的目标bean,当代理拦截到方法调用时,在调用目标bean方法之前执行切面逻辑。知道应用需要被代理的bean的时候Spring才会去创建代理对象。
3)Spring只能实现基于方法级别的连接点
AspectJ和JBoss可以实现字段和构造器级别的连接点。
五.通过切点来选择连接点
在Spring AOP中要用到AspectJ的切点表达式语言来定义切点,但是因为Spring是基于代理的,所以Spring仅支持AspectJ切点指示器的一个子集。下面具体展示一下Spring仅支持的AspectJ切点指示器的一个子集的具体内容:
arg():限制连接点匹配参数为指定类型的执行方法
@args:限制连接点匹配参数由指定注解标注的执行方法
execution():用于匹配是连接点的方法
this():限制连接点匹配AOP代理的bean引用为指定类型的类
target():限制连接点匹配目标对象为指定类型的类
@target:限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within():限制连接点匹配指定的类型
@within:限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在有指定的注解所标注的类里)
@annotation:限制匹配带有指定注解的连接点
六.编写切点
定义Performance接口作为切点:
package concert public interface Performance{ public void perform(); }
我们编写Performance类的perform()触发的通知,切点表达式:execution(* concert.Performance.perform(..))。execution()指示器在方法执行时触发,*表示返回任意类型,concert.Performance.perform表示包名.类名.方法,(..)表示选择任意的perform方法,无论该方法是什么参数。
我们可以使用within()指示器来限制切点范围,比如:execution(* concert.Performance.perform(..))&&within(concert.*),但是XML中&有特殊含义,所以我们分别用and、or、not代表&&、||、!。
Spring还引入了一个新的指示器:bean(),它允许我们使用bean的ID来表示bean。bean()使用bean ID或者bean名称作为参数来限制切点只匹配特定的bean:execution(* concert.Perf
c68d
ormance.perform(..)) and bean('woodstock')。
七.使用注解创建切面
1)定义切面
package spring.aop.demo; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class Audience { @Before("execution(* spring.aop.demo.Performance.perform(..))") public void silenceCellPhones(){ System.out.println("silenceCellPhones..."); } @Before("execution(* spring.aop.demo.Performance.perform(..))") public void takeSeats(){ System.out.println("takeSeats..."); } @AfterReturning("execution(* spring.aop.demo.Performance.perform(..))") public void applause(){ System.out.println("applause..."); } @AfterThrowing("execution(* spring.aop.demo.Performance.perform(..))") public void demandRefund(){ System.out.println("demandRefund..."); } }
Audience类使用@Aspect注解,说明Audience不仅仅是一个POJO类还是一个切面。Audience中的方法都可以使用注解来定义具体的行为。@Before通知方法会在目标方法调用之前调用,@After会在目标方法返回或者抛出异常的时候执行通知方法,@AfterReturning会在目标方法返回后执行通知方法,@AfterThrowing会在目标方法抛出异常后返回通知方法@Around环绕通知方法。
相同的切点表达式我们重复写了四遍,@Pointcut可以在一个@Aspect切面内定义重用的切点。
package spring.aop.demo; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class Audience { @Pointcut("execution(* spring.aop.demo.Performance.perform(..))") public void performance(){} @Before("performance()") public void silenceCellPhones(){ System.out.println("silenceCellPhones..."); } @Before("performance()") public void takeSeats(){ System.out.println("takeSeats..."); } @AfterReturning("performance()") public void applause(){ System.out.println("applause..."); } @AfterThrowing("performance()") public void demandRefund(){ System.out.println("demandRefund..."); } }
Audience只是一个java类,只是声明了它可以作为切面使用,我们依然可以像调用普通方法一下使用它。但是我们如果就此止步的话即使我们使用了@Aspect它也不会被视为切面。如果使用JavaConfig的话可以在配置类上通过使用@EnableAspectJAutoProxy注解启动自动代理功能。代码:
package spring.javaconfig; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import spring.aop.demo.Audience; @Configuration @EnableAspectJAutoProxy @ComponentScan public class AopConfig { public Audience audience(){ return new Audience(); } }
如果是适用XML进行显式bean装配的时候可以使用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:c="http://www.springframework.org/schema/c" 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"> <aop:aspectj-autoproxy/> </beans>2)创建环绕通知
环绕通知可以将被通知的目标方法包围起来,感觉就像一个通知方法中同时写了前置和后置方法。代码如下:
@Around("performance()") public void watchPerformance(ProceedingJoinPoint jp){ try{ System.out.println(""); System.out.println("2"); jp.proceed(); System.out.println("3"); }catch(Throwable a){ } }
ProceedingJoinPoint jp这个参数是一定要有的,因为当要将控制权交给被通知的方法时需要调用它的proceed()方法,如果不调用就会阻塞对被通知方法的调用。
3)处理通知中的参数
如果切面通知的方法有参数怎么办,分析如下代码:
@Pointcut("execution(* spring.aop.demo.Audience.perform(int)&&args(trackNumber))") private void trackPlayed(int trackNumber){} @Before("trackPlayed()") public void countTrack(int trackNumber){ }
args(trackNumber)指定参数,perform(int)指定参数为int类型。
4)通过注解引入新功能
java不是动态语言,一旦编译完成就很难为其添加新方法。但是利用被称为引入的AOP概念就可以为其添加新功能。通过@DeclareParents注解实现。@DeclareParents(value="concert.Performance+",defaultImpl=DefaultEncoreable.class)它由三部分组成:value指定了那种类型的bean要引入该接口,concert.Performance+的+是指实Perfromance的所有子类型而不是其本身。defaultImpl指定引入功能提供实现的类。@DeclareParents注解所标注的静态属性指明了要引入了接口-Encoreable。
八.在XML中声明切面
在Spring的aop命名空间中提供了多个元素用来在XML中声明切面。如下:
<aop:advisor>:定义AOP通知器
<aop:after>:定义AOP后置通知,不管方法是否执行成功
<aop:after-returning>:定义返回通知
<aop:after-throwing>:定义异常通知
<aop:before>:定义AOP前置通知
<aop:around>:定义一个环绕通知
<aop:aspect>:定义一个切面
<aop:aspect-autoproxy>:启动@AspectJ注解驱动的切面
<aop:config>:顶层的aop标签,大多数aop标签都要包含在这个标签下
<aop:pointcut>:定义一个切点
1)定义前后置通知
2)声明环绕通知
3)为通知传递参数
4)通过切面引入新功能
<aop:config> <aop:aspect ref="audience"> <!--为了防止老是写重复的切点表达式可以这么写--> <aop:pointcut id = "performance" expression="切点表达式"/>
<aop:before pointcut-ref="performance" method="方法名"> </aop-before><aop:before pointcut="切点表达式" method="方法名"> </aop-before> <aop:after pointcut="切点表达式" method="方法名"> </aop-after> <aop:after-returning pointcut="切点表达式” method="方法名"> </aop:after-returning>
<aop:after-throwing pointcut="切点表达式” method="方法名"> </aop:after-throwing> </aop:aspect></aop:config>
<aop:around pointcut-ref="proformance" method="watchPerformance"/>
<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>
<aop:aspect> <aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" delegate-ref="encoreableDelegate" /> </aop:aspect>
相关文章推荐
- SPRING IN ACTION 第4版笔记-第二章Wiring Beans-005-<constructor-arg>和c-namespace
- SPRING IN ACTION 第4版笔记-第六章RENDERING WEB VIEWS-005- 使用ApacheTiles(TilesConfigurer、TilesViewResolver、<put-attribute>、<t:insertAttribute>)
- SPRING IN ACTION 第4版笔记-第四章ASPECT-ORIENTED SPRING-010-Introduction为类增加新方法@DeclareParents、<aop:declare-parents>
- SPRING IN ACTION 第4版笔记-第六章RENDERING WEB VIEWS-002- Spring的JSP标签之form标签(<sf:input><sf:errors><sf:form>)
- SPRING IN ACTION 第4版笔记-第四章ASPECT-ORIENTED SPRING-005-定义切面使用@Aspect、@EnableAspectJAutoProxy、<aop:aspectj-autoproxy>
- SPRING IN ACTION 第4版笔记-第八章Advanced Spring MVC-002-SpringFlow的组件(state\<transition>\<var>\<set>\<evaluate>)
- SPRING IN ACTION 第4版笔记-第十一章Persisting data with object-relational mapping-006Spring-Data的运行规则(@EnableJpaRepositories、<jpa:repositories>)
- SPRING IN ACTION 第4版笔记-第二章WIRING BEANS-007-以set方法注入<property>\p-namespace\util-space
- SPRING IN ACTION 第4版笔记-第十一章Persisting data with object-relational mapping-002设置JPA的EntityManagerFactory(<persistence-unit>、<jee:jndi-lookup>)
- Spring In Action<五>
- SPRING IN ACTION 第4版笔记-第八章Advanced Spring MVC-007-给flowl加权限控制<secured>
- SPRING IN ACTION 第4版笔记-第六章RENDERING WEB VIEWS-004- <s:url>、<s:escapeBody>标签
- SPRING IN ACTION 第4版笔记-第二章WIRING BEANS-008-在XML配置文件中引入JAVA配置文件 <import> 、<bean>
- Spring In Action<一>
- Notes on <jQuery in Action> - 5
- struts2 委托spring代理action<转>
- Delegate Action<T in> Func<T in,out Tresult> Predicate<T>
- <Machine Learning in Action >之二 朴素贝叶斯 C#实现文章分类
- SPRING IN ACTION 第4版笔记-第六章RENDERING WEB VIEWS-003- SPRING的GENERAL TAG LIBRARY简介及用<s:message>和ReloadableResourceBundleMessageSource实现国际化
- <Machine Learning in Action >之四 二分k-均值算法 C#实现图像分割