zoukankan      html  css  js  c++  java
  • spring boot 使用 AOP 的正确姿势 --- 心得

    1.前言

      向spring boot转型,所有的配置基本上是用注解完成 ,以前使用spring MVC 需要写一大堆xml文件来配置。

    基本上没什么变化,但是有些地方需要注意:

      环绕通知不要使用异常捕获,否则出现异常后,异常通知不会执行,而返回通知仍然会执行,
    同时返回结果为null,
    可以使用
    throws Throwable 配合pjp.proceed(); 即可,这样就不会出现红色下划线!

      如果在同一个方法引入多个切面,那么需要定义使用顺序,先开始执行的切面将最后结束,呈包含关系,
    在切面类使用注解@Order(整数) 来定义顺序 ,数字越小,执行的越早。
    
    

     2.AOP术语解释

    • 通知、增强处理(Advice) 就是你想要的功能,也就是上说的安全、事物、日子等。

          你给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码

    • 连接点(JoinPoint) 这个就更好解释了,就是spring允许你是通知(Advice)的地方,

          那可就真多了,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,

          spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那

          不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。

    • 切入点(Pointcut) 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,

          那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),

          你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定

          义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

    • 切面(Aspect) 切面是通知和切入点的结合。现在发现了吧,没连接点什么事,链接点就是为了

          让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过

          方法名中的befor,after,around等就能知道),二切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

    • 引入(introduction) 允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
    • 目标(target) 引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,

          他可以在毫不知情的情况下,被咋们织入切面。二自己专注于业务本身的逻辑。

    • 代理(proxy) 怎么实现整套AOP机制的,都是通过代理,这个一会儿给细说。
    • 织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程,有三种方式,但spring采用的是运行时
    • 目标对象 – 项目原始的Java组件。
    • AOP代理 – 由AOP框架生成java对象。
    • AOP代理方法 = advice + 目标对象的方法。

    3.操作

    (1)目录结构

     

     (2)随意做一个业务层

    package com.example.javabaisc.service;
    
    public interface FoodService {
        public String food();
    }
    接口
    package com.example.javabaisc.service.serviceImpl;
    
    
    import com.example.javabaisc.service.FoodService;
    import org.springframework.stereotype.Service;
    
    @Service
    public class FoodServiceImpl implements FoodService {
        @Override
        public String food() {
            System.out.println("=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy");
            System.out.println("吃什么");
            System.out.println("=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy");
            return "apple and tea";
        }
    }
    实现类

    (3)做一个controller层接口

    package com.example.javabaisc.controller;
    
    
    import com.example.javabaisc.service.FoodService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class VVController {
    
        @Autowired
        private FoodService foodService;
    
        @RequestMapping("/")
        public String gg(int id) {
    
            if (id == 1) {
                // 强制抛出异常
                throw new RuntimeException();
            } else {
                return foodService.food();
            }
    
        }
    
    }
    View Code

    这已经完整了一个功能业务了,

    AOP面向切面编程 不会影响业务程序的执行编码,但是可以影响输入参数与返回结果,因此controller和service该干嘛就干嘛,不会因为

    有了AOP就需要其他特别的改动,当然,一些辅助操作可以放在切面 完成,如日志、发短信、发邮件等。【如果是分布式可以使用消息中间件啦】

    (4)现在需要配置 aspect 【面向切面编程】类

    package com.example.javabaisc.aspect;
    
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    
    //aop切面注解,作用是把当前类标识为一个切面,供容器读取
    @Aspect
    @Component
    //定义切面执行顺序,如果在同一个方法有多个切面,那么需要定义使用顺序,先开始直送的切面将最后结束,呈包含关系
    @Order(1)
    public class myAspect {
    
    
        //定义一个公用方法
    //    切入点,表示切入的点,即程序中通用的逻辑业务,这里是请求的路径
        //
        //参数意思是 公共的 任意返回类型 该类里的所有方法 任意输入参数
        //参数public可要可不要
        @Pointcut("execution(public * com.example.javabaisc.controller.VVController.*(..))")
        public void log() {
        }
    
    
        //前置通知  ,表示当前方法是在具体的请求方法之前执行  【权限控制(权限不足抛出异常)、记录方法调用信息日志 】
        @Before("log()")
        public void doBefore(JoinPoint joinPoint) {
            System.out.println("====================");
            System.out.println("前置通知---before");
            System.out.println("====================");
            //如果是controller层 可使用这两句获取 参数 ,当然如果是restful 参数得自己解析路径
    //        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    //        HttpServletRequest request = attributes.getRequest();
            //
            //获取请求的url
    //        String url = request.getRequestURI();
            //
            //获取请求的方法
    //        String method =request.getMethod();
            //
            //获取请求的ip
    //        String ip = request.getRemoteAddr();
            //
            //获取请求参数,返回来的是个object 数组
    //        Object[] args = joinPoint.getArgs();
            //也可以转成 list
    //        List<Object> args = Arrays.asList(joinPoint.getArgs());
            //
            //获取该切入点所在方法的路径,getDeclaringTypeName()获取类名,joinPoint.getSignature().getName()获取方法名
    //        String ss = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
    //        System.out.println(ss);
            //打印com.example.javabaisc.controller.VVController.gg
            //
            //可以获取目标class
    //        Class clazz = joinPoint.getSignature().getDeclaringType();
    
    
        }
    
        //异常通知 :在目标方法出现异常时才会进行执行的代码。【处理异常(一般不可预知),记录日志 】
        //方法体Exception参数:用来接收连接点抛出的异常。Exception类匹配所有的异常,可以指定为特定的异常 例如NullPointerException类等
        @AfterThrowing(pointcut = "log()", throwing = "ex")
        public void throwss(JoinPoint joinPoint, Exception ex) {
            System.out.println("====================");
            System.out.println("异常通知.....afterthrowing");
            System.out.println("====================");
        }
    
    
        //返回通知: 在目标方法正常结束时,才执行的通知    【 如银行在存取款结束后的发送短信消息 】
        @AfterReturning(returning = "obj", pointcut = "log()")
        public void doAfterReturnig(JoinPoint joinPoint, Object obj) {
            //处理完请求,返回内容
            System.out.println("====================");
            System.out.println("返回通知,reponse参数:");
            System.out.println(obj);
            System.out.println("返回通知---afterreturening");
            System.out.println("====================");
        }
    
    
        //后置通知,不管是抛出异常或者正常退出都会执行 ,都会执行(类似于finally代码功能)  【释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 】
        @After("log()")
        public void doAfter(JoinPoint joinPoint) {
            System.out.println("====================");
            System.out.println("后置通知---adfer");
            System.out.println("====================");
        }
    
        //环绕通知,环绕增强  【日志、缓存、权限、性能监控、事务管理 】
        @Around("log()")
        public Object arround(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("====================");
            System.out.println("环绕通知--开始--在前置通知之前");
            Object result = null;
            //获取参数
    //        String methodName = pjp.getSignature().getName();
    //        List<Object> args = Arrays.asList(pjp.getArgs());
    //        try {
                //获取结果
                result = pjp.proceed();
                System.out.println("环绕通知around,结果是 :" + result);
                System.out.println("环绕通知------在方法结束之后");
                System.out.println("====================");
    
    //        } catch (Throwable e) {
    //            System.out.println("环绕通知---在环绕通知的方法里的 异常捕获 导致服务降级,不再触发异常通知方法,也就是说如果需要使用环绕通知,不允许在这里使用 catch ,可使用throws Throwable即可");
    //            e.printStackTrace();
    //        }
            System.out.println("环绕通知---在后置通知之前  ");
            return result;
        }
    }
    View Code

    关于切入点公共方法 注解里面的参数 说明:

     public可要可不要

    ==============================================================================================

    ==============================================================================================

      想象不到吧 ,spring boot搭建 aspect 竟然一个配置类就足够了,

    回想一下 spring MVC ,不仅需要配置xml,还要配置相应的通知方法 ,

    简直就是暗无天日的样子。

    ==============================================================================================

    ==============================================================================================

    3.测试

    (1)启动spring boot ,测试正常执行业务,查看控制台打印结果

    网址访问 http://localhost:8080/?id=22

    (2)测试抛出异常,,查看控制台打印结果

    网址访问 http://localhost:8080/?id=1

     (3)注意,千万不要在环绕通知使用异常捕获 catch ,否则会导致服务降级,异常通知不会执行,

    而返回通知仍会执行,返回结果会是null。

    否则会出现下图的打印顺序

  • 相关阅读:
    SQL——with as 临时表
    SQL 在数据库中查找拥有此列名的所有表
    帆软报表(finereport)鼠标悬停背景变色
    帆软报表(finereport)控件背景色更改
    帆软报表(finereport)使用Event 事件对象 (target)修改提示框样式
    微信indexOf不能使用,代替方式
    基础知识
    VUE知识点
    银行金额处理
    flex-1
  • 原文地址:https://www.cnblogs.com/c2g5201314/p/13097184.html
Copyright © 2011-2022 走看看