• Spring Aop 详解二


    这是Spring Aop的第二篇,案例代码很详解。基本概念可以查看前文。

    切入点表达式

    execution

    execution表达式是到方法级别,具体构成查看上一篇文章中 Spring Aop 详解一

    完全不限制(运行时报错)

    正常情况下,我们可以写出这样不会编译报错的表达式,但是执行效果就呵呵了。不知道是不是规避这种不限制的规则。也不知道是不是我写错了,有搞明白的师兄可以指点一下。

    @Before("execution(* *..*(..))")
    

    限制到包的所有方法

    @Before("execution(* demo.aop.service.*(..))") //demo.aop.service包
    @Before("execution(* demo.aop.service..*(..))") //demo.aop.service包及子包
    @Before("execution(* demo.aop.service..save*(..))")//demo.aop.service 包及子包中sava开头的方法
    

    参数限制

    @Before("execution(* demo.aop.service..*()) ")//demo.aop.service 包及子包中 不需要参数的方法  如save()方法
    
    //demo.aop.service 包及子包中参数列表为 (int,String) 的方法,如save(int age, String name)方法
    @Before("execution(* demo.aop.service..*(int,String)) ")
    
    //限制方法参数时,如果参数是复杂类型参数,需要将这个类型写完整:java.util.List
    @Before("execution(* demo.aop.service..*(java.util.List)) ")
    

    且或非

    //表达式可以用 '&', '||' 和 '!'  做合并
    //包含  demo.aop.controller 下所有类的所有方法  且 不包含 demo.aop.controller.PassportController的所有方法
    @Before("execution(* demo.aop.controller..*(..)) " +
                " && !execution(* demo.aop.controller.PassportController.*(..))"
                + " || execution(* demo.aop.service..*(..))"
        )
    

    within

    within表示确切到类的所有方法,表达式只表达到类级别

    //所有controller包里所有的类的方法
    @Before("within(demo.aop.controller.*)")
    
    //所有controller包,及子包 里所有的类的方法
    @Before("within(demo.aop.controller..*)")
    
    //within 确切到具体的类WithinTestController
    @Before("within(demo.aop.controller.WithinTestController)")
    

    bean

    //通过bean制定 spring容器中的名为beanTestController的bean
    //spring中的bean名字会默认小写首字母,或者显示的命名 @RestController("beanTestController")
    @Before("bean(beanTestController)")
    

    参数引用 args

    如果我们在通知中需要获取到被切入方法(连接点)的参数,那么我们可以通过args表达式来引用。

    引用

    //参数引用,讲切入点的参数引入到通知里面去
    args这里除了引用连接点中的参数之外,还有限制的作用,也就是它只会匹配拥有(String name, Integer age)参数的连接点
    @Before("execution(* demo.aop.controller..*.*(..)) && args( name, age)")
    public void cut2(String name, Integer age) {
        log.info("参数引用,讲切入点的参数引入到通知里面去");
        log.info(name);
        log.info(age.toString());
    }
    

    限制

    //限制参数列表的类型为 ( ),切入没有参数的方法
    @Before("execution(* demo.aop.controller..*.*(..)) && args( )")
    public void cut() {
        log.info("限制参数列表的类型为 ()");
    }
        
    //限制参数列表的类型为 ( String, Integer)
    @Before("execution(* demo.aop.controller..*.*(..)) && args( String, Integer)")
    public void cut1() {
    log.info("限制参数列表的类型为 ( String, Integer)");
    }
    

    引入(Introduction)

    上一篇中概念中提到了引入,具体的实现我们来看一个案例

    • 切面类DeclareParentsAspect.java
    package demo.aop.aspect;
    
    import demo.aop.introduction.CommonParent;
    import demo.aop.introduction.CommonParentImpl;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.DeclareParents;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    @Slf4j
    public class DeclareParentsAspect {
    
        //demo.spring.aop.service..* 代表demo.spring.aop.service包和子包下的所有类
        //这样声明了service包中的类都会引入一个CommonParent父接口,并用CommonParentImpl实现,形成一个代理对象commonParent
        @DeclareParents(value="demo.aop.service..*", defaultImpl= CommonParentImpl.class)
        private CommonParent parent;
        
        
        @Before("execution (* demo.aop.service.UserService.*(..)) && this(commonParent)")
        public void beforeUserService(CommonParent commonParent) {
    
                log.info(commonParent.getClass().toString());
                commonParent.doSomething();
        }
    
    }
    
    
    • 具体引入的接口的实现CommonParent.java CommonParentImpl.java
    package demo.aop.introduction;
    
    public interface CommonParent {
        public void doSomething();
    }
    
    package demo.aop.introduction;
    
    public class CommonParentImpl implements CommonParent {
        @Override
        public void doSomething() {
           log.info("doSomething");
        }
    }
    
    • 测试接口 DeclareParentsTestController.java
    package demo.aop.controller;
    
    import demo.aop.service.RoleService;
    import demo.aop.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @Slf4j
    public class DeclareParentsTestController {
    
        @Autowired
        UserService userService;
    
        @Autowired
        RoleService roleService;
    
        @GetMapping("/declare")
        public String test(){
            userService.save();
            return "ok";
        }
    
    }
    
    

    访问http://localhost:8080/declare 运行结果如下

    2020-10-21 10:22:53.645   demo.aop.aspect.DeclareParentsAspect     : class demo.aop.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$58f5d27e
    2020-10-21 10:22:53.645   demo.aop.introduction.CommonParentImpl   : 引入(Introduction)测试 doSomething
    

    this

    表达式中的this在前文 Spring Aop 详解一。这里和args类似,但这里是直接把service包中的代理类引入进来,我们输出了 commonParent的类型是demo.aop.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$58f5d27e代理对象,并且这个代理对象通过代理得到了doSomething()方法。

    @DeclareParents

    该注解传入一个类似切入点表达式的表达式,让所有的demo.aop.service中的接口的spring bean都成为了代理类了。

    接下来我们证明所有的的service都已经成了代理类了。我们修改一下测试接口如下

    package demo.aop.controller;
    
    
    import demo.aop.service.RoleService;
    import demo.aop.service.UserService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @Slf4j
    public class DeclareParentsTestController {
    
        @Autowired
        UserService userService;
    
        @Autowired
        RoleService roleService;
    
        @GetMapping("/declare")
        public String test(){
            userService.save();
            return "ok";
        }
    
    
         @GetMapping("/declare2")
        public String test2(){
            log.info(roleService.getClass().toString());
            CommonParent commonParent=(CommonParent)roleService;
            commonParent.doSomething();
            roleService.save();
            return "ok";
        }
    }
    
    

    访问http://localhost:8080/declare2 运行结果如下

    2020-10-21 11:04:04.976   d.a.c.DeclareParentsTestController       : class demo.aop.service.impl.RoleServiceImpl$$EnhancerBySpringCGLIB$$e316d514
    2020-10-21 11:04:04.977   demo.aop.introduction.CommonParentImpl   : 引入(Introduction)测试 doSomething
    

    解读如下

    • execution (* demo.aop.service.UserService.*(..))使得beforeUserService通知不会再roleService的save方法执行。
    • @DeclareParents(value="demo.aop.service..*", defaultImpl= CommonParentImpl.class)会使得 roleService 成为一个引入了CommonParent接口实现的 代理对象
    • 因为roleService是代理对象(划重点),并且是实现了CommonParent接口,所以能够类型转换再调用doSomething()方法

    目标对象引用 target

    接着在DeclareParentsAspect.java中添加如下方法

    @Before("execution (* demo.aop.service..*.*(..)) && target(o)")
        public void beforeUserService(Object o) {
            log.info(o.getClass().toString());
    
        }
    
    

    访问http://localhost:8080/declare2 运行结果如下

    2020-10-21 11:20:45.329   demo.aop.aspect.DeclareParentsAspect     : class demo.aop.service.impl.RoleServiceImpl
    

    通过target表达式,我们可以引用得到目标对象目标对象就是被代理的对象,也就是未被切入,也没被代理的对象(官方文档:这个对象永远是一个被代理(proxied)对象)。

    连接点对象(JoinPoint)

    在前文中我们使用了环绕通知,而环绕通知中用到了连接点ProceedingJoinPoint,ProceedingJoinPoint是JoinPoint的子类。其他的通知我们可以使用JoinPoint来引入。

    • 切面 JoinPointAspect.java
    package demo.aop.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    
    @Aspect
    @Component
    public class JoinPointAspect {
    
        //JoinPoint 接口提供了一系列有用的方法,
        // 比如 getArgs()(返回方法参数)、
        // getThis()(返回代理对象)、
        // getTarget()(返回目标对象)、
        // getSignature()(返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)
        @Before("execution (* demo.aop.controller.JoinPointTestController.before(..))")
        public void jp3(JoinPoint point) {
            System.out.println(Arrays.toString(point.getArgs()));
            System.out.println(point.getThis().getClass());
            System.out.println(point.getTarget().getClass());
        }
    
    }
    
    • 测试接口 JoinPointTestController.java
    package demo.aop.controller;
    
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class JoinPointTestController {
    
        @GetMapping("/join/point/Before")
        public String before(String name){
            return "ok";
        }
    }
    
    

    访问http://localhost:8080/join/point/Before?name=xxx 后台输出结果为

    [xxx]
    class demo.aop.controller.JoinPointTestController$$EnhancerBySpringCGLIB$$6a0dc9a4
    class demo.aop.controller.JoinPointTestController
    

    下文预告

    • 通知优先级
    • @ControllerAdvice 实现统一错误处理

    完整代码 https://gitee.com/haimama/java-study/tree/master/spring-aop-demo

  • 相关阅读:
    峰哥说技术:14-Spring Boot异常处理方案源码解析与实践
    峰哥说技术:13-Spring Boot ControllerAdvice处理全局异常
    峰哥说技术:12-Spring Boot文件上传
    峰哥说技术:11-Spring Boot返回JSON
    Vant+小程序+购物车实例
    Element-ui框架Tree树形控件切换高亮显示选中效果
    Element-ui框架checkbox复选框回显
    Vue+Element-ui+DateTimePicker 日期时间选择器传值给后台
    Vue+Element+Select获取选中的对象
    Vue+Element+computed实现购物车
  • 原文地址:https://www.cnblogs.com/mxjhaima/p/13851856.html
Copyright © 2020-2023  润新知