spring--核心技术--面向切面编程

来源:互联网 发布:苹果手机 截图软件 编辑:程序博客网 时间:2024/06/10 02:07

1.定义AOP术语

  • 通知
    切面的工作被称为通知

    • 前置通知:在目标方法调用之前调用通知功能
    • 后置通知:在目标方法调用之后调用通知功能,此时不关心方法的输出
    • 返回通知:在目标方法成功执行之后调用通知
    • 异常通知:在目标方法抛出异常之后调用通知
    • 环绕通知:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义行为
  • 连接点
    在应用执行过程中能够插入切面的一个点。通俗的说就是应用通知的时机

  • 切点
    切点的定义会匹配所要织入的一个或者多个连接点。通俗的说,如果通知定义了切面的“什么”和“何时”,则切点定义了“何处”。

  • 切面
    切面是通知和切点的结合
  • 引入
    引入允许我们向现有类添加新方法或属性。
  • 织入
    织入是把切面应用到目标对象并创建新的代理对象的过程。
    在目标对象的生命周期里有多个点可以织入:
    • 编译期
    • 类加载期
    • 运行期

2.Spring对AOP的支持

  • spring通知是用Java编写的
  • Spring在运行时通知对象
  • spring只支持方法级别的连接点

3.通过切点选择连接点

在springAOP中,要使用AspectJ的切点表达式语言来定义切点。
下表列出了SpringAOP所支持的AspectJ切点指示器

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

观察上表,可以发现只有execution()指示器是实际执行匹配的,而其他指示器都是用来限制匹配的,所以execution()是我们在编写切点定义时最主要使用的指示器。在此基础上使用其他指示器限制所匹配的切点。

(1) 编写切点

基本格式

//在方法执行时触发(返回任意类型 方法所属的类.方法(使用任意参数))execution(* concert.Performance.perform(..))

限制匹配

//当concert包下任意类的方法被调用时执行Performance.perform()execution(* concert.Performance.perform(..))     && within(concert.*)

(2)在切点中选择bean

execution(* concert.Performance.perform())    and bean(beanID)    //或者and !bean(beanID) 表示不匹配该bean

4.使用注解创建切面

(1) 定义切面

  1. 一般情况
//该接口表示任何类型的现场表演public interface Performance {    public void perform();}
import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspectpublic class Audience {    @Before("execution(* ch04.Performance.perform(..))")    public void silenceCellPhones(){        System.out.println("silencing cell phones");    }    @Before("execution(* ch04.Performance.perform(..))")    public void takeSeats(){        System.out.println("taking seats");    }    @AfterReturning("execution(* ch04.Performance.perform(..))")    public void applause(){        System.out.println("CLAP!");    }    @AfterThrowing("execution(* ch04.Performance.perform(..))")    public void demandRefund(){        System.out.println("Demanding a refund!");    }}

上例中,Audience类使用@AspectJ注解进行标注。该注解表明Audience不仅是POJO,还是一个切面。
Audience有四个方法,定义了一个观众可能在观看演出时可能做出的行为。在演出前观众就坐takeSeats(),手机静音silenceCellPhones()。如果演出精彩则要鼓掌applause()。演出没有达到观众预期则会要求退款demandRefund()

AspectJ提供了五个注解来定义通知。@After @AfterReturning @AfterThrowing @Around @Before

  1. 通过@Pointcut注解声明频繁使用的切点表达式
@Aspectpublic class Audience {    //定义命名的切点    @Pointcut("execution(* ch04.Performance.perform(..))")    public void performance(){}    @Before("performance()")    public void silenceCellPhones(){        System.out.println("silencing cell phones");    }    @Before("performance()")    public void takeSeats(){        System.out.println("taking seats");    }    @AfterReturning("performance()")    public void applause(){        System.out.println("CLAP!");    }    @AfterThrowing("performance()")    public void demandRefund(){        System.out.println("Demanding a refund!");    }}

(2) 启用自动代理

javaConfig法

@Configuration//启动AspectJ自动代理@EnableAspectJAutoProxy@ComponentScanpublic class ConcertConfig {    //声明Audience bean    @Bean    public Audience audience(){        return new Audience();    }}

XML法

<aop:aspectj-autoproxy /><bean class="concert.Audience" />

(3) 与众不同的通知–环绕通知

