【Spring Boot 快速入门】二十、Spring Boot 基于AOP注解实现日志记录功能

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【Spring Boot 快速入门】二十、Spring Boot 基于AOP注解实现日志记录功能

前言


  在很多后台管理系统中,有明确的权限和角色的管控,当然也少不了操作日志的记录。本文将基于Spring 的AOP特性开发一个日志记录功能。下面记录一下整个开发工程


快速开始


  使用Spring的AOP特性,首先了解AOP是什么,AOP在程序开发过程中是指面向切面编程,通过预编译和动态代理实现程序功能。AOP中主要有切点、切面、连接点、目标群、通知、织入方式等。通知类型常用的有前置通知、环绕通知、后置通知等,在日志记录的过程中一般使用环绕通知。具体的AOP的相关概念大家不熟悉的可以去查询一下。


版本信息


  本次Spring Boot 基于AOP注解实现日志记录功能,主要版本信息如下:


Spring Boot 2.3.0.RELEASE
aspectjweaver 1.9.6
maven 3

 主要引入的依赖是aspectjweaver,如果aspectjweaver 和Spring Boot 版本不一致,可能会报找不到切点等相关的异常,可以替换位相关版本即可解决。


<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>


基础信息


  实现日志记录功能,主要是将操作日志记录到数据库中或者搜索引擎中,方便查询。在日志中需要记录操作人、操作时间、请求的参数、请求的ip、请求的连接、操作类型等信息。本次示例的建表SQL如下:


CREATE TABLE `log_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `model` varchar(255) DEFAULT NULL COMMENT '模块',
  `log_type` tinyint(4) DEFAULT NULL COMMENT '类型0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=登录,9=清空数据,10查询',
  `url` varchar(255) DEFAULT NULL COMMENT '请求链接',
  `method` varchar(255) DEFAULT NULL COMMENT '请求方法',
  `class_name` varchar(255) DEFAULT NULL COMMENT '类名',
  `method_name` varchar(255) DEFAULT NULL COMMENT '方法名',
  `params` varchar(500) DEFAULT NULL COMMENT '请求参数',
  `ip` varchar(255) DEFAULT NULL COMMENT 'ip地址',
  `user_id` int(11) DEFAULT NULL COMMENT '操作人id',
  `user_name` varchar(255) DEFAULT NULL COMMENT '操作人',
  `sys_info` varchar(255) DEFAULT NULL COMMENT '系统信息',
  `create_user` varchar(200) CHARACTER SET utf8 DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_user` varchar(200) CHARACTER SET utf8 DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `data_state` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0 删除   1未删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

 本次将操作类型分为11类包登录、退出、增删改查、导入导出、清空等相关日志操作类别。


@Getter
@AllArgsConstructor
public enum LogTypeEnum {
    /**
     * 0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=登录,9=清空数据,10查询
     * */
    OTHER(0,"其它"),
    ADD(1,"新增"),
    UPDATE(2,"修改"),
    DEL(3,"删除"),
    AUTH(4,"授权"),
    EXPORT(5,"导出"),
    IMPORT(6,"导入"),
    QUIT(7,"强退"),
    GENERATE_CODE(8,"登录"),
    CLEAR(9,"清空"),
    QUERY(10,"查询"),
    ;
    @EnumValue
    private int value;
    private String desc;
}


Log注解


  当数据初始化完成之后,就是编写一个自定义的Log注解。本次使用的注解主要是针对方法进行注解。具体如下:

  • @Target:注解的目标位置,主要可以有接口、类、枚举、字段、方法、构造函数、包等位置。可以根据需要进行配置。日志使用的本次基于方法注解。
  • @Retention:是指注解保留的位置,可以在源码中、类中、运行中。本次日志操作记录肯定是在运行中使用,所以选择RUNTIME。
  • @Documented:字面意思文档,也就是说明该注解将被包含在javadoc中。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**
     * 模块
     * */
    String model() default "";
    /**
     * 操作
     * */
    LogTypeEnum logType() default LogTypeEnum.OTHER;
}


LogAspect


  定义一个日志的LogAspect切面。在类中需要使用@Aspect和 @Component指明这是一个切面方法在运行中进行扫描包。需要注意的是这个方法中需要定义切面和通知类型。最后根据注解和请求参数中的信息,查询到操作日志需要的信息。由于本次无需登录直接接口请求,所以操作人和操作id在演示中使用了默认值。本次示例只提取了部分操作日志信息,在项目中需要加入的日志信息多,可以根据需求进行修改。


