您好,登錄后才能下訂單哦!
這篇文章主要介紹“SpringBoot怎么通過自定義注解與異步來管理日志”,在日常操作中,相信很多人在SpringBoot怎么通過自定義注解與異步來管理日志問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”SpringBoot怎么通過自定義注解與異步來管理日志”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
我們在企業(yè)級的開發(fā)中,必不可少的是對日志的記錄,實現(xiàn)有很多種方式,常見的就是基于AOP+注解進(jìn)行保存,同時考慮到程序的流暢和效率,我們可以使用異步進(jìn)行保存!
我這里的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>
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
數(shù)據(jù)庫保存日志表的設(shè)計,這里一切從簡,一般日志多的后期會進(jìn)行分庫分表,或者搭配ELK進(jìn)行分析,分庫分表一般采用根據(jù)方法類型!
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 '模塊標(biāo)題', `business_type` int(2) NULL DEFAULT 0 COMMENT '業(yè)務(wù)類型(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 '主機(jī)地址', `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; /** * 操作日志記錄(SysLog)實體類 * * @author qrxm * @since 2023-03-26 02:09:54 */ @Data @ToString @TableName("sys_log") public class SysLog implements Serializable { private static final long serialVersionUID = 811241510868515068L; /** * 日志主鍵 */ @TableId private Long id; /** * 模塊標(biāo)題 */ private String title; /** * 業(yè)務(wù)類型(0其它 1新增 2修改 3刪除) */ private Integer businessType; /** * 方法名稱 */ private String method; /** * 請求方式 */ private String requestMethod; /** * 操作人員 */ private String operName; /** * 請求URL */ private String operUrl; /** * 主機(jī)地址 */ private String operIp; /** * 操作時間 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date operTime; }
大體思路:
先手寫一個注解
切面來進(jìn)行獲取要保存的數(shù)據(jù)
一個發(fā)布者來發(fā)布要保存的數(shù)據(jù)
一個監(jiān)聽者監(jiān)聽后保存(異步)
完整項目架構(gòu)圖如下:
package com.example.springbootlog.annotation; import com.example.springbootlog.constant.BusinessTypeEnum; import java.lang.annotation.*; /** * 自定義操作日志記錄注解 * @author qrxm */ @Target(ElementType.METHOD) // 注解只能用于方法 @Retention(RetentionPolicy.RUNTIME) // 修飾注解的生命周期 @Documented public @interface Log { String value() default ""; /** * 模塊 */ String title() default "測試模塊"; /** * 方法名稱 */ String method() default "測試方法"; /** * 功能 */ BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER; }
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; } }
這里是以切片后進(jìn)行發(fā)起的,當(dāng)然規(guī)范流程是要加異常后的切片,這里以最簡單的進(jìn)行測試哈,大家按需進(jìn)行添加?。?/p>
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 @Component public class SysLogAspect { private final Logger logger = LoggerFactory.getLogger(SysLogAspect.class); @Autowired private EventPubListener eventPubListener; /** * 以注解所標(biāo)注的方法作為切入點 */ @Pointcut("@annotation(com.example.springbootlog.annotation.Log)") public void sysLog() {} /** * 在切點之后織入 * @throws Throwable */ @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()); // 發(fā)布消息 eventPubListener.pushListener(sysLog); logger.info("=======日志發(fā)送成功,內(nèi)容:{}",sysLog); } }
package com.example.springbootlog.utils; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import javax.servlet.http.HttpServletRequest; 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); } /** * 從多級反向代理中獲得第一個非unknown IP地址 * * @param ip 獲得的IP地址 * @return 第一個非unknown 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; } /** * 檢測給定字符串是否為未知,多用于檢測HTTP請求相關(guān) * * @param checkString 被檢測的字符串 * @return 是否未知 */ public static boolean isUnknown(String checkString) { return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } }
事件發(fā)布是由ApplicationContext對象進(jìn)行發(fā)布的,直接注入使用即可!
使用觀察者模式的目的:為了業(yè)務(wù)邏輯之間的解耦,提高可擴(kuò)展性。
這種模式在spring和springboot底層是經(jīng)常出現(xiàn)的,大家可以去看看。
發(fā)布者只需要關(guān)注發(fā)布消息,監(jiān)聽者只需要監(jiān)聽自己需要的,不管誰發(fā)的,符合自己監(jiān)聽條件即可。
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; @Component public class EventPubListener { @Autowired private ApplicationContext applicationContext; /** * 事件發(fā)布方法 * @param sysLogEvent */ public void pushListener(SysLog sysLogEvent) { applicationContext.publishEvent(sysLogEvent); } }
@Async:單獨開啟一個新線程去保存,提高效率!
@EventListener:監(jiān)聽
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 @Component public class MyEventListener { @Autowired private SysLogService sysLogService; // 開啟異步 @Async // 開啟監(jiān)聽 @EventListener(SysLog.class) public void saveSysLog(SysLog event) { log.info("=====即將異步保存到數(shù)據(jù)庫======"); sysLogService.saveLog(event); } }
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.*; /** * 操作日志記錄(SysLog)表控制層 * */ @Slf4j @RestController @RequestMapping("sysLog") public class SysLogController { @Log(method = "測試添加方法", title = "測試呢", businessType = BusinessTypeEnum.INSERT) @GetMapping("/saveLog") public void saveLog() { log.info("我就是來測試一下是否成功!"); } }
package com.example.springbootlog.service; import com.example.springbootlog.entity.SysLog; import com.baomidou.mybatisplus.extension.service.IService; /** * 操作日志記錄(SysLog)表服務(wù)接口 */ 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; /** * 操作日志記錄(SysLog)表服務(wù)實現(xiàn)類 */ @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); } }
這里使用mybatis-plus進(jìn)行保存
package com.example.springbootlog.dao; import com.example.springbootlog.entity.SysLog; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** * 操作日志記錄(SysLog)表數(shù)據(jù)庫訪問層 */ public interface SysLogDao extends BaseMapper<SysLog> { }
訪問地址:http://localhost:8080/sysLog/saveLog
到此,關(guān)于“SpringBoot怎么通過自定義注解與異步來管理日志”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。