《Spring实战》--笔记 第四章 面向切面的Spring

4.1 什么是面向切面编程

切面能够将横切关注点模块化,将其抽出来成为一个通用的功能。
在这里插入图片描述

4.1.1定义AOP术语

在这里插入图片描述

  • 通知(Advice)
    切面的工作成为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
    Spring切面可以定义5种通知:
    • 前置通知(before):在目标方法被调用前调用该通知功能
    • 后置通知(After):在目标方法被调用后调用该通知方法,不关心方法的输出是什么
    • 返回通知(After-returning):在目标方法成功执行后调用通知
    • 异常通知(After-throwing):在目标方法抛出异常后调用通知
    • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
  • 连接点(Join point)
    在程序执行过程中能够插入切面的一个点成为连接点,这个点可以是调用方法、抛出异常、甚至修改一个字段。切面代码通过这些连接点插入到应用的正常流程中,并添加新的行为。
  • 切点(Pointcut)
    切点的定义会匹配所要织入的一个或者多个连接点。通常是明确的类和方法名称,或者是用正则表达式定义所匹配的类和方法名。有些AOP框架可以创建动态的切点,可以在运行时候来决定是否进行应用的通知。
  • 切面(Aspect)
    切面是通知和切点的结合。通知和切点共同定义了切面的全部内容–它是什么,在何时和何处完成其功能。
  • 引入(Introduction)
    引入允许我们向现有的类添加新方法或者属性。
  • 织入
    织入是指把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期有多个点可以进行织入:
    • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
    • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用 之前增强该目标类的字节码。AspectJ5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。
    • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。 Spring AOP就是以这种方式织入切面的。

4.1.2 Spring对AOP的支持

Spring提供了4种类型的AOP支持:

  • 基于代理的经典Spring AOP
  • 纯POJO切面
  • @AspectJ注解驱动的切面
  • 注入式AspectJ切面(适用Spring各版本)

前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。

Spring借鉴了AspectJ的切面,编程模型和AspectJ几乎一致。如果AOP的需求超过了简单的方法调用(构造器,属性拦截),可以使用AspectJ来实现切面.

  1. Spring通知是Java写的,
    1. Spring创建的通知都是用Java类编写的。这样可以和使用和Java开发一样的IDE来开发切面,而且,定义通知应用的切点通常使用注解或者用xml编写,这两种语法对java开发者非常熟悉。
    2. AspectJ相反,他是通过Java语言拓展的方式实现的。
      • 优点通过特有的AOP语言,可以获得更强大和细粒度的控制,还有更丰富的AOP工具集.
      • 缺点是需要额外学习新的工具和语法
  2. Spring在运行时通知对象
    通过在代理中包裹切面,Spring在运行期 把切面织入到Spring管理的bean中。
    代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean,当代理拦截到方法调用时,在调用目标之前,会执行切面逻辑。
    直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP的切面。
    在这里插入图片描述
  3. Spring只支持方法级别的连接点
    因为Spring基于动态代理,所以只支持方法连接点,例如AspectJ和JBoss,除了支持方法切点,还支持字段和构造器接入点。
    • Spring缺少对字段连接点的支持,无法创建细粒度的通知,例如拦截对象字段的修改
    • 不支持构造器连接点,无法在bean创建时应用通知。但是方法拦截可以满足绝大部分的需求了。如果需要方法拦截之外的连接点拦截功能,可以利用AspectJ来补充。

4.2 通过切点来选择连接点

Spring使用AspectJ的切点表达式语言来定义切点,且只支持AspectJ切点指示器(Point designator)的一个子集。

AspectJ指示器描述
arg()限制连接点匹配参数为指定类型的执行方法
@args()限制连接点匹配参数由指定注解标注的执行方法
executing()用于匹配是连接点的执行方法
this()限制连接点匹配AOP代理的bean引用为指定类型的类
target限制连接点匹配目标对象为指定类型的类
@target限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within()限制连接点匹配指定的类型
@within()限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方 法定义在由指定的注解所标注的类里)
@annotation限定匹配带有指定注解的连接点

Spring中使用了其他的AspectJ指示器,会抛出IllegalArgument-Exception异常。

上面的方法中,只有executing()是用来实际执行匹配的,其他的指示器都是用来限制匹配的。所以execution使我们编写切点定义的主要使用的指示器。execution的基础之上使用其他指示器来限制匹配的切点。