@Aspect
@Component
public class LogAspect {
    @Resource
    private LogInfoMapper logInfoMapper;
    /**
     * @ClassName logPointCut
     * @Description:切点信息
     * @Author JavaZhan @公众号:Java全栈架构师
     * @Version V1.0
     **/
    @Pointcut("@annotation(com.example.demo.log.Log)")
    public void logPointCut(){
    }
    /**
     * @ClassName aroundForLog
     * @Description:环绕通知
     * @Author JavaZhan @公众号:Java全栈架构师
     * @Version V1.0
     **/
    @Around("logPointCut()")
    public Object aroundForLog(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = point.proceed();
        saveSmsLog(point);
        return result;
    }
    /**
     * @ClassName saveLogInfo
     * @Description: 保存操作日志
     * @Author JavaZhan @公众号:Java全栈架构师
     * @Version V1.0
     **/
    private void saveLogInfo(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogInfo logInfo = new LogInfo();
        logInfo.setClassName(joinPoint.getTarget().getClass().getName());
        logInfo.setMethodName(signature.getName());
        Log log = method.getAnnotation(Log.class);
        if(log != null){
            logInfo.setModel(log.model());
            logInfo.setLogType(log.logType().getValue());
        }
        Object[] args = joinPoint.getArgs();
        try{
            String params = JSONObject.toJSONString(args);
            logInfo.setParams(params);
        }catch (Exception e){
        }
        ServletRequestAttributes servletRequestAttributes =   (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        logInfo.setIp(IpUtils.getIpAddr(request));
        logInfo.setUrl(request.getServletPath());
        logInfo.setMethod(request.getMethod());
        logInfo.setUserId(123);
        logInfo.setUserName("admin");
        logInfo.setCreateTime(new Date());
        logInfo.setDataState(1);
        //保存操作日志
        logInfoMapper.insert(logInfo);
    }
}


测试日志


  本示例将基于常用的接口进行测试。在Controller方法中,我们调用自定义注解,根据方法的实际使用含义指定方法的model信息和操作类型信息即可。


@RequestMapping("user")
@Controller
public class UserController {
    @Resource
    private UserService userService;
    @RequestMapping("getAllUser")
    @ResponseBody
    @Log(model = "查询用户列表",logType = LogTypeEnum.QUERY)
    public List<User> getAllUser(){
        return userService.getAllUser();
    }
    @RequestMapping("getUserById")
    @ResponseBody
    @Log(model = "获取指定用户",logType = LogTypeEnum.QUERY)
    public User getUserById(Integer id ){
        return userService.getById(id);
    }
}


调用接口:http://127.0.0.1:8888/user/getAllUser 返回的数据信息如下。


image.png


查看日志表中,可以看到已经新增一条查询用户列表的日志信息。


image.png


下面访问其他接口:http://127.0.0.1:8888/user/getUserById?id=1 返回的数据信息如下。


image.png


查看日志表中,可以看到已经新增一条获取指定用户的日志信息。


image.png


结语


  好了,以上就是Spring Boot 基于AOP注解实现日志记录功能的示例



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
2月前
|
XML Java 数据格式
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
本文介绍了如何使用Spring框架的注解方式实现AOP(面向切面编程)。当目标对象没有实现接口时,Spring会自动采用CGLIB库进行动态代理。文中详细解释了常用的AOP注解,如`@Aspect`、`@Pointcut`、`@Before`等,并提供了完整的示例代码,包括业务逻辑类`User`、配置类`SpringConfiguration`、切面类`LoggingAspect`以及测试类`TestAnnotationConfig`。通过这些示例,展示了如何在方法执行前后添加日志记录等切面逻辑。
177 2
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
|
29天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
38 1
|
2月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
76 2
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
118 1
|
2月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
29 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
2月前
|
Java Spring
springboot 学习十一:Spring Boot 优雅的集成 Lombok
这篇文章是关于如何在Spring Boot项目中集成Lombok,以简化JavaBean的编写,避免冗余代码,并提供了相关的配置步骤和常用注解的介绍。
105 0
|
24天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
176 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
2月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
256 3
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1655 14
|
2月前
|
Python
log日志学习
【10月更文挑战第9天】 python处理log打印模块log的使用和介绍
35 0