Spring实战之AOP

一、AOP介绍

  • 散布于应用中多处的功能被称为横切关注点,这些横切关注点是与业务逻辑分离的。
  • 常见的横向关注点有日志,事务,安全等
  • 横向关注点可以被模块化为特殊的类,成为切面
  • 创建切点来定义切面所织入的连接点是AOP框架的基本功能

通知:

  切面的工作被称为通知。描述切面要完成的工作以及何时执行这个工作。

Spring切面可以应用5种类型的通知:

  • 前置通知(Before) : 在目标方法被调用之前调用通知功能;
  • 后置通知(After) : 在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(After-returning) : 在目标方法成功执行之后调用通知;
  • 异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
  • 环绕通知(Around) : 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

连接点

  连接点是在应用执行过程中能够插入切面的一个点。 这个点可以是调用方法时、 抛出异常时、 甚至修改一个字段时。 切面代码可以利用这些点插入到应用的正常流程之中, 并添加新的行为。

切点

  一个切面并不需要通知应用的所有连接点。 切点有助于缩小切面所通知的连接点的范围。 如果说通知定义了切面的“什么”和“何时”的话, 那么切点就定义了“何处”。 切点的定义会匹配通知所要织入的一个或多个连接点。

连接点指所有可插入切面的点,切点指部分连接点。

切面

  通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能。

引入

  引入允许我们向现有的类添加新方法或属性。例如, 我们可以创建一个Auditable通知类, 该类记录了对象最后一次修改时的状态。 这很简单, 只需一个方法, setLastModified(Date), 和一个实例变量来保存这个状态。 然后, 这个新方法和实例变量就可以被引入到现有的类中, 从而可以在无需修改这些现有的类的情况下, 让它们具有新的行为和状态。

织入:

  织入是把切面应用到目标对象并创建新的代理对象的过程。 切面在指定的连接点被织入到目标对象中。

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

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

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

二、编写切点

execution()指示器 是我们在编写切点定义时最主要使用的指示器。 在此基础上, 我们使用其他指示器来限制所匹配的切点。

我们使用execution()指示器选择Performance的perform()方法。 方法表达式以“*”号开始, 表明了我们不关心方法返回值的类型。 然后, 我们指定了全限定类名和方法名。 对于方法参数列表, 我们使用两个点号(..) 表明切点要选择任意的perform()方法, 无论该方法的入参是什么。

假设我们需要配置的切点仅匹配concert包。 在此场景下, 可以使用within()指示器来限制匹配. 类似地, 我们可以使用“||”操作符来标识或(or) 关系, 而使用“!”操作符来标识非(not) 操作

Spring还引入了一个新的bean()指示器, 它允许我们在切点表达式中使用bean的ID来标识bean。 bean()使用bean ID或bean名称作为参数来限制切点只匹配特定的bean。在这里, 我们希望在执行Performance的perform()方法时应用通知, 但限定bean的ID为woodstock。 同样可以使用or、非等操作

三、使用注解创建切面:

1. @AspectJ注解

  Audience类使用@AspectJ注解进行了标注。 该注解表明Audience不仅仅是一个POJO,还是一个切面。Audience类中的方法都使用注解来定义切面的具体行为。

package com.springinaction.springidol;
@Aspect
public class Audience {
    // 表演之前
    @Before(“execution(** concert.Performance.perform(..))”)
    public void takeSeats() {
        System.out.println("The audience is taking their seats.");
    }
    // 表演之前
@Before(“execution(** concert.Performance.perform(..))”)
    public void turnOffCellPhones() {
        System.out.println("The audience is turning off their cellphones");
    }
    // 表演之后
    @AfterReturning(“execution(** concert.Performance.perform(..))”)
    public void applaud() {
        System.out.println("CLAP CLAP CLAP CLAP CLAP");
    }
    // 表演失败之后
    @AfterThrowing(“execution(** concert.Performance.perform(..))”)
    public void demandRefund() {
        System.out.println("Boo! We want our money back!");
    }
}

2.@Pointcut注解声明频繁使用的切点表达式:

package com.springinaction.springidol;
@Aspect
public class Audience {

    @Pointcut(“execution(** concert.Performance.perform(..))”)
    Public void performance(){ }

