这篇文章主要介绍“SpringBoot怎么通过自定义注解与异步来管理日志”,在日常操作中,相信很多人在SpringBoot怎么通过自定义注解与异步来管理日志问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”SpringBoot怎么通过自定义注解与异步来管理日志”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
一、前言
我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,同时考虑到程序的流畅和效率,我们可以使用异步进行保存!
二、基础环境
1. 导入依赖
我这里的springboot版本是:2.7.4
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot-log</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-log</name> <description>springboot-log 日志 Demo</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.16</version> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
2. 编写yml配置
server:
port: 8080spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
druid:
initial-size: 5
max-active: 100
min-idle: 5
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: true
test-on-return: false
三、数据库设计
数据库保存日志表的设计,这里一切从简,一般日志多的后期会进行分库分表,或者搭配ELK进行分析,分库分表一般采用根据方法类型!
DROP TABLE IF EXISTS `sys_log`;CREATE TABLE `sys_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键', `title` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '模块', `business_type` int(2) NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', `method` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '方法名称', `request_method` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '请求方式', `oper_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '操作人员', `oper_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '请求URL', `oper_ip` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '主机地址', `oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1585197503834284034 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;
实体类:
package com.example.springbootlog.entity;import java.util.Date;import java.io.Serializable;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import com.fasterxml.jackson.annotation.JsonFormat;import lombok.Data;import lombok.ToString;@Data@ToString@TableName("sys_log")public class SysLog implements Serializable { private static final long serialVersionUID = 811241510868515068L; @TableId private Long id; private String title; private Integer businessType; private String method; private String requestMethod; private String operName; private String operUrl; private String operIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date operTime;}
四、主要功能
大体思路:
先手写一个注解
切面来进行获取要保存的数据
一个发布者来发布要保存的数据
一个监听者监听后保存(异步)
完整项目架构图如下:
1. 编写注解
package com.example.springbootlog.annotation;import com.example.springbootlog.constant.BusinessTypeEnum;import java.lang.annotation.*;@Target(ElementType.METHOD) // 注解只能用于方法@Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期@Documentedpublic @interface Log { String value() default ""; String title() default "测试模块"; String method() default "测试方法"; BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;}
2. 业务类型枚举
package com.example.springbootlog.constant;public enum BusinessTypeEnum { OTHER(0,"其它"), INSERT(1,"新增"), UPDATE(2,"修改"), DELETE(3,"删除"); private Integer code; private String message; BusinessTypeEnum(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public String getMessage() { return message; }}
3. 编写切片
这里是以切片后进行发起的,当然规范流程是要加异常后的切片,这里以最简单的进行测试哈,大家按需进行添加!!
package com.example.springbootlog.aspect;import com.example.springbootlog.annotation.Log;import com.example.springbootlog.entity.SysLog;import com.example.springbootlog.listener.EventPubListener;import com.example.springbootlog.utils.IpUtils;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.util.Date;@Aspect@Componentpublic class SysLogAspect { private final Logger logger = LoggerFactory.getLogger(SysLogAspect.class); @Autowired private EventPubListener eventPubListener; @Pointcut("@annotation(com.example.springbootlog.annotation.Log)") public void sysLog() {} @After("sysLog()") public void doAfter(JoinPoint joinPoint) { Log log = ((MethodSignature) joinPoint.getSignature()).getMethod() .getAnnotation(Log.class); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String method = request.getMethod(); String url = request.getRequestURL().toString(); String ip = IpUtils.getIpAddr(request); SysLog sysLog = new SysLog(); sysLog.setBusinessType(log.businessType().getCode()); sysLog.setTitle(log.title()); sysLog.setMethod(log.method()); sysLog.setRequestMethod(method); sysLog.setOperIp(ip); sysLog.setOperUrl(url); // 从登录中token获取登录人员信息即可 sysLog.setOperName("我是测试人员"); sysLog.setOperTime(new Date()); // 发布消息 eventPubListener.pushListener(sysLog); logger.info("=======日志发送成功,内容:{}",sysLog); }}
4. ip工具类
package com.example.springbootlog.utils;import com.baomidou.mybatisplus.core.toolkit.StringUtils;import javax.servlet.http.HttpServletRequest;public class IpUtils { public static String getIpAddr(HttpServletRequest request) { if (request == null) { return "unknown"; } String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); } public static String getMultistageReverseProxyIp(String ip) { // 多级反向代理检测 if (ip != null && ip.indexOf(",") > 0) { final String[] ips = ip.trim().split(","); for (String subIp : ips) { if (false == isUnknown(subIp)) { ip = subIp; break; } } } return ip; } public static boolean isUnknown(String checkString) { return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); }}
5. 事件发布
事件发布是由ApplicationContext对象进行发布的,直接注入使用即可!
使用观察者模式的目的:为了业务逻辑之间的解耦,提高可扩展性。
这种模式在spring和springboot底层是经常出现的,大家可以去看看。
发布者只需要关注发布消息,监听者只需要监听自己需要的,不管谁发的,符合自己监听条件即可。
package com.example.springbootlog.listener;import com.example.springbootlog.entity.SysLog;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.stereotype.Component;@Componentpublic class EventPubListener { @Autowired private ApplicationContext applicationContext; public void pushListener(SysLog sysLogEvent) { applicationContext.publishEvent(sysLogEvent); }}
6. 监听者
@Async:单独开启一个新线程去保存,提高效率!
@EventListener:监听
package com.example.springbootlog.listener;import com.example.springbootlog.entity.SysLog;import com.example.springbootlog.service.SysLogService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.event.EventListener;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;@Slf4j@Componentpublic class MyEventListener { @Autowired private SysLogService sysLogService; // 开启异步 @Async // 开启监听 @EventListener(SysLog.class) public void saveSysLog(SysLog event) { log.info("=====即将异步保存到数据库======"); sysLogService.saveLog(event); }}
五、测试
1. controller
package com.example.springbootlog.controller;import com.example.springbootlog.annotation.Log;import com.example.springbootlog.constant.BusinessTypeEnum;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.*;@Slf4j@RestController@RequestMapping("sysLog")public class SysLogController { @Log(method = "测试添加方法", title = "测试呢", businessType = BusinessTypeEnum.INSERT) @GetMapping("/saveLog") public void saveLog() { log.info("我就是来测试一下是否成功!"); }}
2. service
package com.example.springbootlog.service;import com.example.springbootlog.entity.SysLog;import com.baomidou.mybatisplus.extension.service.IService;public interface SysLogService extends IService<SysLog> { Integer saveLog(SysLog sysLog);}
package com.example.springbootlog.service.impl;import com.example.springbootlog.entity.SysLog;import com.example.springbootlog.service.SysLogService;import com.example.springbootlog.dao.SysLogDao;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service("sysLogService")public class SysLogServiceImpl extends ServiceImpl<SysLogDao, SysLog> implements SysLogService { @Autowired private SysLogDao sysLogDao; @Override public Integer saveLog(SysLog sysLog) { return sysLogDao.insert(sysLog); }}
3. dao
这里使用mybatis-plus进行保存
package com.example.springbootlog.dao;import com.example.springbootlog.entity.SysLog;import com.baomidou.mybatisplus.core.mapper.BaseMapper;public interface SysLogDao extends BaseMapper<SysLog> {}
4. 测试
访问地址:http://localhost:8080/sysLog/saveLog
5. 数据库
到此,关于“SpringBoot怎么通过自定义注解与异步来管理日志”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!