• SpringBoot学习11 - Spring-Aop(常用的切点表达式关键字Demo讲解演示)


    3. Spring-AOP

    注意1: spring-aop仅支持在方法层面中的切面,如果想支持他更加细粒化的切面(比如字段)使用AspectJ,官方文档1官方文档2

    注意2: 对于多个切面都指向同一个切点问题,如果切面没有实现Ordered接口或者添加@Order注解定义切面执行优先级的情况下,切面的执行顺序是不固定的,官方文档

    文档入口

    官网文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    概述

    文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn

    使用AOP方式
    1. 基于XML形式(spring独有):schema-based approach
    2. 基于注解方式(AspectJ包的注解):@AspectJ annotation style
    概念
    概念
    Join point(连接点):需要被横切的方法位置定义(比如某个controller方法、含有某个注解的业务的方法或者需要被日志记录的方法等等)
    pointcut(切点):其实就是用什么【 切点表达式】来表示【连接点】==可以理解成连接点、切点是一个东西
    @annotation(logInfo):某个业务方法含有自定义的@LogInfo注解
    execution(* com.xyz.myapp.service.*.*(..)):service包下的类某个业务方法
    Aspect(切面):增强的内容具体实现类:比如自定义的LogAop类 == 很像Spring的拦截器HandlerInterceptor
    Advice(通知):增强的内容在【连接点】的执行时机 == 很像Spring的拦截器HandlerInterceptor的方法比如preHandle、postHandle
    Introduction(引入):其实就是【Advice执行时机】里面的具体的增强内容,可以简单理解成跟【Advice】一样的东西
    Weaving(织入):其实就是将增强的类(切面)与被增强的业务方法(切点)在编译时将两者结合生成一个新的类(代理类)== 代码层两者分开,运行时两者已经结合在一起
    执行时机(Advice)
    建议用这个
    通知(Advice)类别 == 增强的时机
    Before:在被增强的业务方法(切点)之前执行 == 执行时机顺序1
    After returning:在被增强的业务方法(切点)【正常执行完】之后(不抛出任何异常)== 执行时机顺序2.1
    After throwing:在被增强的业务方法(切点)执行过程【某种原因导致抛出异常】后 == 执行时机顺序2.2
    After:不管被增强的业务方法(切点)是正常执行结束或者抛出异常导致执行结束之后 == 执行时机顺序3
    Around:囊括前面的四种执行时机(Advice)
    切点表达式的关键字

    关键字作用文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-designators

    切点定义简单demo文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples

    切点定义详细讲解demo文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-advice

    常用
    很少用
    很少用
    较少用
    demo1
    demo2
    切点表达式关键字
    execution:匹配某些业务方法
    within:在spring-aop中仅用匹配某些业务方法(跟execution很像)
    this:advice方法的入参注入当前切面代理对象实例 == 建议使用org.aspectj.lang.JoinPoint#getThis替代
    target:advice方法的入参注入被代理对象实例 == 建议使用org.aspectj.lang.JoinPoint#getTarget替代
    args:advice方法的入参注入当前切面方法的入参 == 建议使用org.aspectj.lang.JoinPoint#getArgs替代
    bean(spring管理的Bean名字或id):spring-aop特有的关键字
    bean(basicErrorController)
    bean(*Controller)
    @target:切面方法所在的目标类的注解==仅以当前类的注解为准(父类有但当前类没有也匹配不到)
    @within:切面方法所在的目标类或者目标类的父类有目标注解(如果是在父类上,目标注解必须有@Inherited修饰才行)
    @args:切面方法入参的Class被修饰的注解 == 说的比较抽象直接看下面的demo就懂了
    @annotation:匹配含有某个注解的目标方法
    通配符
    *:任意类名、方法名
    &&:and
    ||:or
    !:否定
    .:下一层层级
    ..:任意层级

    切点定义

    //下面两条作用都是一样:都是匹配代表controller包下类中的方法切点
    execution(* top.linruchang.springbooottest.controller.*.*(..))
    within(top.linruchang.springbooottest.controller..*)
    
    //&&用法:必须是controller包下的方法  且  必须是TestController里面的testDict2方法
    execution(* top.linruchang.springbooottest.controller.*.*(..)) && execution(public * top.linruchang.springbooottest.controller.TestController.testDict2(..))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    参数内置可实例化的类

    官网文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj-advice-params

    接口
    JoinPoint的继承接口
    接口
    自动注入切面方法参数(一个切面方法只能填其中一个,多个则报错)
    JoinPoint:切点信息
    ProceedingJoinPoint:切点信息
    JoinPoint.StaticPart:切点信息

    在这里插入图片描述

    ControllerAop.java

    @Aspect
    @Component
    public class ControllerAop {
    
    
        @Pointcut("! bean(basicErrorController)")
        public void pointcut4(){}
    
    
        
        @Around(value = "bean(*Controller) && pointcut4()")
        public Object aroundMethod(JoinPoint proceedingJoinPoint) throws Throwable {
    
            Console.log("\n=====================");
            Console.log("切点执行前");
            Signature signature = proceedingJoinPoint.getSignature();
            Console.log("被ControllerAop横切的方法名:{},{}",proceedingJoinPoint.getTarget().getClass().getName(),signature.getName());
            Object result = ((ProceedingJoinPoint)proceedingJoinPoint).proceed();
            Console.log("切点执行后");
            return result;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    用法

    方式1:基于注解形式

    文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4


    SpringBoootTestApplication.java

    @SpringBootApplication
    @EnableAspectJAutoProxy(proxyTargetClass=true,exposeProxy=true)  //支持cglib代理以及当前线程获取获取当前的代理类(AOPContext)
    public class SpringBoootTestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBoootTestApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9


    TestController.java

    @RestController
    @RequestMapping("/test")
    public class TestController {
    
    	@GetMapping("testDict")
    	public Object testDict(HttpServletRequest httpServletRequest) {
    		Console.log("TestController的testDict被执行");        
    		return Dict.create()
    				.set("name", "lrc")
    				.set("year", 20);
    	}
    
    	@GetMapping("testDict2")
    	public Object testDict2(HttpServletRequest httpServletRequest) {
    		Console.log("TestController的testDict2被执行");    
    		return Dict.create()
    				.set("name", "lrc2")
    				.set("year", 202);
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    使用方式

    切点定义详细讲解demo文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-advice

    简单切点


    ControllerAop.java

    @Aspect
    @Component
    public class ControllerAop {
    
        @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        public void pointcut() {}
    
        
        @Around("pointcut()")
        public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Console.log("切点执行前");
            Object result = proceedingJoinPoint.proceed();
            Console.log("切点执行后");
            return result;
        }
    
    }
    
    
    //=================上下两种切点定义是一样的,都是同样的效果===================================
    
    @Aspect
    @Component
    public class ControllerAop {
    
        
        @Around("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Console.log("切点执行前");
    		Signature signature = proceedingJoinPoint.getSignature();
            Console.log("被ControllerAop横切的方法名:{}",signature.getName());        
            Object result = proceedingJoinPoint.proceed();
            Console.log("切点执行后");
            return result;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述

    在这里插入图片描述

    复杂切点(&&)

    此demo的切点: 必须是controller包下的方法 且 必须是TestController里面的testDict2方法

    @Aspect
    @Component
    public class ControllerAop {
    
        @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        public void pointcut() {}
    
        @Pointcut("execution(public * top.linruchang.springbooottest.controller.TestController.testDict2(..))")
        public void pointcut2() {}
    
        @Pointcut("pointcut() && pointcut2()")
        public void pointcut3(){}
        
        //下面三个切点定义都是一样,如果不想定义任何其他切点方法,直接使用第二种即可,当然还可直接将前面的三个pointcut、pointcut2、pointcut3方法直接删掉即可
        // @Around("pointcut2() && pointcut()")
        // @Around("execution(* top.linruchang.springbooottest.controller.*.*(..)) && execution(public * top.linruchang.springbooottest.controller.TestController.testDict2(..))")
        @Around("pointcut3()")
        public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Console.log("\n=====================");
            Console.log("切点执行前");
            Signature signature = proceedingJoinPoint.getSignature();
            Console.log("被ControllerAop横切的方法名:{}",signature.getName());
            Object result = proceedingJoinPoint.proceed();
            Console.log("切点执行后");
            return result;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述

    切点(Spring-AOP特有关键字)- Bean(Bean在Spring容器中的名字或ID)

    注意: 如果不知道某个类在Spring容器中的Bean名字,可以使用org.springframework.beans.factory.ListableBeanFactory#getBeanNamesForType(java.lang.Class)进行查看获取



    此demo的切点: Spring管理的Bean名是Controller结尾的类所有方法 且 不是BasicErrorController的方法,注意BasicErrorController在Spring容器中的名字的basicErrorController而不是BasicErrorController

    @Aspect
    @Component
    public class ControllerAop {
        
        @Pointcut("! bean(basicErrorController)")
        public void pointcut4(){}    
        
        @Around("bean(*Controller) && pointcut4()")
        // @Around("bean(*Controller) && !bean(basicErrorController)")
        public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
            Console.log("\n=====================");
            Console.log("切点执行前");
            Signature signature = proceedingJoinPoint.getSignature();
            Console.log("被ControllerAop横切的方法名:{},{}",proceedingJoinPoint.getTarget().getClass().getName(),signature.getName());
            Object result = proceedingJoinPoint.proceed();
            Console.log("切点执行后");
            return result;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    更细粒化的AOP执行时机注解- @Before、@AfterReturning、@AfterThrowing、@After

    注意: 个人不建议使用那么细粒化的注解,由于注解时机太多。建议使用@around万金油注解代表任意切面时机,进行替代



    TestController.java

    @RestController
    @RequestMapping("/test")
    public class TestController {
    
    
        @GetMapping("testDict2")
        public Object testDict2(@RequestParam(defaultValue = "1") Integer num, HttpServletRequest httpServletRequest) {
            Console.log("===TestController的testDict2被执行");
            int num2 = 1 / num;
            return Dict.create()
                    .set("name", "lrc")
                    .set("year", 20);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16


    ControllerAop2.java

    @Aspect
    @Component
    public class ControllerAop2 {
    
    
    
        @Pointcut("! bean(basicErrorController)")
        public void pointcut4(){}
    
        @Pointcut("bean(*Controller) && pointcut4()")
        public void pointcut5(){}
    
    
        public String classMethodName(JoinPoint joinPoint) {
            Signature signature = joinPoint.getSignature();
            if(signature instanceof MethodSignature) {
                String declaringTypeName = signature.getDeclaringTypeName();
                String name = signature.getName();
                return StrUtil.format("{}.{}",declaringTypeName,name);
            }
            return null;
        }
    
        /**
         * 切点执行前
         * @param joinPoint
         */
        @Before("pointcut5()")
        public void before(JoinPoint joinPoint) {
            Console.log("\n=====================");
            Console.log("【{}】before:切点执行前", classMethodName(joinPoint));
        }
    
        /**
         * 切点正常执行完
         * @param joinPoint
         * @param pointResultValue  切点执行过程中导致切点执行终止的异常
         */
        @AfterReturning(value = "pointcut5()", returning = "pointResultValue")
        public void afterReturning(JoinPoint joinPoint, Object pointResultValue) {
            Console.log("【{}】afterReturning:切点正常执行结束【{}】" , classMethodName(joinPoint),pointResultValue);
        }
    
    
        /**
         * 切点不正常执行完(有异常抛出)
         * @param joinPoint
         * @param pointThrowException
         */
        @AfterThrowing(value = "pointcut5()",throwing = "pointThrowException")
        public void afterThrowing(JoinPoint joinPoint,Throwable pointThrowException) {
            Console.log("【{}】afterThrowing:切点执行发生异常【{}】", classMethodName(joinPoint),ExceptionUtil.stacktraceToOneLineString(pointThrowException));
        }
    
    
        /**
         * 处理完:@afterReturning、@afterThrowing的增强方法后执行的逻辑
         * @param joinPoint
         */
        @After("pointcut5()")
        public void after(JoinPoint joinPoint) {
            Console.log("【{}】after:切点最终执行结束", classMethodName(joinPoint));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    在这里插入图片描述

    执行时机的方法注入切面方法的入参 - args(当前advice方法中的参数名,…)

    当前demo切面:controller包下的方法 且 切点方法第一个参数是Integer类型
    ControllerAop3.java

    @Aspect
    @Component
    public class ControllerAop3 {
        @Pointcut("execution(* top.linruchang.springbooottest.controller.TestController.testDict2(..))")
        public void pointcut6(){}
    
    
    
        @Around(value = "pointcut6() && args(num2,..)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint, Integer num2) throws Throwable {
            Console.log("\n==================");
            Console.log("around执行切面方法前:切面方法其中一个入参值(来自方法参数注入):num[{}]", num2);
            Console.log("around执行切面方法前:切面方法入参值(来自切面信息获取):", proceedingJoinPoint.getArgs());
            Object proceedResult = proceedingJoinPoint.proceed();
            Console.log("around执行切面方法结束");
            return proceedResult;
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    @RestController
    @RequestMapping("/test")
    public class TestController {
        @GetMapping("testDict2")
        public Object testDict2(@RequestParam(defaultValue = "1") Integer num, HttpServletRequest httpServletRequest) {
            Console.log("===TestController的testDict2被执行");
            int num2 = 1 / num;
            return Dict.create()
                    .set("name", "lrc")
                    .set("year", 20);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    执行时机的方法注入修饰切点方法的注解 - @annotation(当前advice方法中的注解类型的参数名,…)


    LogInfo.java

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface LogInfo {
    
        /**
         * 日志内容
         * @return
         */
        String value() default "";
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12


    当前demo切面:controller包下的方法 且 切点方法被@Loginfo注解修饰
    ControllerAop4.java

    @Aspect
    @Component
    public class ControllerAop4 {
    
    
    
    
        // @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        @Pointcut("within(top.linruchang.springbooottest.controller..*)")
        public void pointcut6(){}
    
    
    
    
        @Around(value = "pointcut6() && @annotation(logInfo)")
        // @Around(value = "@annotation(logInfo)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo) throws Throwable {
            Console.log("\n==================");
            Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
            String logInfoValue = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getAnnotation(LogInfo.class).value();
            Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
            Object proceedResult = proceedingJoinPoint.proceed();
            Console.log("around执行切面方法结束");
            return proceedResult;
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29


    TestController.java

    @RestController
    @RequestMapping("/test")
    public class TestController {
        @LogInfo("功能:测试AOP")
        @GetMapping("testDict")
        public Object testDict(@RequestParam(defaultValue = "1") Integer num, HttpServletRequest httpServletRequest) {
            Console.log("TestController的testDict被执行");
    
            int num2 = 1 / num;
            return Dict.create()
                    .set("name", "lrc")
                    .set("year", 20);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    执行时机的方法注入修饰切点方法的注解、以及切点方法的入参


    LogInfo.java

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface LogInfo {
    
        /**
         * 日志内容
         * @return
         */
        String value() default "";
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12


    当前demo切面:controller包下的方法 且 切点方法必须含有至少两个入参同时切点方法被@Loginfo注解修饰
    ControllerAop5

    @Aspect
    @Component
    public class ControllerAop5 {
    
    
    
        @Pointcut("! bean(basicErrorController)")
        public void pointcut4(){}
    
        @Pointcut("bean(*Controller) && pointcut4()")
        public void pointcut5(){}
    
    
        public String classMethodName(JoinPoint joinPoint) {
            Signature signature = joinPoint.getSignature();
            if(signature instanceof MethodSignature) {
                String declaringTypeName = signature.getDeclaringTypeName();
                String name = signature.getName();
                return StrUtil.format("{}.{}",declaringTypeName,name);
            }
            return null;
        }
    
    
        // @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        @Pointcut("within(top.linruchang.springbooottest.controller..*)")
        public void pointcut6(){}
    
    
    
    
        @Around(value = "pointcut6() && args(num4,num5,..) && @annotation(logInfo)")
        // @Around(value = "@annotation(logInfo)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo, Object num4, Object num5) throws Throwable {
            Console.log("\n==================");
            Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
            Console.log("around执行切面方法前:切面方法的入参(来自方法参数注入):{},{}", num4 ,num5);
            String logInfoValue = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getAnnotation(LogInfo.class).value();
            Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
            Console.log("around执行切面方法前:切面方法的入参(来自切面信息获取):", proceedingJoinPoint.getArgs());
            Object proceedResult = proceedingJoinPoint.proceed();
            Console.log("around执行切面方法结束");
            return proceedResult;
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48


    TestController.java

    @RestController
    @RequestMapping("/test")
    public class TestController {
        @LogInfo("功能:测试AOP3")
        @GetMapping("testDict3")
        public Object testDict3(@RequestParam(defaultValue = "1") Integer num, @RequestParam(defaultValue = "1") Integer num2, HttpServletRequest httpServletRequest) {
            Console.log("===TestController的testDict2被执行");
            int newNum = 1 / num;
            return Dict.create()
                    .set("name", "lrc")
                    .set("year", 20);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    切点表达式的target、this关键字使用(很少用不想看的直接忽略即可)

    ControllerAop6.java

    @Aspect
    @Component
    public class ControllerAop6 {
    
    
        // @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        @Pointcut("within(top.linruchang.springbooottest.controller..*)")
        public void pointcut6(){}
    
    
        @Around(value = "pointcut6() && target(targetBean) && this(thisBean)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint, Object targetBean, Object thisBean) throws Throwable {
            Console.log("\n==================");
            Console.log("around执行切面方法前:切面方法的所在的对象实例(来自方法参数注入):{}========={}",targetBean.getClass(),targetBean);
            Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自方法参数注入):{}========={}",thisBean.getClass(),thisBean);
            Console.log("around执行切面方法前:切面方法的所在的对象实例(来自切面信息获取):{}========={}", proceedingJoinPoint.getTarget().getClass(),proceedingJoinPoint.getTarget());
            Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):{}========={}", proceedingJoinPoint.getThis().getClass(),proceedingJoinPoint.getThis());
            Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):targetBean与proceedingJoinPoint.getTarget()是否是同一个对象实例========={}", proceedingJoinPoint.getTarget() == targetBean);
            Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):thisBean与proceedingJoinPoint.getThis()是否是同一个对象实例========={}", proceedingJoinPoint.getThis() == thisBean);
            Console.log("around执行切面方法前:切面方法的所在的代理对象实例(来自切面信息获取):thisBean与targetBean是否是同一个对象实例========={}", targetBean == thisBean);
            Object proceedResult = proceedingJoinPoint.proceed();
            Console.log("around执行切面方法结束");
            return proceedResult;
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27


    TestController.java

    @RestController
    @RequestMapping("/test")
    public class TestController {
        @LogInfo("功能:测试AOP4")
        @GetMapping("testDict4")
        public Object testDict4(@RequestParam(defaultValue = "1") Integer num) {
            Console.log("===TestController的testDict4被执行");
            int newNum = 1 / num;
            return Dict.create()
                    .set("name", "lrc")
                    .set("year", 20);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    执行时机的方法注入修饰切点方法入参Bean的Class上的注解 - @args(advice方法入参名字)

    EnhaceAnnotationUtil.java

    package top.linruchang.springbooottest.utils;
    
    import cn.hutool.core.annotation.AnnotationUtil;
    import cn.hutool.core.exceptions.UtilException;
    import cn.hutool.core.util.ArrayUtil;
    import cn.hutool.core.util.ModifierUtil;
    import cn.hutool.core.util.ReflectUtil;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.util.*;
    
    /**
     * @author LinRuChang
     * @version 1.0
     * @date 2022/09/14
     * @since 1.8
     **/
    public class EnhaceAnnotationUtil extends AnnotationUtil {
    
    
        /**
         * 注解的值转成Map
         *
         * @param currentAnnotation 注解实例
         * @return
         */
        public static Map<String, Object> getAnnotationValueMap(Annotation currentAnnotation) {
    
            return Optional.ofNullable(currentAnnotation)
                    .map(annotation -> {
                        Method[] objectMethods = ReflectUtil.getMethods(Object.class);
                        final List<Method> methods = ReflectUtil.getPublicMethods(annotation.getClass(), t -> {
                            if (ArrayUtil.isEmpty(t.getParameterTypes())) {
                                // 只读取无参方法
                                final String name = t.getName();
                                return  (false == "hashCode".equals(name))
                                        && (false == "toString".equals(name))
                                        && (false == "annotationType".equals(name))
                                        && (false == ArrayUtil.contains(objectMethods, t));
                            }
                            return false;
                        });
    
    
                        final HashMap<String, Object> result = new HashMap<>(methods.size(), 1);
                        for (Method method : methods) {
                            result.put(method.getName(), ReflectUtil.invoke(annotation, method));
                        }
                        return result;
                    }).orElse(new HashMap<>());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54


    TestController.java

    @RestController
    @RequestMapping("/test")
    public class TestController {
        @LogInfo("功能:测试AOP9")
        @GetMapping("testDict9")
        public Object testDict9(Article article, HttpServletRequest httpServletRequest) {
            Console.log("===TestController的testDict9被执行");
            return article;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10


    Article.java

    @Data
    @ToString(callSuper=true)
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Accessors(chain = true)
    @TableName("article")
    public class Article extends BaseDB implements Serializable {
    
        private static final long serialVersionUID = -4714261187453073302L;
    
        /** 文章标题 */
        private String title;
    
        /** 文章内容 */
        private String content;
    
        /** 缩略文章,最大200字符,从content抽出的纯文本内容  参考{@link Article#ABBREVIATION_CONTENT_CHAR_MAX}*/
        private String abbreviationContent;
    
        /** 文章被查看次数 */
        private Integer checkNum;
    
        /** 当前文章被点赞数 */
        private Integer likeNum;
    
        /** 当前文章不喜欢数 */
        private Integer notLikeNum;
    
        /** 文章是否违规:0未违规 1违规 - 违规不可显示 */
        private Integer isViolation;
    
        /** 创建者 - 冗余字段 - user表的nickName */
        private String createBy;
    
        /**列表文章的图片显示 - 如果指定则使用指定的图片(功能未做),没有指定则使用文章第一张图片,在没有则使用文章中有图标则使用目录分裂的图片*/
        private String imgUrl;
    
        /**文章内部图片*/
        private String imgUrls;
    
        /**文章内容类型:1富文本 2Markdown 3留空*/
        private String type;
    
        /**文章状态;-1违规 0草稿 1发布且公开  2发布且私密*/
        private String status;
    
    
        //================常亮======================
        //文章状态
        public final  static String STATUS_VIOLATION = "-1";
        public final  static String STATUS_DRAFT = "0";
        public final  static String STATUS_PUBLISH_PUBLIC = "1";
        public final  static String STATUS_PUBLISH_PRIVATE = "2";
    
        //文章编辑类型
        public final  static String TYPE_MARKDOWN = "1";
        public final  static String TYPE_RICH = "2";
    
        public final static Integer  ABBREVIATION_CONTENT_CHAR_MAX = 200;
    
    
    
        //=====================================
    
        /**文章标签ID*/
        @TableField(exist = false)
        private String tagIds;
    
        /**文章所属用户*/
        @TableField(exist = false)
        private String userId;
    
        /**文章所属用户昵称*/
        @TableField(exist = false)
        private String nickName;
    
    
        /** 是否点赞该文章 1点赞 0不点赞 */
        @TableField(exist = false)
        private Integer isLike;
    
        /** 是否踩这篇文章 1踩文章 0不睬文章 */
        @TableField(exist = false)
        private Integer isNotLike;
    
        /** 评论数 */
        @TableField(exist = false)
        private Integer commentNum;
    
        /** 文章创作者-头像图片地址 */
        @TableField(exist = false)
        private String headUrl;
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97


    当前demo切面:controller包下的方法 且 切点方法第一个参数的类型Class被@TableName修饰,且切点方法被@LogInfo注解修饰
    ControllerAop9.java

    @Aspect
    @Component
    public class ControllerAop9 {
    
    
    
        @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        // @Pointcut("within(top.linruchang.springbooottest.controller..*)")
        public void pointcut6() {
        }
    
    
        @Around(value = "@annotation(logInfo) && @args(tableName,..)")
        public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo, TableName tableName) throws Throwable {
            Console.log("\n==================");
            Console.log("around执行切面方法前");
    
            Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
    
    
    
            Method currentJointPointMethod = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
            String logInfoValue = currentJointPointMethod.getAnnotation(LogInfo.class).value();
            Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
    
            Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自方法参数注入):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(tableName));
    
            TableName firstArgsClassTableName = AnnotationUtil.getAnnotation(proceedingJoinPoint.getArgs()[0].getClass(), TableName.class);
            Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自切面信息获取):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(firstArgsClassTableName));
    
            Object proceedResult = proceedingJoinPoint.proceed();
            Console.log("around执行切面方法结束");
            return proceedResult;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在这里插入图片描述

    执行时机的方法注入修饰切点方法所在的Class上的注解 - @target(advice方法入参名字)、@within(advice方法入参名字)

    注意: @target、@within功能差不多,都是取切点类上的注解。但是@within可以取目标类的父类注解,所以建议使用@within,因为覆盖范围更广


    Article.java

    @Data
    @ToString(callSuper=true)
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @Accessors(chain = true)
    @TableName("article")
    public class Article extends BaseDB implements Serializable {
    
        private static final long serialVersionUID = -4714261187453073302L;
    
        /** 文章标题 */
        private String title;
    
        /** 文章内容 */
        private String content;
    
        /** 缩略文章,最大200字符,从content抽出的纯文本内容  参考{@link Article#ABBREVIATION_CONTENT_CHAR_MAX}*/
        private String abbreviationContent;
    
        /** 文章被查看次数 */
        private Integer checkNum;
    
        /** 当前文章被点赞数 */
        private Integer likeNum;
    
        /** 当前文章不喜欢数 */
        private Integer notLikeNum;
    
        /** 文章是否违规:0未违规 1违规 - 违规不可显示 */
        private Integer isViolation;
    
        /** 创建者 - 冗余字段 - user表的nickName */
        private String createBy;
    
        /**列表文章的图片显示 - 如果指定则使用指定的图片(功能未做),没有指定则使用文章第一张图片,在没有则使用文章中有图标则使用目录分裂的图片*/
        private String imgUrl;
    
        /**文章内部图片*/
        private String imgUrls;
    
        /**文章内容类型:1富文本 2Markdown 3留空*/
        private String type;
    
        /**文章状态;-1违规 0草稿 1发布且公开  2发布且私密*/
        private String status;
    
    
        //================常亮======================
        //文章状态
        public final  static String STATUS_VIOLATION = "-1";
        public final  static String STATUS_DRAFT = "0";
        public final  static String STATUS_PUBLISH_PUBLIC = "1";
        public final  static String STATUS_PUBLISH_PRIVATE = "2";
    
        //文章编辑类型
        public final  static String TYPE_MARKDOWN = "1";
        public final  static String TYPE_RICH = "2";
    
        public final static Integer  ABBREVIATION_CONTENT_CHAR_MAX = 200;
    
    
    
        //=====================================
    
        /**文章标签ID*/
        @TableField(exist = false)
        private String tagIds;
    
        /**文章所属用户*/
        @TableField(exist = false)
        private String userId;
    
        /**文章所属用户昵称*/
        @TableField(exist = false)
        private String nickName;
    
    
        /** 是否点赞该文章 1点赞 0不点赞 */
        @TableField(exist = false)
        private Integer isLike;
    
        /** 是否踩这篇文章 1踩文章 0不睬文章 */
        @TableField(exist = false)
        private Integer isNotLike;
    
        /** 评论数 */
        @TableField(exist = false)
        private Integer commentNum;
    
        /** 文章创作者-头像图片地址 */
        @TableField(exist = false)
        private String headUrl;
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97


    Impact.java

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    //特别注意:这里很关键有没这个注解,关系到@within是否AOP可以匹配到父类的@Impact上 
    @Inherited
    public @interface Impact {
    
        String value() default "";
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9


    TestController.java

    @RestController
    @RequestMapping("/test")
    @Impact("作用:用于测试的控制器")
    public class TestController {
    
        @LogInfo("功能:测试AOP9")
        @GetMapping("testDict9")
        public Object testDict9(Article article, HttpServletRequest httpServletRequest) {
            Console.log("===TestController的testDict9被执行");
            return article;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13


    Test2Controller.java

    @Impact("作用:公共的控制器")
    public class BaseController {
    }
    
    @RestController
    @RequestMapping("/test2")
    public class Test2Controller extends BaseController {
    
        @LogInfo("功能:测试AOP9")
        @GetMapping("testDict9")
        public Object testDict9(Article article, HttpServletRequest httpServletRequest) {
            Console.log("===Test2Controller的testDict9被执行");
            return article;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16


    当前demo切面:controller包下的方法 且 切点方法第一个参数的类型Class被@TableName修饰,且切点方法被@LogInfo注解修饰,同时切点所在的目标类被@Impact注解修饰
    ControllerAop10.java

    @Aspect
    @Component
    public class ControllerAop10 {
    
    
    
        @Pointcut("execution(* top.linruchang.springbooottest.controller.*.*(..))")
        public void pointcut6() {
        }
    
    
        // @Around(value = "@annotation(logInfo) && @args(tableName,..) && @target(impact)")   //当前类无目标注解但继承的类有目标注解不会匹配
        @Around(value = "@annotation(logInfo) && @args(tableName,..) && @within(impact)")  //继承的类有目标注解(@Impact必须有@Inherited修饰才行)也行
        public Object around(ProceedingJoinPoint proceedingJoinPoint, LogInfo logInfo, TableName tableName, Impact impact) throws Throwable {
            Console.log("\n==================");
            Console.log("around执行切面方法前");
    
            Console.log("around执行切面方法前:切面方法的日志注解(来自方法参数注入):logInfo内容[{}]", logInfo.value());
            Method currentJointPointMethod = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
            String logInfoValue = currentJointPointMethod.getAnnotation(LogInfo.class).value();
            Console.log("around执行切面方法前:切面方法的日志注解(来自切面信息获取):", logInfoValue);
    
    
            Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自方法参数注入):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(tableName));
            TableName firstArgsClassTableName = AnnotationUtil.getAnnotation(proceedingJoinPoint.getArgs()[0].getClass(), TableName.class);
            Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自切面信息获取):tableName内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(firstArgsClassTableName));
    
            Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自方法参数注入):impact内容[{}]", EnhaceAnnotationUtil.getAnnotationValueMap(impact));
            Console.log("around执行切面方法前:切面方法第一个入参的Bean类上的注解(来自切面信息获取):impact内容[{}]", AnnotationUtil.getAnnotationValueMap(proceedingJoinPoint.getTarget().getClass(),Impact.class));
    
            Object proceedResult = proceedingJoinPoint.proceed();
            Console.log("around执行切面方法结束");
            return proceedResult;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36


    测试1:@within形式
    在这里插入图片描述


    测试2:@within形式 == 代码不用动,只要将注解Impact的@Inherited删掉即可
    在这里插入图片描述


    测试3:@target形式 == 将ControllerAop10的注释的切点放开,关闭原先切点的即可
    在这里插入图片描述

    方式2:基于XML形式(淘汰)

    文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-schema

    注意: 有需要的自行去官网学习,用XML配置Bean、AOP已经淘汰,遇到在去官网看即可,SpringBoot只要会基于注解方式的AOP即可

  • 相关阅读:
    shiro学习之HashedCredentialsMatcher密码匹配过程
    分布式调度器xxl-job
    511. 游戏玩法分析 I
    宇视网络视频录像机添加摄像机常规方法
    mac 打不开 idea 或者 pycharm 的方法
    基于SpringBoot的在线题库管理系统的设计与实现(源码+lw+部署文档+讲解等)
    SpringMVC基础概述
    OceanBase 4.2 主备库 FailOver 与 SwitchOver 概念
    php二维数组查找某个id=1的元素
    商品换购小程序
  • 原文地址:https://blog.csdn.net/weixin_39651356/article/details/126860004