史上最简! 注解+AOP实现记录日志还能这样玩!!!

本文通过一个简单的SpringBoot项目,演示了如何使用AOP和自定义注解实现方法调用前后的日志记录以及参数和返回值的修改。详细介绍了注解的声明、切面的创建以及环绕通知的使用,展示了AOP在实际应用中的功能。
摘要由CSDN通过智能技术生成

前言

在这里插入图片描述

  四月已经到中旬了,昨天有粉丝小伙伴向我询问AOP实现记录操作日志相关方面的问题,希望我写篇这方面的博客,话不多说,今天抽空立刻安排,希望能帮助到其它也有需要的朋友~
  本次只阐述原理,用控制台输出的形式展示,没用插入到数据库的表中,可以将输出语句换成插入操作,将记录插入到日志表中~

一、创建简单的springboot项目

1、项目结构

在这里插入图片描述

2、配置pom.xml与properties文件

  在新建的springboot项目pom.xml中添加下面需要用到的2种依赖

 <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--  AOP  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

  在properties文件中添加访问端口

server.port=8080

3、各个类具体内容

  User

package com.nanyi.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * @Description:User实体
 * @Author nanyi
 * @Date 2021/4/13 20:03
 **/
@Data
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
}

  UserService

package com.nanyi.service;

import com.nanyi.entity.User;
import com.nanyi.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Description:类描述
 * @Author nanyi
 * @Date 2021/4/13 20:03
 **/
@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    public User findUserById(Integer id) {
        return userMapper.findUserById(id);
    }
}

  UserMapper

package com.nanyi.mapper;

import com.nanyi.entity.User;
import org.springframework.stereotype.Component;

/**
 * @Description:类描述
 * @Author nanyi
 * @Date 2021/4/13 20:03
 **/
@Component
public class UserMapper {

    public User findUserById(Integer id) {
        if (id > 5) {
            return null;
        }
        return new User(id, "user--" + id);
    }
}

  UserController

package com.nanyi.controller;

import com.nanyi.aop.Log;
import com.nanyi.entity.User;
import com.nanyi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description:类描述
 * @Author nanyi
 * @Date 2021/4/13 20:03
 **/
@RestController
public class UserController {
    @Autowired
    UserService userService;

    @GetMapping("user/{id}")
    public User findUser(@PathVariable("id") Integer id) {
        return userService.findUserById(id);
    }
}

二、使用注解执行固定的操作

  现在我们已经有了这样的一个简单的web项目了,直接访问localhost:8080/user/4后,显然会得到一个如下的json串

{"id":4,"name":"user--4"}

  现在我们就通过自定义注解来为它增加一个日志的功能。
  假设我们现在的目的是,在调用controller中的findUser方法前,先在控制台输出一句话。好了那就开始做吧,我们先创建一个aop包,里面创建我们自定义的注解类Log:

package com.nanyi.aop;

import java.lang.annotation.*;

/**
 * @Description:自定义注解
 * @Author nanyi
 * @Date 2021/4/13 20:03
 **/
@Target(ElementType.METHOD) // 元注解:作用在方法上
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,表示注解会被保留到什么阶段,此处为运行阶段
@Documented
public @interface Log {
    String operModul() default ""; // 操作模块

    String value() default "";// 内容

}

  这里注解类上的三个注解称为元注解,其分别代表的含义如下:

  @Documented:注解信息会被添加到Java文档中
  @Retention:注解的生命周期,表示注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运行阶段
  @Target:注解作用的位置,ElementType.METHOD表示该注解仅能作用于方法上
  然后我们可以把注解添加到方法上:

@RestController
public class UserController {
    @Autowired
    UserService userService;

    @Log(value = "这是日志内容", operModul = "用户查询模块")
    @GetMapping("user/{id}")
    public User findUser(@PathVariable("id") Integer id) {
        return userService.findUserById(id);
    }
}

  这个注解目前是没有任何作用的,因为我们仅仅是对注解进行了声明,并没有在任何地方来使用这个注解,注解的本质也是一种广义的语法糖,最终还是要利用Java的反射来进行操作,不过Java给我们提供了一个AOP机制,可以对类或方法进行动态的扩展,
我们创建切面类:

package com.nanyi.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @Description:日志切面类
 * @Author nanyi
 * @Date 2021/4/13 20:04
 **/
@Component
@Aspect
public class LogAspect {
    @Pointcut("@annotation(com.nanyi.aop.Log)")
    private void pointcut() {
    }

