SpringBoot AOP学习(二):Spring AOP实现日志功能

·  阅读 504
SpringBoot AOP学习(二):Spring AOP实现日志功能

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

前言

感谢阅读菜菜的文章,本篇文章是继上一篇 SpringBoot AOP学习(一):AOP的诞生及AOP注解介绍后对AOP注解的使用作一个具体的应用,由于本身我也是才接触不久,借此机会把自己的学习心得记录下来,也希望各位大佬不吝赐教~ 在这里插入图片描述 为了学起来更加得心应手,这里简单复习了下IOC:

IOC理论,用来实现对象之间的“解耦”,解决对象之间的耦合度过高的问题。IOC(控制反转)的具体实现是通过借助于“第三方”实现具有依赖关系的对象之间的解耦,这个“第三方”就是IOC容器;同时,IOC也叫依赖注入(DI),那么这两种叫法的区别是什么,且看: 控制反转:获得依赖对象的过程被反转了,从前主动,现在被动 依赖注入:就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中 他们两兄弟是同一个东西从不同的角度来进行描述的,IOC中最基本的技术就是“反射(Reflection)”编程

AOP日志功能实战

菜菜接触一个项目要实现日志功能,需求如下:

1、记录操作人、操作时间 2、记录request请求参数 3、记录response回调数据 4、记录具体的业务描述供系统使用者查看(这里需要自定义注解)

在这里插入图片描述

案例代码结构

为了方便,菜菜将所有代码放在了同一包内 在这里插入图片描述

重点就是这个Aspect切面类 开始实现 首先, 在这里插入图片描述 先定义实体类和controller类

ReqDTO.java

package com.caicai.aop.csdn;


import lombok.Data;

@Data
public class ReqDTO {

    private String user_id;

    private String user_name;
}

复制代码

TestController.java

package com.caicai.aop.csdn;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/aop")
public class TestController {

        @RequestMapping(path = "/test", method = RequestMethod.POST)
        @MyLog(operateType = "Log测试", operateExplain = "模拟日志记录")  //这里使用的自定义注解
        public String test(@RequestBody ReqDTO reqDTO) {
//            int i = 1 / 0;   //模拟异常
            System.out.println("调用 Log测试 方法");
            return "调用 Log测试 方法 end" ;
        }
    }

复制代码

然后是定义自定义注解

MyLog .java

package com.caicai.aop.csdn;

import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface MyLog {
    /** 操作类型 **/
    String operateType();
    /** 操作解释 **/
    String operateExplain();
}

复制代码

最后,定义我们的切面 在这里插入图片描述

要想把一个类变成切面类,需要两步,

第一步,在类上使用 @Component 注解 把切面类加入到IOC容器中 第二步,在类上使用 @Aspect 注解 使之成为切面类

TestAspect .java

package com.caicai.aop.csdn;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @Aspect 切面类注解实例
 * @author 菜菜bu菜
 */


//声明这是一个组件
@Component
//声明这是一个切面Bean
@Aspect
public class TestAspect {


    //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
    //@annotation表示标注了某个注解的所有方法
    @Pointcut("@annotation(com.caicai.aop.csdn.MyLog)")
    public void aspect(){   }


    //配置前置通知,使用在方法aspect()上注册的切入点
    //同时接受JoinPoint切入点对象,可以没有该参数
  
    @Before("aspect()")
    public void Before(){
        System.out.println("---------Before方法开始执行");
    }

    //配置后置通知,使用在方法aspect()上注册的切入点
    @After("aspect()")
    public void After(JoinPoint joinPoint){
        System.out.println("---------After方法开始执行");
    }

    //最终通知
    //returning能够将目标方法的返回值传到切面增强方法里
    //声明rvt时指定的类型会限制目标方法必须返回指定类型(String)的值或没有返回值
    //此处将rvt的类型声明为Object,意味着对目标方法的返回值不加限制
    @AfterReturning(pointcut ="aspect()",returning = "rvt")
    public void AfterReturning(String rvt){
        System.out.println("--------AfterReturning方法开始执行:---"+rvt);
    }

    //异常通知
    //声明e时指定的类型会限制目标方法必须抛出指定类型的异常
    //此处将e的类型声明为Throwable,意味着对目标方法抛出的异常不加限制
    @AfterThrowing(pointcut="aspect()",throwing="e")
    public void AfterThrowing(Throwable e){
        System.out.println("--------AfterThrowing方法开始执行:"+e);
    }


    //@Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
    //功能很强大,可以深入了解下
    @Around("aspect()")
    public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("--------Around方法开始执行");
        //获取自定义注解里面的值
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        MyLog logAnnotation = (MyLog)method.getAnnotation(MyLog.class);
        System.err.println("operateType:------"+logAnnotation.operateType());
        System.err.println("operateExplain:------"+logAnnotation.operateExplain());

        //获取入参
        Object[] objs = joinPoint.getArgs();
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); // 参数名
        Map<String, Object> paramMap = new HashMap<String, Object>();
        for (int i = 0; i < objs.length; i++) {
            paramMap.put(argNames[i], objs[i]);
        }
        System.err.println("入参:"+paramMap.toString());

        //获取出参
        Object result =joinPoint.proceed();
        System.err.println("出参:"+result.toString());
        return result;

    }

}

复制代码

测试

正常测试

在这里插入图片描述 结果:

--------Around方法开始执行
---------Before方法开始执行
调用 Log测试 方法
--------AfterReturning方法开始执行:---调用 Log测试 方法 end
---------After方法开始执行
operateType:------Log测试
operateExplain:------模拟日志记录
入参:{reqDTO=ReqDTO(user_id=1234, user_name=3423)}
出参:调用 Log测试 方法 end
复制代码

异常测试

在TestController.java下test方法里面加上 int i = 1 / 0; //模拟异常

  @RequestMapping(path = "/test", method = RequestMethod.POST)
        @MyLog(operateType = "Log测试:", operateExplain = "模拟日志记录")  //这里使用的自定义注解
        public String test(@RequestBody  ReqDTO reqDTO) {
            int i = 1 / 0;   //模拟异常
            System.out.println("调用 Log测试 方法");
            return "调用 Log测试 方法 end" ;
        }
复制代码

再次发送请求 在这里插入图片描述 结果:

--------Around方法开始执行
---------Before方法开始执行
--------AfterThrowing方法开始执行:java.lang.ArithmeticException: / by zero
---------After方法开始执行
operateType:------Log测试
operateExplain:------模拟日志记录
入参:{reqDTO=ReqDTO(user_id=1234, user_name=3423)}
复制代码

总结

AOP真真真强啊!面向切面编程(aop)是对面向对象编程(oop)的补充,面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。

实现AOP的技术,主要分为两大类

  1. 采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
  2. 采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。

AOP框架具有的两个特征

  1. 各个步骤之间的良好隔离性
  2. 源代码无关性

AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象。 在这里插入图片描述 最后,对于实际应用中 @Around的应用非常广泛,下一篇来具体学习一下它~

分类:
后端
标签:
分类:
后端
标签: