Java:Spring 的 AOP

135 阅读5分钟

Java:Spring 的 AOP

测试活动:Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

1. AOP的四个常用术语

  • 连接点
    • 类中有哪些方法可以被增强。
    • 就是在功能中有多少个方法可以给你修改
  • 切入点
    • 实际被增强的方法
    • 你实际上要修改那个功能
  • 通知(增强)
    • 实际被增强的逻辑部分

    • 你修改了什么

    • 通知分5类

      • 前置通知 @Before

      • 后置通知 @AfterReturning

      • 环绕通知 @Around

      • 异常通知 @AfterThrowing

      • 最终通知 @After

        package com.atguigu.spring5.aopanno;
        
        import org.aspectj.lang.ProceedingJoinPoint;
        import org.aspectj.lang.annotation.*;
        import org.springframework.stereotype.Component;
        
        /**
         * @author workplace
         * @date 2022/3/9 19:18
         * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
         */
        @Component
        @Aspect
        public class UserProxy {
            /*
             * 我们通过在增强类里的通知方法上通过标签将切入点表达式注入value属性中指定增强
             *   @标签(value = "切入点表达式")
             *
             * 切入点表达式格式:
             *   exception(权限修饰符 返回类型 类全路径.方法名称(参数列表))
             *   权限修饰符:public,private,protect之类的,常用 * 标识任意权限修饰符
             *   返回类型:short,int,double,float,long,String,char,boolean,byte... 如果是 void 用空格代替
             *   类全路径:可以用 * 标识当前路径下的所有方法进行增强
             *   方法名称:可以用 * 标识当前类的所有方法增强
             *   参数列表:可以用 .. 来标表示所有的
             *
             * 例子:
             *   对指定包指定类的指定方法增强
             *   execution(* void com.atguigu.spring5.aopanno.User.add(..))
             *   对指定包指定类里所有方法增强
             *   execution(* public com.atguigu.spring5.aopanno.User.*(..))
             *   对包里所有类和方法增强(有几级包就写几级的 * )
             *   execution(* com.atguigu.spring5.aopanno.*.*(..))
             *
             * */
        
        
            /**
             * 打算让通知在方法之前运行。
             * 生成前置通知
             */
            @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
            public void before() {
                System.out.println("UserProxy before......");
            }
        
            /**
             * 最终通知
             */
            @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
            public void after() {
                System.out.println("UserProxy after......");
            }
        
            /**
             * 后置通知(返回通知)
             * 在被增强方法返回值之后执行
             */
            @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
            public void afterReturning() {
                System.out.println("UserProxy afterReturning......");
            }
        
            /**
             * 异常通知。在执行被增强方法之后进行。
             */
            @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
            public void afterThrowing() {
                System.out.println("UserProxy afterThrowing......");
            }
        
            /**
             * 环绕通知
             * @param point 被增强方法,连接点
             * @throws Throwable 被抛出的异常
             */
            @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
            public void around(ProceedingJoinPoint point) throws Throwable {
                System.out.println("before around");
        
                // 被增强的方法执行
                point.proceed();
        
                System.out.println("after around");
            }
        }
        
  • 切面
    • 把通知应用到切入点的过程。
    • 把修改过的功能放回到原来的位置就是切面。切面是个动作。