4.2.1 编写切点

定义一个Performance接口:

public interface Performance{
    public void perfom();
}

下图是一个切点表达式,是编写perform()方法触发的通知。
在这里插入图片描述
返回类型的*代表不关系方法返回值的类型。类名使用了全限定名。参数列表的两个点代表无论该方法的入参是什么。

假设配置切点仅匹配concert,可以使用within()指示器来限制匹配。
在这里插入图片描述
这里使用了"&&“操作符形成"and"操作,同样可以使用”||"、"!"来进行更多的逻辑操作。

4.2.2 在切点中选择bean

切点表达式中还可以使用bean的ID来标识bean。

下面这个表达式只会匹配id为"woodstock"的bean的performance()方法。
在这里插入图片描述

同样可以匹配去除某一个指定的bean。
在这里插入图片描述

4.3 使用注解创建切面

Aspect5之前,编写AspectJ切面需要学习一种Java的拓展语言,Aspect5之后可以使用注解来把任意类转变为切面。

4.3.1 定义切面

现在有一个观众类Audience,是观看演出的切面:
在这里插入图片描述
Audience上使用了@Aspect注解,说明Audience不仅是一个POJO,还是一个切面。

Audience上有4个方法,定义了一个观众在观看演出时可能会做的事情

  1. 演出开始之前,观众需要就坐:takeSeats()
  2. 演出开始之前,观众需要手机静音:silenceCellPhones()
  3. 演出很精彩,结束之后,观众需要鼓掌:applause()
  4. 演出失败(发生异常),观众要求退票,demandRedund()

这4个方法都使用了通知注解来表明他们应该在什么时候调用,AspectJ提供了五个注解来定义通知。

注解通知
@After通知方法在目标方法返回或者抛出异常后调用
@AfterReturn通知方法会在目标方法返回后调用
@AfterThrowing通知方法会在目标方法抛出异常后调用
@Around通知方法会将目标方法封装起来
@Before通知方法会在目标方法调用之前执行

注解的执行顺序


try{
    try{
        //@Before
        method.invoke(..);
    }finally{
        //@After
    }
    //@AfterReturning
}catch(){
    //@AfterThrowing
}

这里的切点表达式重复了4遍,可以将它只定义一次,然后每次需要的时候引用他。这里可以使用@Pointcut注解在一个@AspectJ切面内定义可以重用的切点。

使用@Pointcut简化后的Audience类:
在这里插入图片描述
在这个类中,performance()方法使用了@Pointcut注解。为@Pointcut注解设置的值是一个切点表达式,这样就可以在任何的切点表达式中使用performance()。performance()方法内容不重要,实际上应该时空的,它只是一个标记,供@Pointcut注解依附。

Audience本质还是一个Java类,需要在容器中将它声明为一个bean,注意的是:仅仅将它声明为一个bean,它并不会被视为切面,它的注解不会被解析,也不会创建将其转换为切面的代理。

如果使用JavaConfig的简化,可以在配置类上的使用EnableAspectJ-AutoProxy注解启用自动代理功能。
在这里插入图片描述

如果使用XML配置bean,需要使用Spring AOP命名空间的<aop:aspectj-autoproxy>元素。
在这里插入图片描述
启用了自动代理之后,AspectJ自动代理会为使用@Aspect注解的bean创建一个代理。

Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的直到,切面依然是基于代理的。本质上他还是Spring基于代理的切面。所以尽管使用的是@AspectJ注解,还是受限于代理方法的调用。如果想使用AspectJ其他的能力,还是需要使用AspectJ并且不依赖Spring来创建基于代理的切面。

4.3.2 创建环绕通知

环绕通知是最强的通知类型,可以将被通知的目标方法完全包装起来,实际上相当于在一个通知方法中同时编写前置通知和后置通知。

使用环绕通知重写Audience方法:
在这里插入图片描述
@Around注解表明watchPerformance()是一个环绕通知,他将原本四个分开的通知集合到了一个通知中。

ProceedingJoinPoint是它的参数,这个参数的proceed()方法就是被通知方法方法。如果不调用proceed()方法,这个通知实际上会阻塞被通知方法的调用。

4.3.3 处理通知中的参数

在切点表达式中声明参数,这个参数传入到通知方法中,表达式中的args(trackNumber)限定符表示传递给playTrack()方法的int类型参数也会传递到通知中。切点中的参数和切点方法中的参数名字是一样的,这样完成了从命名切点到通知方法的参数转义。
在这里插入图片描述

