码迷,mamicode.com
首页 > 编程语言 > 详细

《Spring实战 第三版》四

时间:2020-03-09 13:31:09      阅读:78      评论:0      收藏:0      [点我收藏+]

标签:png   限制   die   增加   value   tar   标识   matching   方法调用   

《第四章 面向切面的Spring》

在软件开发中,分布于应用中多处的功能被称为横切关注点(cross-cutting concerns)
如安全和日志等功能即为横切关注点
通常,这些横切关注点从概念上是与应用的业务逻辑相分离的
但具体实现上往往是直接嵌入到应用的业务逻辑之中
将这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所解决的

什么是面向切面编程

切面可以帮助我们模块化横切关注点
横切关注点可以被模块化为特殊的类,这些类被称为切面
这样做有两个好处:首先,每个关注点现在都中集中于一处,而不是分散到多处代码中
其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码
次要关注点的代码就被转移到切面中了

1.定义AOP术语

通知(Advice)
通知定义了切面是什么以及何时使用
Spring切面可以应用5种类型的通知

  1. Before 在方法调用之前调用通知
  2. After 在方法完成之后调用通知,不论方法执行是否成功
  3. After-returnning 在方法成功执行之后调用通知
  4. After-throwing 在方法抛出异常之后调用通知
  5. Around 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

连接点(Joinpoint)
我们的应用可能需要对数以千计的时机应用通知,这些时间被称为连接点
连接点是在应用执行过程中能够插入切面地一个点

切点(PointCut)
如果通知定义了切面的“什么”和“何时”,那么切点就定义了何处
切点的定义会匹配通知所要织入的一个或多个连接点
我们通常使用明确的类和方法名来指定这些切点
或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点

切面(Aspect)
切面是通知和切点的结合
通知和切点共同定义了关于切面的共同内容

引入
引入允许我们向现有的类添加新方法或属性
可以在无需修改现有的类的情况下,让他们具有新的行为和状态

织入
织入是将切面应用到目标对象来创建新的代理对象的过程
切面在指定的连接点被织入目标对象中
在目标对象的声明周期里有多个点可以进行织入

2.Spring对AOP的支持

并不是所有的AOP框架都是一样的,它们多多少少都是有一些分别
我们更关注于Spring AOP
Spring AOPt提供了4种各具特色的AOP支持:

  1. 基于代理的经典AOP
  2. @AspectJ注解驱动的切面
  3. 纯POJO切面
  4. 注入式AspectJ切面(适合Spring个版本)

Spring通知是Java编写的
Spring所创建的通知都是用标准的Java类编写的
定义通知所应用的切点通常在Spring配置文件里采用XML来编写的

Spring在运行期间通知对象
通过在代理类中包裹切面,Spring在运行期间将切面织入到Spring管理的Bean中

Spring只支持方法连接点
Spring基于动态代理
只支持方法连接点

使用切点连接选择点

在Spring AOP中,需要使用AspectJ的切点表达式语言来定义切点
关于Spring AOP的AspectJ切点,最重要的一点是Spring仅支持AspectJ切点指示器的一个子集
下面列出了Spring AOP所支持的AspectJ切点指示器
技术图片

当Spring尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常

1编写切点

execution(*com.Instrument.play(..))
我们使用execution()指示器选择Instrument的play方法
方法表达式以*号开始,标识了我们不关心方法返回值的类型
接着我们指定了全限定类名和方法名
对于方法的参数列表,我们使用(..)标识切点选择任意的play方法
即无论该方法的入参是什么

现在假设我们需要配置切点仅匹配com包,在此场景下,可以使用within指示器来限制匹配
execution(*com.Instrument.play(..))&&within(com.*)
使用了&&操作符把execution把with指示器连接在一起
类似的可以使用||或者!操作符来表达或、非的关系
也可以使用and,or和not来替换上述操作符

2.使用Spring的bean()指示器

Spring 2.5还引入了一个新的bean指示器
该指示器允许我们在切点表达式中使用Bean的ID来表示Bean
execution(*com.Instrument.play(..))and bean(eddile)
在这里,我们希望在执行Instrument的play方法时应用通知,但限定Bean的ID为eddile
还可以使用非操作符指定除了指定ID的Bean意外的其他Bean应用通知
execution(*com.Instrument.play(..))and !bean(eddile)

在XML中声明切面

在Spring的AOP配置命名空间中,提供了声明式切面的选择
Spring的AOP配置元素简化了基于POJO切面的声明

1.声明前置和后置通知

假设我们用拥有一个观众类

public class Audience {
    //表演之前
    public void takeSeats(){
        System.out.println("the audience is taking theirs seats.");
    }
    //表演之前
    public void turnOffCellPhones(){
        System.out.println("the audience is turning off their cellphones");
    }
    //表演之后
    public void applaud(){
        System.out.println("CLAP CLAP CLAP CLAP CLAP");
    }
}

并将其在XML文件中进行声明
<bean id="audience" class="com.Audience"/>

现在,Audience其实已经具备了成为一个切面的所有条件
现在它只需要一点Spring的AOP魔法
其关于AOP的配置如下:

<aop:config>
    <!--声明一个简单的切面-->
    <aop:aspect ref="audience">
        <!--定义了匹配切点的方法执行之前调用前置通知方法-->
        <aop:before pointcut="execution(* com.Performer.perform(..))" method="takeSeats"/>
        <aop:before pointcut="execution(* com.Performer.perform(..))" method="turnOffCellPhones"/>
        <!--定义了匹配切点的方法执行之后调用后置通知方法-->
        <aop:after pointcut="execution(* com.Performer.perform(..))" method="applaud"/>
    </aop:aspect>
