Spring框架aop


Spring4和Spring5下aop的区别(Springboot1+升级到2+),可查阅 此博客

一、介绍

Spring 框架的一个关键组件是面向方面的编程(AOP)框架。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

二、为什么使用aop

aop可以将核心业务代码和其他日志类代码解耦,使得核心代码保持纯净,可以将通用方法抽离,方便维护。
开发中在多个模块间有重复的代码,我们通常是怎么处理的?

  1. 复制粘贴(代码冗余,维护困难)
  2. 抽象成一个方法,然后在需要的地方分别调用这个方法(需求多变时,维护困难)
  3. jdk动态代理(可以实现,但是写起来难)
  4. AOP(实现简单,方便维护)

2.1 jdk动态代理

// 核心代码(无入侵)
@Component
public class MyMathCalculator implements Calculator {
    @Override
    public int add(int i,int j){
        int result = i + j;
        return result;
    }
}
// 核心方法的代理类
public class CalcultorProxy {
    /**
     * 为传入的对象创建一个代理对象
     * @param calculator 被代理的对象 要创建代理对象的值
     * @return 代理对象
     */
    public static Calculator getProxy(Calculator calculator){

        // ClassLoader 类加载器
        ClassLoader loader = calculator.getClass().getClassLoader();
        // 实现的所有接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        // 方法执行器
        InvocationHandler h = new InvocationHandler() {
            /**
             *
             * @param proxy 代理对象,给jdk使用的
             * @param method 目标对象的方法
             * @param args 执行目标方法所需参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 利用反射执行目标方法 invoke即为目标方法执行后返回的值
                System.out.println("jdk动态代理~~~");
                Object invoke = null;
                try {
                    System.out.println("LogUtil-->start-->方法执行前");
                    invoke = method.invoke(calculator, args);
                    System.out.println("LogUtil-->return-->方法执行完成");
                }catch (Exception e){
                    System.out.println("LogUtil-->error-->方法执行失败");
                }finally {
                    System.out.println("LogUtil-->end-->方法执行结束");
                }
                return invoke;
            }
        };
        Object o = Proxy.newProxyInstance(loader, interfaces, h);
        return (Calculator) o;
    }
}
// 测试类
@SpringBootTest
public class test {

    /**
     * 使用jdk动态代理可以和业务代码解耦
     * jdk默认的动态代理,如果对象没有实现任何接口,jdk则不能实现代理
     */
    @Test
    public void test(){
        Calculator calculator = new MyMathCalculator();
        System.out.println(calculator.add(1, 2));

        System.out.println("~~~~~~~~jdk动态代理~~~~~~~~~");
        Calculator proxy = CalcultorProxy.getProxy(calculator);
        int add1 = proxy.add(1,2);
        System.out.println(add1);
    }
}
// 3
// ~~~~~~~~jdk动态代理~~~~~~~~~
// jdk动态代理~~~
// LogUtil-->start-->方法执行前
// LogUtil-->return-->方法执行完成
// LogUtil-->end-->方法执行结束
// 3

三、AOP 核心概念

概念描述
切面(aspect)类是对物体特征的抽象,切面就是对横切关注点的抽象
连接点(joinpoint)被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
切入点(pointcut)这是一组一个或多个连接点,通知应该被执行
通知(advice)所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
横切关注点对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

在这里插入图片描述

四、AOP通知的类型

    @Before 方法执行前运行                  前置通知
    @After 方法执行结束运行                  后置通知
    @AfterReturning 方法正常返回时运行        异常通知
    @AfterThrowing 目标方法抛出异常后运行      返回通知
    @Around 环绕                           环绕通知
        spring最强大的通知,合并了以上四种通知。利用反射执行方法,类似于method.invoke(obj,args);
        环绕通知执行顺序要优先其他通知
        
    try {
        @Before
        invoke = method.invoke(calculator, args);
        @AfterReturning
    }catch (Exception e){
        @AfterThrowing
    }finally {
        @After
    }

五、AOP 运用场景

  1. Authentication 权限
  2. Caching 缓存
  3. Context passing 内容传递
  4. Error handling 错误处理
  5. Lazy loading 懒加载
  6. Debugging 调试
  7. logging, tracing, profiling and monitoring 记录 跟踪 优化 校准
  8. Performance optimization 性能优化
  9. Persistence 持久化
  10. Resource pooling 资源池
  11. Synchronization 同步
  12. Transactions 事务

六、AOP实现

5.1 xml方式

  1. 将目标类和切面类通过bean注入到ioc容器中
  2. 使用aop:config配置aop信息
  3. 使用<aop:aspect ref=“logUtil03”>标示那个类为切面类
  4. aop:beforeaop:afteraop:after-throwingaop:after-returningaop:around标示什么时候执行
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
	<!-- 目标类 -->
    <bean id="myMathCalculator04" class="com.guocoffee.spring.aop.aop03.impl.MyMathCalculator04"></bean>
    <!-- 切入类 -->
    <bean id="logUtil03" class="com.guocoffee.spring.aop.aop03.util.LogUtil03"></bean>

    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.guocoffee.spring.aop.aop03..*(..))"/>
        <!-- 相当于@Aspect -->
        <aop:aspect ref="logUtil03" order="1">
            <aop:before method="logStart" pointcut="execution(* com.guocoffee.spring.aop.aop03..*(..))"></aop:before>
            <aop:after method="logEnd" pointcut-ref="myPointcut"></aop:after>
            <aop:after-throwing method="logError" pointcut-ref="myPointcut" throwing="exception"></aop:after-throwing>
            <aop:after-returning method="logReturn" pointcut-ref="myPointcut" returning="result"></aop:after-returning>
            <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
        </aop:aspect>