在这里插入图片描述
现在,可以在Spring配置中将TrackCounter和BlankDisc定义为bean,并启用AspectJ自动代理。
在这里插入图片描述

4.3.4 通过注解引入新功能

java不是动态语言,一旦类编译完成,很难为类添加新功能。
Spring AOP有一个概念叫做"引入",他可以为SpringBean添加新方法。

Spring中,切面实现了它包装的bean相同接口的代理,如果代理暴露新的接口,切面通知的bean也想实现了新的接口,即使底层实现类没有实现也无所谓。
在这里插入图片描述

首先,有这样一个Encoreable接口。

public interface Encoreable{
    void performEncore();
}
/**
 * Encoreable接口实现类
 */
@Component
public class DefaultEncoreable implements Encoreable{
    @Override
    public void performEncore() {
        System.out.println("haha");
    }
}

正常方式,我们可以让一个类实现该接口来增强功能,但是有时候可能这个类不是你可以修改的。

首选创建一个新的切面:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EncoreableIntroducer {
    @DeclareParents(value = "demo6.Performance+",defaultImpl = DefaultEncoreable.class)
    public Encoreable encoreable;
}

Encoreable也是一个切面,但是它与一般的切面不同,它没有前置、后置或者环绕通知。
@DeclareParents注解可以将Encoreable接口引入到Performance Bean中。

@DeclareParents注解由三部分组成:

  • value属性指定了那种类型的bean要引入该接口。(后面的"+"表示Performance的所有子类型,而非Performance本身)
  • defaultImpl属性指定了为引入功能提供实现的类。上面例子中是DefaulEncoreable。
  • @DeclareParents注解所标注的静态属性指明了要引入的接口,上面例子是Encoreable接口。

接着我们需要将EncoreableIntroducer声明为一个bean后就可以使用了。

@Test
public void test1(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        Performance performer = ctx.getBean("performer", Performance.class);
        performer.perform();
        Encoreable encoreable = (Encoreable) performer;
        encoreable.performEncore();
    }

这里的Perfonrmance和Emcoreable都是接口。

注解和自动代理能很方便的创建切面。但是有时没有源码,不能对类进行修改添加注解。
就可以在XML中声明切面。

4.4 在XML中声明切面

基于注解的配置>基于Java的配置>基于XML的配置。但是,如果你需要声明切面,但是又不能为通知类添加注解的时候,那么就必须转向XML配置了。

Spring AOP在XML中的命名空间

AOP配置元素用途
<aop:advisor>定义AOP通知器
<aop:after>定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-rerturning>定义AOP返回通知
<aop:afterthrowing>定义AOP异常通知
<aop:around>定义AOP环绕通知
<aop:aspect>定义一个切面
<aop:aspectj-autoproxy>启用@Aspect注解驱动的切面
<aop:before>定义一个AOP前置通知
<aop:config>顶层的AOP配置元素。大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:declare-parents>以透明的方式为被通知的对象引入额外的接口
<aop:pointcut>定义一个切点

把之前用@Aspect声明的切面Audience类,把他的所有注解全部移除

public void Audience{
   
    public void silenceCellPhones(){
        System.out.println("Silencing cell phone");
    }

    public void takeSeats(){
        System.out.println("Taking seats");
    }

    public void demandRefund(){
        System.out.println("Demanding a refund");
    }
}

现在他就是个普通的java类,可以把他注册成为一个Spring应用上下文中的bean。我们现在可以在XML文件中将它声明成为一个切面。

4.4.1 声明前置和后置通知

在这里插入图片描述

大多数AOP配置元素必须在<aop:config>元素的上下文内使用,在<aop:config>元素内可以声明一个或者多个通知器、切面或者切点。上面例子中使用<aop:config>声明了一个简单的切面,使用ref元素引用了一个POJO bean,该bean实现了切面,也就是Audience。ref引用的bean提供了在切面中通知所调用的方法。

通知逻辑是如何织入到业务逻辑中的:
在这里插入图片描述

在这个例子中,所有的pointcut值都是相同的,在java配置中可以使用@Pointcut注解消除这种重复的值,在XML配置中则可以使用<aop:pointcut>元素。然后用pointcut-ref属性来引用这个命名切点。
在这里插入图片描述