    // 表演之前
    @Before(“performance()”)
    public void takeSeats() {
        System.out.println("The audience is taking their seats.");
    }
    …….
}

  Audience是一个Java类, 只不过它通过注解表明会作为切面使用而已。像其他的Java类一样, 它也可以装配为Spring中的bean:

3.在JavaConfig中启用AspectJ注解的自动代理

Package concert;

@Configuration
@EnableAspectAutoProxy   //该注解表明启用自动代理
@ComponentScan
Public class ConcertConfig{

    //声明Audience bean
    @bean
    Public Audience audience(){
       Return new Audience();
    }
}

  AspectJ自动代理会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。

4.在XML中, 通过Spring的aop命名空间启用AspectJ自动代理

<?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:aop="http://www.springframework.org/schema/aop"
    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
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">   

    <aop:aspectj-autoproxy />
    <bean ... />
</beans>

5.创建环绕通知:

@Aspect
public class AspectJAroundAudience {
    // 定义切点
    @Pointcut("execution(* com.springinaction.springidol.Performer.perform(..))")
    public void performance() {  }

    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint joinpoint) {
        try {
            // 表演之前
            System.out.println("The audience is taking their 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 performance took " + (end - start) + " milliseconds.");
        } catch (Throwable t) {
            // 表演失败之后
            System.out.println("Boo! We want our money back!");
        }
    }
}

@Around注解表明watchPerformance()方法会作为performance()切点的环绕通知。
它接受ProceedingJoinPoint作为参数。 这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法。

6.处理通知中的参数

package com.springinaction.springidol;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AspectJMagician implements MindReader {
    private String thoughts;

    @Pointcut("execution(* com.springinaction.springidol.Thinker.thinkOfSomething(String)) && args(thoughts))")
    public void thinking(String thoughts){

    }

    @Override
    @Before("thinking(thoughts)")
    public void interceptThoughts(String thoughts) {
        System.out.println("Intercepting volunteer's thoughts");
        this.thoughts = thoughts;
    }

    @Override
    public String getThoughts() {
        return thoughts;
    }

}

通知中的参数

  切点表达式中的args(trackNumber)限定符表明传递给playTrack()方法的int类型参数也会传递到通知中去。 切点定义中参数的名称trackNumber也与切点方法中的参数相匹配。这样就完成了从命名切点到通知方法的参数转移。

四、在XML中声明切面:

1. 声明前置和后置通知:

<bean id="eddie" class="com.springinaction.springidol.Instrumentalist">
        <property name="instrument">
            <bean class="com.springinaction.springidol.Guitar" />
        </property>
        <property name="song" value="my love" />
    </bean>

    <bean id="audience" class="com.springinaction.springidol.Audience" />

    <aop:config>
        <aop:aspect ref="audience"><!-- 引用audience Bean -->
            <!-- 声明切入点 -->
            <aop:pointcut id="performance"
                expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
            <!-- 表演之前 -->
            <aop:before pointcut-ref="performance" method="takeSeats" />
            <aop:before pointcut-ref="performance" method="turnOffCellPhones" />
            <!-- 表演之后 -->
            <aop:after-returning pointcut-ref="performance" method="applaud" />
            <!-- 表演失败之后 -->
            <aop:after-throwing pointcut-ref="performance" method="demandRefund" />
        </aop:aspect>
    </aop:config>
</beans>

2. 声明环绕通知:

<bean id="audience" class="com.springinaction.springidol.AroundAudience" />

<aop:config>
    <aop:aspect ref="audience"><!-- 引用audience Bean -->
        <!-- 声明切入点 -->
        <aop:pointcut id="performance"
            expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
        <aop:around method="watchPerformance" pointcut-ref="performance" />
    </aop:aspect>
</aop:config>

3. 为通知传递参数:

<bean id="volunteer" class="com.springinaction.springidol.Volunteer" />
<bean id="magician" class="com.springinaction.springidol.Magician" />

<aop:config>
    <aop:aspect ref="magician"><!-- 引用magician Bean -->
        <!-- 声明切入点 -->
        <aop:pointcut id="thinking"
            expression="execution(* com.springinaction.springidol.Thinker.thinkOfSomething(String)) and args(thoughts) " />
        <aop:before method="interceptThoughts" pointcut-ref="thinking"
            arg-names="thoughts" />
    </aop:aspect>
</aop:config>

XML业务逻辑

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值