    </aop:config>
</beans>
public class LogUtil03 {

    public void logStart(JoinPoint joinPoint){
        // 获取目标发放运行是传输的参数
        Object[] args = joinPoint.getArgs();

        // 获取方法签名
        // 方法签名:int com.guocoffee.spring.aop.aop02.impl.MyMathCalculator03.add(int,int)
        Signature signature = joinPoint.getSignature();
        System.out.println("方法签名:"+signature);
        // 获取方法名
        String name = signature.getName();
        System.out.println(name + "方法执行开始-->LogUtil-->start-->方法执行前,参数列表:" + Arrays.toString(args));
    }

    public void logReturn(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行完成返回-->LogUtil-->return-->方法执行完成返回,返回结果:" + result);
    }

    public void logError(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        exception.printStackTrace();
        System.out.println(name + "方法执行异常-->LogUtil-->error-->方法执行失败,异常信息:" + exception);
    }

    public static void logEnd(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行结束LogUtil-->end-->方法执行结束");
    }

    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        // 执行参数
        Object[] args = pjp.getArgs();
        // 执行方法名
        String name = pjp.getSignature().getName();
        Object proceed = null;
        try {
            // @Before
            System.out.println(name + "方法执行开始-->around环绕-->方法执行前,参数列表:" + Arrays.toString(args));
            // 利用反射执行方法,类似于method.invoke(obj,args);
            proceed = pjp.proceed(args);
            // @AfterReturning
            System.out.println(name + "方法执行完成返回-->around环绕-->方法执行完成返回,返回结果:" + proceed);
        } catch (Exception e){
            // @AfterThrowing
            System.out.println(name + "方法执行异常-->around环绕-->方法执行失败,异常信息:" + e);
        } finally {
            // @After
            System.out.println(name + "方法执行结束-->around环绕-->方法执行结束");
        }
        // 返回值,此处返回值是什么,对应方法获取的返回值则是啥
        // 所以此处一定要返回执行反射后返回的值
        return proceed;
    }
}

5.2 注解方式

  1. 将目标类和切面类注入到ioc容器中 @Component
  2. 标示那个是切面类 @Aspect
  3. 在切面类中标示在何时调用切面类 @Before @After @AfterReturning @AfterThrowing @Around
  4. 开启基于注解的aop功能
@Aspect
@Order(1)   // 多个切面时运行顺序按照order小的先执行
@Component
public class LogUtil02 {

    /**
     *
     * try {
     *      @Before
     *      invoke = method.invoke(calculator, args);
     *      @AfterReturning
     * }catch (Exception e){
     *      @AfterThrowing
     * }finally {
     *      @After
     * }
     *
     * @Before 方法执行前运行                  前置通知
     * @After 方法执行结束运行                  后置通知
     * @AfterReturning 方法正常返回时运行        异常通知
     * @AfterThrowing 目标方法抛出异常后运行      返回通知
     * @Around 环绕                           环绕通知
     *
     *
     * Spring4和Spring5执行顺序存在差异
     * 执行顺序(Spring4):
     *  正常执行:@Before-->@After-->@AfterReturning
     *  异常执行:@Before-->@After-->@AfterThrowing
     * 执行顺序(Spring5):
     *  正常执行:@Before-->@AfterReturning-->@After
     *  异常执行:@Before-->@AfterThrowing-->@After
     *
     * 切入点表达式
     *      固定写法:execution(访问权限修饰符 返回值类型 全类名.方法名(参数)
     *      通配符:
     *          *
     *              1、匹配一个或多个字符
     *                  execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int)
     *                  execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*.*(int ,int)
     *                  execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*03.*(int ,int)
     *              2、任意参数(第一个参数为int 第二个参数为任意参数)
     *                  execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*03.*(int ,*)
     *              3、只能匹配一层路径
     *                  execution(public int com.guocoffee.spring.aop.*.impl.MyMath*03.*(..)
     *              4、任意返回值
     *                  execution(public * com.guocoffee.spring.aop.*.impl.MyMath*03.*(..)
     *              5、访问权限修饰符不能写*  可以不写,默认任意修饰符
     *                  execution(* com.guocoffee.spring.aop.*.impl.MyMath*03.*(..)
     *          ..
     *              1、匹配任意参数
     *                  execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*03.*(..)
     *              2、匹配任意层级
     *                  execution(public int com.guocoffee.spring..impl.MyMath*03.*(..)
     *
     * 最模糊: execution(* *(..)) 或者 execution(* *.*(..))  任意包下的任意类,尽量不要使用
     *
     * && || !
     *
     * && 两个表达式均需要满足
     * || 两个表达式只要满足一个
     * ! 除表达式以外的
     * execution(public int com.guocoffee.spring..impl.MyMath*03.*(..) && execution(public int com.guocoffee.spring..impl.MyMath*02.*(..)
     */