这个<aop:pointcut>是在<aop:aspect>里面定义的,只能在当前这个<aop:aspect>里面引用,如果想在多个<aop:aspect>里面引用同一个<aop:aspect>,可以将其声明在<aop:config>元素下面,这样<aop:config>里面的<aop:aspect>都能引用它。

4.4.2 声明环绕通知

Audience前置通知是关闭手机,后置通知是鼓掌,如果我们还想知道其中花费了多长时间,就涉及到前置通知和后置通知的信息共享,虽然可以使用一个成员变量来记录时间,但是由于Audience是单例的,会存在线程安全问题。

环绕通知可以更方便的解决这个问题,使用环绕通知,可以将前置通知、后置通知集中在给一个方法内,这样就不需要成员变量来进行通信,避免了线程安全问题。

现在有个新的Audience类的watchPerformance()方法,他没有使用任何的注解。
在这里插入图片描述
这个方法中包含了之前四个通知方法的所有功能,在XML中我们使用<aop:around>元素来声明环绕通知。
在这里插入图片描述
像其他通知的XML元素一样,指定了一个切点和一个通知方法的名字。在这里,我们使用跟之前一样的切点,但是为该切点所设置的method属性值为watchPerformance()方法。

4.3.3 为通知传递参数

有这样一个类TrackCounter。
在这里插入图片描述
我们通过Spring XML配置TrackCounter bean和BlankDisc bean,并且将TrackCounter转化成为一个切面,用它来记录磁道播放次数。
在这里插入图片描述

4.4.4 通过切面引入新的功能

使用<aop:declare-parents>元素,可以实现和@DeclareParents注解一样引入新的功能。声明时和Java表达方式基本一致。

在这里插入图片描述

  • types-matching属性匹配要通知的类型
  • implement-interface属性指明增强的接口
  • default-impl属性指明增强功能具体的实现类

4.5 注入AspectJ切面

Spring AOP只能在方法执行时进行通知,如果我们想使用除了方法切点之外的切点(例如构造器切点可以在对象创建时应用通知),可以使用AspectJ。

Spring的依赖注入可以将bean装配到AspectJ切面当中去。

这里有一个CriticAspect切面,它的功能是在表演结束之后为表演发表评论。
在这里插入图片描述

程序中的performance()切点配合perform()方法。当它配合afterReturning()通知一起使用,就可以让该切点在表演结束时候起作用。

在这个类中,CriticAspect并不是自己实现了发表评论的功能,而是他持有一个CriticismEngine对象,调用该对象的getCriticism()方法来实现发表评论的功能。为了防止CriticEngine和CriticismEngine二者之间的耦合,可以用依赖注入来消除耦合。
在这里插入图片描述

CriticismEngine是一个声明了简单getCriticism()方法的接口。下面是他的实现类:
在这里插入图片描述
这个实现类会在注入的评论池中随机选择一个评论,我们需要将它声明为一个Spring bean。
在这里插入图片描述

最后我们为CriticAspect装配CriticismEngineImpl。其实AspectJ切面不需要Spring就可以织入到应用中。但是如果想使用Spirng的依赖注入为AspectJ切面注入对象,就需要在Spring配置中将切面声明为一个bean。
在这里插入图片描述

在上面代码中,使用到了factory-method属性,通常情况,Spring bean是由Spring容器初始化的,但是AspectJ切面是由AspectJ在运行期创建的,所以Spring不能负责创建CriticAspect,这里需要使用AspectJ切面提供的aspectOf() 方法来返回该切面的一个单例。

4.6 小结

AOP是面向对象编程的一个强大补充。通过AspectJ,我们现在可以把之前分散在应用各处的行为放入可重用的模块中。我们显示地声明在何处如何应用该行为。这有效减少了代码冗余,并让我们的类关注自身的主要功能。

Spring提供了一个AOP框架,让我们把切面插入到方法执行的周围。现在我们已经学会如何把通知织入前置、后置和环绕方法的调用中,以及为处理异常增加自定义的行为。

关于在Spring应用中如何使用切面,我们可以有多种选择。通过使用@AspectJ注解和简化的配置命名空间,在Spring中装配通知和切点变得非常简单。最后,当Spring AOP不能满足需求时,我们必须转向更为强大的AspectJ。对于这些场景,我们了解了如何使用Spring为AspectJ切面注入依赖。

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可 6私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值