2. 演示 AOP 操作(AspectJ 注解)

  1. 创建类,在类里定义方法。这个方法就是我们说的连接点。

    image-20220309193420889

    package com.atguigu.spring5.aopanno;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:15
     */
    public class User {
        public void add() {
            System.out.println("User add......");
        }
    }
    
  2. 创建增强类(编写增强逻辑)

    image-20220309193956282

    package com.atguigu.spring5.aopanno;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    public class UserProxy {
        /**
         * 前置通知
         */
        public void before() {
            System.out.println("UserProxy before......");
        }
    }
    
  3. 进行通知的配置。红框就是添加的信息。

    image-20220309194300159

    <?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.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--开始注解扫描-->
        <context:component-scan base-package="com.atguigu.spring5.aopanno"/>
    
        <!--开启AspectJ生成代理对象-->
        <aop:aspectj-autoproxy/>
    </beans>
    
  4. 通过注解生成对象(IoC 内容)

    image-20220309194610041

    image-20220309194637077

    package com.atguigu.spring5.aopanno;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:15
     */
    @Component
    public class User {
        public void add() {
            System.out.println("User add......");
        }
    }
    
    package com.atguigu.spring5.aopanno;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    @Component
    public class UserProxy {
        /**
         * 前置通知
         */
        public void before() {
            System.out.println("UserProxy before......");
        }
    }
    
  5. 生成代理对象

    image-20220309195102433

    package com.atguigu.spring5.aopanno;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    @Component
    @Aspect
    public class UserProxy {
    }
    
  6. 通过注解对切入点生成前置增强

    image-20220309202245377

    package com.atguigu.spring5.aopanno;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    @Component
    @Aspect
    public class UserProxy {
        /*
         * 我们通过在增强类里的通知方法上通过标签将切入点表达式注入value属性中指定增强
         *   @标签(value = "切入点表达式")
         *
         * 切入点表达式格式:
         *   exception(权限修饰符 返回类型 类全路径.方法名称(参数列表))
         *   权限修饰符:public,private,protect之类的,常用 * 标识任意权限修饰符
         *   返回类型:short,int,double,float,long,String,char,boolean,byte... 如果是 void 用空格代替
         *   类全路径:可以用 * 标识当前路径下的所有方法进行增强
         *   方法名称:可以用 * 标识当前类的所有方法增强
         *   参数列表:可以用 .. 来标表示所有的
         *
         * 例子:
         *   对指定包指定类的指定方法增强
         *   execution(* void com.atguigu.spring5.aopanno.User.add(..))
         *   对指定包指定类里所有方法增强
         *   execution(* public com.atguigu.spring5.aopanno.User.*(..))
         *   对包里所有类和方法增强(有几级包就写几级的 * )
         *   execution(* com.atguigu.spring5.aopanno.*.*(..))
         *
         * */
    
    
        /**
         * 打算让通知在方法之前运行。
         * 生成前置通知
         */
        @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void before() {
            System.out.println("UserProxy before......");
        }
    }
    
  7. 通过测试类创建被增强类的方法可以得到结果

    package test;
    
    import com.atguigu.spring5.aopanno.User;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @author workplace
     * @date 2022/3/9 20:25
     */
    public class TestAop {
        @Test
        public void test1() {
            ApplicationContext context
                    = new ClassPathXmlApplicationContext("bean1.xml");
            User user = context.getBean("user", User.class);
            user.add();
        }
    }
    

    结果

    image-20220309202729198

  8. 总结

    image-20220309203621290

3. 抽取相同切入点

  • 如果多个增强都是指向同一个切入点,可以把此切入点抽取出来。这样子就不需要每写一个增强方法就要写一次相同的切入点表达式了。(懒狗必备)

  • 通过 @PointCut(value = "execution(切入点表达式)") 将切入点抽取出来。

  • image-20220309205755577

    @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void pointCut() {
    
    }
    
    /**
     * 打算让通知在方法之前运行。
     * 生成前置通知
     */
    @Before(value = "pointCut")
    public void before() {
        System.out.println("UserProxy before......");
    }
    
    /**
     * 最终通知
     */
    @After(value = "pointCut")
    public void after() {
        System.out.println("UserProxy after......");
    }
    

4. 多个增强类同时增强一个切入点,执行优先级

  • 如果一个方法在同一时间有多个增强类的增强方法,我们可以通过设置增强类的优先级来调整增强方法的执行次序。

  • 通过给类设置标签 @Order(优先级) 设置优先级。数字越小优先级越高。

    image-20220309210616962

    @Component
    @Aspect
    @Order(1)
    public class UserProxy {}