一、aop概述

aop即面向切面编程,简单说就是把程序中重复的代码抽取出来,在需要执行时,使用动态代理的技术在不改变源码的基础上动态的增强原有的功能。

作用和优势: 在程序运行期间,不修改源码对已有方法进行增强

二、aop的相关术语

2.1 Joinpoint(连接点)

在aop中点指的就是方法,Joinpoint就是指业务层的各个方法

2.2 Pointcut(切入点)

指的是要对那些业务层方法进行拦截

2.3 Advice(通知)

所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知

2.4 Aspect(切面)

是切入点和通知(引介)的结合

三、基于xml配置的aop的使用步骤

3.1 抽取公共的代码制作成通知类

3.2 把通知类配置到spring容器中

3.3 使用aop:config 来声明aop的配置

3.4 使用aop:aspect来声明切面的配置

3.5 在切面内部使用 aop:pointcut来声明切入点

切入点就是指定哪些service方法会被aop拦截到

切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.lyy.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.lyy.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.lyy.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用
来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)

3.6 在切面的内部配置通知类型

有前置通知(aop:before)、后置通知(aop:after-returning),异常通知(after-throwing),最终通知(aop:after),环绕通知(aop:around)

配置完成后的结果如下

<!--通知类使用注解配置-->
    <!--使用aop:config 来声明aop的配置-->
    <aop:config>
        <aop:aspect id="transactionAspect" ref="transactionManager">
            <!--这个切入点表达式表示com.lyy.service及其子包中的任意类的任意方法-->
            <aop:pointcut id="servicePointcut" expression="execution(* com.lyy.service..*.*(..))"/>
            <!--在切面的内部配置通知的类型-->
            <!--配置前置通知-->
            <aop:before method="startTransaction" pointcut-ref="servicePointcut"/>
            <!--后置通知-->
            <aop:after-returning method="commit" pointcut-ref="servicePointcut"/>
            <!--配置异常通知-->
            <aop:after-throwing method="rollBack" pointcut-ref="servicePointcut"/>
        </aop:aspect>
    </aop:config>

四、环绕通知详解

它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式

通过spring提供的ProceedingJoinPoint这个类型的参数来控制增强代码的执行

/**
     * spring aop的环绕通知,手动控制增强代码的执行时机
     * @param proceedingJoinPoint
     * @return
     */
    public Object around(ProceedingJoinPoint proceedingJoinPoint){
        Object returnValue=null;
        try {
            //开启事务
            startTransaction();//前置通知
            Object[] args = proceedingJoinPoint.getArgs();
            returnValue = proceedingJoinPoint.proceed(args);
            //提交事务
            commit();//后置通知
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //回滚事务
            rollBack();//异常通知
        } finally {
            //最终通知
        }
        return returnValue;
    }

把上面这个方法配置在aop的切面中,当做环绕通知的内容。

六、多个切面执行顺序

切面的配置顺序决定了执行顺序,具体的执行顺序和方法嵌套调用时的执行顺序类似

public static void main(String[] args) {
        m1();
    }

    public static void m1(){
        try {
            System.out.println("m1-前置");
            m2();
            System.out.println("m1-后置");
        } catch (Exception e) {
            System.out.println("m1-异常");
        } finally {
            System.out.println("m1-最终");
        }
    }

    public static void m2(){
        try {
            System.out.println("m2-前置");
            m3();
            System.out.println("m2-后置");
        } catch (Exception e) {
            System.out.println("m2-异常");
        } finally {
            System.out.println("m2-最终");
        }
    }

    public static void m3(){
        System.out.println("业务方法");

    }

这几个方法嵌套执行时,顺序如下

m1-前置
m2-前置
业务方法
m2-后置
m2-最终
m1-后置
m1-最终

类似的,多个切面的执行顺序也是这样的

切面1前置
切面2前置
业务代码
且面2后置
切面2最终
切面1后置
切面1最终

七、总结

通过spring的aop可以在程序运行期间,对已有方法进行增强,其内部是通过动态代理实现的。

使用时的关键点是先抽取出公共代码制作成对应的通知,通知类型有:前置通知,后置通知,异常通知,最终通知。还有一个环绕通知相当于是前几个类型的综合,可以在代码中手动控制增强代码什么时候执行 。

然后在spring的配置文件中通过aop:config标签来进行aop的配置,配置的过程包括配置切面,配置切入点,配置通知类型等。

注意上面的配置虽然使用的是注解+xml的配置,通知类的bean也是用注解配置的,但aop相关的配置是在spring的配置文件中配置的。

示例工程地址
示例工程地址