    @Before("pointcut() && @annotation(logger)")
    public void advice(JoinPoint joinPoint, Log logger) {
//        从切面切入点通过反射机制获取切入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//        获取切入点所在的方法
        Method method = signature.getMethod();
//        获取注解中定义的各个属性
        Log log = method.getAnnotation(Log.class);
//        打印内容,此处可以换成插入操作,将需要的日志内容插入到数据库中
        System.out.println(log.value() + "-----------" + log.operModul());

        System.out.println("注解作用的方法名: " + method.getName());

        System.out.println("所在类的简单类名: " + signature.getDeclaringType().getSimpleName());

        System.out.println("所在类的完整类名: " + signature.getDeclaringType());

        System.out.println("目标方法的声明类型: " + Modifier.toString(signature.getModifiers()));
    }
}

  其中@Pointcut声明了切点(这里的切点是我们自定义的注解类),@Before声明了通知内容,在具体的通知中,我们通过@annotation(logger)拿到了自定义的注解对象,所以就能够获取我们在使用注解时赋予的值了。这里如果对于切点和通知等概念不了解的,建议先去查阅一些aop的知识再回来看本文较好,本文更注重于实践,而不是概念的讲解,然后我们现在再来启动web服务,在浏览器上输入localhost:8080/user/4:
在这里插入图片描述

三、使用注解修改参数和返回值

  现在我们的注解需要对方法的参数作出修改,以findUser()方法为例,假设我们传入的用户id是从1开始计数,后端则是从0开始计数,我们的@KthLog注解的开发者喜欢“多管闲事”,想要帮助其他人减轻一点压力,那该怎么做呢?
  在这个应用场景中,我们需要做的有两件事:将传入的id减1,给返回的user类中的id加1。这就涉及到如何拿到参数的问题。因为我们需要管理方法执行前和执行后的操作,所以我们使用@Around环绕注解,如下:

    @Around("pointcut() && @annotation(logger)")
    public Object advice(ProceedingJoinPoint joinPoint, Log logger) {
         System.out.println(log.value() + "-----------" + log.operModul());
    
        Object result = null;
        
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }

  这里除了将@Before改为@Around之外,还将参数中的JoinPoint改为了ProceedingJoinPoint,不过不用担心,JoinPoint能做的ProceedingJoinPoint都能做。这里通过调用proceed()方法,执行了实际的操作,并获取到了返回值,那么接下来对于返回值的操作相信就不用我再多说了,现在问题就是如何获取到参数

  ProceedingJoinPoint继承了JoinPoint接口,在JoinPoint中,存在一个getArgs()方法,用于获取方法参数,返回的是一个Object数组,与之匹配的则是proceed(args)方法,这两个方法结合起来,就能够实现我们的目的:

    @Around("pointcut() && @annotation(logger)")
    public Object advice(ProceedingJoinPoint joinPoint, KthLog logger) {
          System.out.println(log.value() + "-----------" + log.operModul());

        Object result = null;

        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            if(args[i] instanceof Integer) {
                args[i] = (Integer)args[i] - 1;
                break;
            }
        }

        try {
            result = joinPoint.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        if(result instanceof User) {
            User user = (User) result;
            user.setId(user.getId() + 1);
            return user;
        }
        return result;
    }

  这里为了让结果更明显,我们在UserDao处添加一些输出,来显示实际执行的参数和返回的值各自是什么:

@Component
public class UserDao {

    public User findUserById(Integer id) {
        System.out.println("查询id为[" + id + "]的用户");
        if(id > 5) {
            return null;
        }
        User user = new User(id, "user-" + id);
        System.out.println("返回的用户为[" + user.toString() + "]");
        return user;
    }
}

  现在我们访问http://localhost:8080/user/4,来看控制台打印的结果:

这是日志内容-----------用户查询模块
注解作用的方法名: findUser
所在类的简单类名: UserController
所在类的完整类名: class com.nanyi.controller.UserController
目标方法的声明类型: public
查询id为[3]的用户
返回的用户为[User(id=3, name=user--3)]

  我们发现在url上输入的4,在后端被转换成了3,最终查询的用户也是id为3的用户,说明我们参数转换成功了,然后我们来看浏览器得到的响应结果:
在这里插入图片描述
  返回的用户id是4,而不是后端查询的3,说明我们对返回值的修改也成功了

四、总结

  成为一个小胖子,没事摸摸小肚子~

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值