    //环绕通知方法    @Around("performance()")    public void watchPerformance(ProceedingJoinPoint joinPoint){        try {            System.out.println("silencing cell phones");            System.out.println("taking seats");            //一定要调用该方法            joinPoint.proceed();            System.out.println("CLAP!");        } catch (Throwable throwable) {            System.out.println("Demanding a refund!");        }    }
  • 可以看到这个通知所达到的效果与之前配置的前置通知和后置同志是一样的。但是,位于同一方法中。
  • 这个新的通知方法接收ProceedingJoinPoint作为参数
  • 通知方法中可以做任何事情,当要将控制权交给被通知方法时,需要调用ProceedingJoinPointproceed()方法
  • 不调用proceed()方法则会阻塞对被通知方法的调用。所以按需进行不调用或者多次调用

(4) 处理通知中的参数

表明传入被通知方法中的参数也会传递到通知中去。而且需要注意的是指定参数的名称要与切点方法签名中的参数相匹配。

execution(* 方法所属类型.方法(接收参数类型)) && args(指定参数)

(5) 通过注解引入新功能

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

可以看出,EncoreableIntroducer是一个切面。但是没有提供前置,后置,环绕通知,而是通过@DeclareParents注解,将Encoreable接口引入到Performance bean中。

@DeclareParents注解由三部分构成:
- value属性指定了哪种类型的bean要引入该接口
- defaultImpl属性指定了为引入功能提供实现的类
- @DeclareParents 注解所标注的静态属性指明了要引入的接口。

缺陷:必须为通知类添加注解,所以必须要有源码
解决方案:XML配置

5.在XML中声明切面

spring的AOP配置元素能够以非侵入性的方式声明切面

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

(1)以之前环境为例:

<aop:config>    <aop:aspect ref="audience"> <!-- 引用的bean --->        <aop:before pointcut="execution(* ch04.Performance.perform(..))" method="silenceCellPhones" />        <aop:before pointcut="execution(* ch04.Performance.perform(..))" method="takeSeats" />        <aop:after-returning pointcut="execution(* ch04.Performance.perform(..))" method="applause" />        <aop:after-throwing pointcut="execution(* ch04.Performance.perform(..))"method="demandRefund" />    </aop:aspect></aop:config>

(2)消除重复元素:

<aop:config>    <aop:aspect ref="audience"> <!-- 引用的bean --->        <aop:pointcut id="performance" expression="execution(* ch04.Performance.perform(..))" />        <aop:before pointcut-ref="performance" method="silenceCellPhones" />        <aop:before pointcut="performance" method="takeSeats" />        <aop:after-returning pointcut="performance" method="applause" />        <aop:after-throwing pointcut="performance" method="demandRefund" />    </aop:aspect></aop:config>

(3)环绕通知

<aop:config>    <aop:aspect ref="audience"> <!-- 引用的bean --->        <aop:pointcut id="performance" expression="execution(* ch04.Performance.perform(..))" />        <aop:around pointcut-ref="performance" method="watchPerformance" />    </aop:aspect></aop:config>

(4)通知传递参数

<aop:config>    <aop:aspect ref="audience"> <!-- 引用的bean --->        <aop:pointcut id="performance" expression="execution(* ch04.Performance.perform(int)) and args(num)" />        <aop:before pointcut-ref="performance" method="watchPerformance" />    </aop:aspect></aop:config>

(5)通过切面引入新功能

1.default-impl用全限定类名显示指定

<aop:aspect>    <aop:declare-parents types-matching="ch04.Performance+" implement-interface="ch04.Encoreable" default-impl="ch04.DefaultEncoreable"/></aop:aspect>

2.delegate-ref属性引用了一个spring bean作为引入的委托

<aop:aspect>    <aop:declare-parents types-matching="ch04.Performance+" implement-interface="ch04.Encoreable" delegate-ref="encoreableDelegate"/></aop:aspect>

6.注入AspectJ切面

相关背景代码

public aspect CriticAspect {    public CriticAspect() {}    pointcut performance() : execution(* perform(..));    afterReturning() : performance(){        System.out.println(criticsmEngine.getCriticsm());    }    private CriticsmEngine criticsmEngine;    public void setCriticsmEngine(CriticsmEngine criticsmEngine){        this.criticsmEngine = criticsmEngine;    }}

要注入AspectJ的话:

<bean class="xxxx.CriticAspect" factory-method="aspectOf">    <!--- 其他属性配置 ---></bean>

尤其要注意的是 factory-method="aspectOf"
因为spring不能负责创建Aspect,所以需要aspectOf工厂方法获得切面引用,然后像bean一样依赖注入