</aop:config>

显然,我们各个通知方法调用的切点是一样的
可以将切点独立出来声明,然后再引用切点
<aop:pointcut id="performer" expression="execution(* com.Performer.perform(..))"/>
然后再在通知里使用pointcut-ref来引用定义好的切点

2.声明环绕通知

假如我们需要观众报告每个参赛者的表演时间,那么可以使用环绕通知
先定义一个方法:

public void watchPerformance(ProceedingJoinPoint joinPoint){
    try {
        System.out.println("the audience is taking theirs seats.");
        System.out.println("the audience is turning off their cellphones");
        long start = System.currentTimeMillis();
        joinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println("CLAP CLAP CLAP CLAP CLAP");
        System.out.println("the performer took"+(end-start)+"milliseconds");
    }
    catch (Throwable t){
        System.out.println("we want our money back ");
    }
}

然后就是使用<aop:around>元素来定义环绕通知即可
<aop:around pointcut-ref="performer" method="watchPerformance"/>

3.为通知传递参数

除了环绕通知,我们的通知都没有用到参数
为了给通知传递参数其实很简单
这里是通过配置实现将被通知的方法的的参数传递给通知

<aop:config>
<aop:aspect ref="magician">
    <!--thoughts为参数的名字-->
    <!--切点定义了一个带参数的方法 参数名为thoughts,类型为Sting-->
    <aop:pointcut id="thinking" expression="execution(* com.Thinker.thinkOfSomething(String)) and args(thoughts)"/>
    <!--前置通知通过arg-names将参数进行传递-->
    <aop:before pointcut-ref="thinking" method="interceptThoughts" arg-names="thoughts"/>
</aop:aspect>
</aop:config>
4.通过切面引入新功能

一些编程语言,例如Ruby和Groovy,有开放类的理念,可以不用直接修改类或对象
就能够为类或对象增加新的方法

实际上,Java可以利用被称为引入的AOP概念,切面可以为Spring Bean添加新方法
举个例子,现在拥有某一接口的多个实现类,在此基础上我们需要在接口中再增加一个方法
一个方法是一一修改实现类,另一个方法是通过AOP来实现,不需要修改现有的实现

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

<aop:declare-parents>声明了此切面所通知的所有Bean在它的对象层次结构中拥有新的父类型
类型匹配Performer接口的那些Bean将会实现Contestant接口
那么Contestant接口中方法的实现来自于何处
default-impl属性通过给定的全限定类名来显示指定Constestant接口的实现
或者使用delegate-ref属性给出引用的Bean的ID

在mian函数中测试

Performer performer = (Performer) ctx.getBean("duke");
performer.perform();
Contestant contestant = (Contestant)performer;
contestant.receiveAward();

发现,确实Performer接口既实现了自身的方法也实现了Contestant接口的方法

注解切面

使用注解来创建切面是AspectJ 5 所引入的关键特性

@Aspect
public class Audience {
    //定义切点 匹配Performer的perform方法
    @Pointcut(
            "execution(* com.Performer.perform(..))"
    )
    public void performance(){}
    //表演之前  前置通知
    @Before("performance()")
    public void takeSeats(){
        System.out.println("the audience is taking theirs seats.");
    }
    //表演之前  前置通知
    @Before("performance()")
    public void turnOffCellPhones(){
        System.out.println("the audience is turning off their cellphones");
    }
    //表演之后 后置通知
    @After("performance()")
    public void applaud(){
        System.out.println("CLAP CLAP CLAP CLAP CLAP");
    }
    //环绕通知
    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint joinPoint)
    {
        ...
    }
}

如上,就是使用注解实现的一个切面
切点的名称来自于注解所应用的方法名称,所以该切点的名称为performance
performance方法的实际内容并不重要
该方法只是一个标识,供@Pointcut注解依附
同时该类仍然是一个POJO类

最后让Spring将Audience应用为一个切面即可
为此,我们需要在Spring上下文中声明一个自动代理Bean
该Bean知道如何把@AspectJ注解所标注的Bean转化为代理通知

Spring在aop命名空间中提供了一个自定义的配置元素<aop:aspectj-autoproxy>
使用该元素后将在Spring上下文中创建一个AnnotationAwareProxyCreator类,它会自动代理一些Bean
这些Bean的方法需要与使用@AspectJ注解的Bean中所定义的切点相匹配
而这些切点又是使用@Pointcut注解定义出来的
总之,最后在配置文件XML中加上
<aop:aspectj-autoproxy/>
那么基于注解的AOP定义完成
传递参数给所标注的通知

@Aspect
public class Magician implements MindReader {
    private String thoughts;
    //声明参数化的切点
    @Pointcut("execution(* com.Thinker.thinkOfSomething(String)) && args(thoughts)")
    public void thinking(String thoughts){}
    @Before("thinking(thoughts)")
    //把参数传递给通知
    public void interceptThoughts(String thoughts) {
        System.out.println("Intercepting volunteer's thoughts: "+thoughts);
        this.thoughts = thoughts;
    }
    public String getThoughts() {
        return thoughts;
    }
}

标注引入
使用注解来为已有的Bean引入接口

@Aspect
public class ContestantIntroducer {
    @DeclareParents(value = "com.Performer+", defaultImpl = GraciousContestant.class)
    public static Contestant contestant;
}

上面这段代码为Performer Bean引入了Contestant接口
value等同于type-matching属性
defaultImpl等同于default-impl
static属性则指定了将被引入的接口

《Spring实战 第三版》四

标签:png   限制   die   增加   value   tar   标识   matching   方法调用   

原文地址:https://www.cnblogs.com/ASE265/p/12448157.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!