    /**
     * 表达式抽取
     * 1、新建一个无放回值的空方法
     * 2、使用@Pointcut注解,将表达是写入其中
     * 3、@Before("myAspect()") @After("myAspect()") 在注解里直接使用此方法
     */
    @Pointcut("execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))")
    public void myAspect(){};

    @Before("myAspect()")
    public void logStart(JoinPoint joinPoint){
        // 获取目标发放运行是传输的参数
        Object[] args = joinPoint.getArgs();

        // 获取方法签名
        // 方法签名:int com.guocoffee.spring.aop.aop02.impl.MyMathCalculator03.add(int,int)
        Signature signature = joinPoint.getSignature();
        System.out.println("方法签名:"+signature);
        // 获取方法名
        String name = signature.getName();
        System.out.println(name + "方法执行开始-->LogUtil-->start-->方法执行前,参数列表:" + Arrays.toString(args));
    }

    /**
     * returning = "result" 告诉spring,用result来接受执行方法返回的值
     * double result:只接受double类型的返回值
     * Object result:任意类型的返回值
     * @param joinPoint 封装了链接点的所有信息
     * @param result 返回结果
     */
    @AfterReturning(value = "execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))",returning = "result")
    public void logReturn(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行完成返回-->LogUtil-->return-->方法执行完成返回,返回结果:" + result);
    }

    /**
     * throwing = "exception" 告诉spring,用exception来接受运行方法时出现的异常
     *
     * NullPointerException exception:只接受空指针异常
     * Exception exception:接受所有异常
     *
     * @param joinPoint 封装了链接点的所有信息
     * @param exception 接受错误信息
     */
    @AfterThrowing(value = "execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))",throwing = "exception")
    public void logError(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        exception.printStackTrace();
        System.out.println(name + "方法执行异常-->LogUtil-->error-->方法执行失败,异常信息:" + exception);
    }

    @After("execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))")
    public static void logEnd(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行结束LogUtil-->end-->方法执行结束");
    }

    /**
     * 环绕通知是Spring中最强大的,集合了其他四种
     */
    @Around("myAspect()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        // 执行参数
        Object[] args = pjp.getArgs();
        // 执行方法名
        String name = pjp.getSignature().getName();
        Object proceed = null;
        try {
            // @Before
            System.out.println(name + "方法执行开始-->around环绕-->方法执行前,参数列表:" + Arrays.toString(args));
            // 利用反射执行方法,类似于method.invoke(obj,args);
            proceed = pjp.proceed(args);
            // @AfterReturning
            System.out.println(name + "方法执行完成返回-->around环绕-->方法执行完成返回,返回结果:" + proceed);
        } catch (Exception e){
            // @AfterThrowing
            System.out.println(name + "方法执行异常-->around环绕-->方法执行失败,异常信息:" + e);
        } finally {
            // @After
            System.out.println(name + "方法执行结束-->around环绕-->方法执行结束");
        }
        // 返回值,此处返回值是什么,对应方法获取的返回值则是啥
        // 所以此处一定要返回执行反射后返回的值
        return proceed;
    }

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

    <context:component-scan base-package="com.guocoffee.spring.aop.aop02"></context:component-scan>
    <!--
        使用注解开启aop
        1、将目标类和切面类注入到ioc容器中 @Component
        2、标示那个是切面类 @Aspect
        3、在切面类中标示在何时调用切面类 @Before @After @AfterReturning @AfterThrowing @Around
        4、开启基于注解的aop功能
    -->
    <!-- 开启基于注解的aop功能,需要aop命名空间 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值