本篇文章为你整理了SpringBoot自定义注解+异步+观察者模式实现业务日志保存()的详细内容,包含有 SpringBoot自定义注解+异步+观察者模式实现业务日志保存,希望能帮助你了解 SpringBoot自定义注解+异步+观察者模式实现业务日志保存。
我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,但是考虑到程序的流畅和效率,我们可以使用异步进行保存,小编最近在spring和springboot源码中看到有很多的监听处理贯穿前后:这就是著名的观察者模式!!
二、基础环境
项目这里小编就不带大家创建了,直接开始!!
1. 导入依赖
小编这里的springboot版本是:2.7.4
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
2. 编写yml配置
server:
port: 8088
spring:
datasource:
#使用阿里的Druid
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.239.131:3306/test?serverTimezone=UTC
username: root
password: root
三、数据库设计
数据库保存日志表的设计,小编一切从简,一般日志多的后期会进行分库分表,或者搭配ELK进行分析,分库分表一般采用根据方法类型,这需要开发人员遵循rest风格,不然肯定都是post,纯属个人见解哈!!大家可以根据自己的公司的要求进行补充哈!!
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;
实体类:
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
* 操作日志记录表 sys_log
@Data
@TableName("sys_log")
public class SysLog {
private static final long serialVersionUID = 1L;
* 日志主键
@TableId
private Long id;
* 操作模块
private String title;
* 业务类型(0其它 1新增 2修改 3删除)
private Integer businessType;
* 请求方式
private String requestMethod;
* 操作人员
private String operName;
* 请求url
private String operUrl;
* 操作地址
private String operIp;
* 操作时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime operTime;
四、主要功能
大体思路:
先手写一个注解--- 切面来进行获取要保存的数据--- 一个发布者来发布要保存的数据--- 一个监听者监听后保存(异步)
完整项目架构图如下:
1. 编写注解
import com.example.demo.constant.BusinessTypeEnum;
import java.lang.annotation.*;
* 自定义操作日志记录注解
* @author wangzhenjun
* @date 2022/10/26 15:37
@Target(ElementType.METHOD) // 注解只能用于方法
@Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期
@Documented
public @interface Log {
String value() default "";
* 模块
String title() default "测试模块";
* 功能
BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
2. 业务类型枚举
/**
* @author wangzhenjun
* @date 2022/10/26 11:22
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. 编写切片
这里小编是以切片后进行发起的,当然规范流程是要加异常后的切片,这里以最简单的进行测试哈,大家按需进行添加!!
import com.example.demo.annotation.Log;
import com.example.demo.entity.SysLog;
import com.example.demo.listener.EventPubListener;
import com.example.demo.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.time.LocalDateTime;
* @author wangzhenjun
* @date 2022/10/26 15:39
@Aspect
@Component
public class SysLogAspect {
private final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
@Autowired
private EventPubListener eventPubListener;
* 以注解所标注的方法作为切入点
@Pointcut("@annotation(com.example.demo.annotation.Log)")
public void 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.setRequestMethod(method);
sysLog.setOperIp(ip);
sysLog.setOperUrl(url);
// 从登录中token获取登录人员信息即可
sysLog.setOperName("我是测试人员");
sysLog.setOperTime(LocalDateTime.now());
// 发布消息
eventPubListener.pushListener(sysLog);
logger.info("=======日志发送成功,内容:{}",sysLog);
4. ip工具类
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import javax.servlet.http.HttpServletRequest;
* @author wangzhenjun
* @date 2022/10/26 16:27
* 获取IP方法
* @author jw
public class IpUtils {
* 获取客户端IP
* @param request 请求对象
* @return IP地址
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 boolean isUnknown(String checkString) {
return StringUtils.isBlank(checkString) "unknown".equalsIgnoreCase(checkString);
5. 事件发布
事件发布是由ApplicationContext对象进行发布的,直接注入使用即可!
使用观察者模式的目的:为了业务逻辑之间的解耦,提高可扩展性。
这种模式在spring和springboot底层是经常出现的,大家可以去看看。
发布者只需要关注发布消息,监听者只需要监听自己需要的,不管谁发的,符合自己监听条件即可。
import com.example.demo.entity.SysLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
* @author wangzhenjun
* @date 2022/10/26 16:38
@Component
public class EventPubListener {
@Autowired
private ApplicationContext applicationContext;
// 事件发布方法
public void pushListener(SysLog sysLogEvent) {
applicationContext.publishEvent(sysLogEvent);
6. 监听者
@Async:单独开启一个新线程去保存,提高效率!
@EventListener:监听
/**
* @author wangzhenjun
* @date 2022/10/25 15:22
@Slf4j
@Component
public class MyEventListener {
@Autowired
private TestService testService;
// 开启异步
@Async
// 开启监听
@EventListener(SysLog.class)
public void saveSysLog(SysLog event) {
log.info("=====即将异步保存到数据库======");
testService.saveLog(event);
1. controller
/**
* @author wangzhenjun
* @date 2022/10/26 16:51
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Log(title = "测试呢",businessType = BusinessTypeEnum.INSERT)
@GetMapping("/saveLog")
public void saveLog(){
log.info("我就是来测试一下是否成功!");
2. service
/**
* @author wangzhenjun
* @date 2022/10/26 16:55
public interface TestService {
int saveLog(SysLog sysLog);
/**
* @author wangzhenjun
* @date 2022/10/26 16:56
@Service
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
@Override
public int saveLog(SysLog sysLog) {
return testMapper.insert(sysLog);
3. mapper
这里使用mybatis-plus进行保存
/**
* @author wangzhenjun
* @date 2022/10/26 17:07
public interface TestMapper extends BaseMapper SysLog {
4. 测试
5. 数据库
铛铛铛,终于完成了!这个实战在企业级必不可少的,每个项目搭建人不同,但是结果都是一样的,保存日志到数据,这样可以进行按钮的点击进行统计,分析那个功能是否经常使用,那些东西需要优化。只要是有数据的东西,分析一下总会有收获的!后面日志多了就行分库分表,ELK搭建。知道的越多不知道的就越多,这一次下来,知道下面要学什么了嘛!!
可以看下一小编的微信公众号,和网站文章首发看,欢迎关注,一起交流哈!!
![](https://img2022.cnblogs.com/blog/2471401/202210/2471401-20221028085553420-1506161649.jpg)
点击访问!小编自己的网站,里面也是有很多好的文章哦!
以上就是SpringBoot自定义注解+异步+观察者模式实现业务日志保存()的详细内容,想要了解更多 SpringBoot自定义注解+异步+观察者模式实现业务日志保存的内容,请持续关注盛行IT软件开发工作室。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。