SpringBoot中使用AOP實(shí)現(xiàn)日志記錄功能
AOP簡(jiǎn)介
AOP的全稱是Aspect-Oriented Programming,即面向切面編程(也稱面向方面編程)。它是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充,目前已成為一種比較成熟的編程方式。
在傳統(tǒng)的業(yè)務(wù)處理代碼中,通常都會(huì)進(jìn)行事務(wù)處理、日志記錄等操作。雖然使用OOP可以通過(guò)組合或者繼承的方式來(lái)達(dá)到代碼的重用,但如果要實(shí)現(xiàn)某個(gè)功能(如日志記錄),同樣的代碼仍然會(huì)分散到各個(gè)方法中。這樣,如果想要關(guān)閉某個(gè)功能,或者對(duì)其進(jìn)行修改,就必須要修改所有的相關(guān)方法。這不但增加了開發(fā)人員的工作量,而且提高了代碼的出錯(cuò)率。
為了解決這一問(wèn)題,AOP思想隨之產(chǎn)生。AOP采取橫向抽取機(jī)制,將分散在各個(gè)方法中的重復(fù)代碼提取出來(lái),然后在程序編譯或運(yùn)行時(shí),再將這些提取出來(lái)的代碼應(yīng)用到需要執(zhí)行的地方。這種采用橫向抽取機(jī)制的方式,采用傳統(tǒng)的OOP思想顯然是無(wú)法辦到的,因?yàn)镺OP只能實(shí)現(xiàn)父子關(guān)系的縱向的重用。雖然AOP是一種新的編程思想,但卻不是OOP的替代品,它只是OOP的延伸和補(bǔ)充。

創(chuàng)建日志數(shù)據(jù)庫(kù)
創(chuàng)建日志記錄表
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_oper_log -- ---------------------------- DROP TABLE IF EXISTS `sys_oper_log`; CREATE TABLE `sys_oper_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主鍵', `operation` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '操作', `business_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '業(yè)務(wù)類型', `method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '方法名稱', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '操作時(shí)間', `oper_name` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作用戶', `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '參數(shù)', `ip` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '請(qǐng)求的ip地址', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2058 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志記錄' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
創(chuàng)建用戶表
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID', `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名 ', `age` int(11) NULL DEFAULT NULL COMMENT '年齡 ', `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '郵箱 ', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (2, 'Jack', 20, 'test2@baomidou.com'); INSERT INTO `user` VALUES (3, 'Tom', 28, 'test3@baomidou.com'); INSERT INTO `user` VALUES (4, 'Sandy', 21, 'test4@baomidou.com'); INSERT INTO `user` VALUES (5, 'Billie', 24, 'test5@baomidou.com'); INSERT INTO `user` VALUES (6, 'sss', 18, '123@qq.com'); INSERT INTO `user` VALUES (8, 'sss', 18, '123@qq.com'); SET FOREIGN_KEY_CHECKS = 1;
簡(jiǎn)單看一下表格的結(jié)構(gòu),我這里的數(shù)據(jù)就不給大家展示了。


SpringBoot使用AOP
一、導(dǎo)入依賴
下邊的三個(gè)依賴是我們的核心依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>二、創(chuàng)建我們的項(xiàng)目結(jié)構(gòu)
- 創(chuàng)建UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}- 創(chuàng)建UserService
public interface UserService extends IService<User> {
}- 創(chuàng)建UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}- 創(chuàng)建UserController
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Log(operation = "查找用戶",businessType = BusinessType.LIST)
@RequestMapping("/find-user")
public String findUser(){
return userService.list().toString();
}
}創(chuàng)建兩個(gè)實(shí)體
- 創(chuàng)建User
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer age;
private String email;
}- 創(chuàng)建SysOperLog
@Data
@TableName("sys_oper_log")
public class SysOperLog {
@TableId(type = IdType.AUTO)
private Long id;
private String operation;
private String businessType;
private String method;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
private String operName;
private String params;
private String ip;
}三、使用AOP
1.創(chuàng)建枚舉類
這個(gè)枚舉類的作用就是記錄我們調(diào)用的接口是什么樣的一個(gè)類型的,是查找、刪除還是其他。
public enum BusinessType {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 刪除
*/
DELETE,
/**
* 瀏覽
*/
LIST
}2..創(chuàng)建Log注解
默認(rèn)的操作為空,默認(rèn)的操作類型是OTHER。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 操作名稱
* @return
*/
String operation() default "";
/**
* 操作的類型
* @return
*/
BusinessType businessType() default BusinessType.OTHER;
}3.創(chuàng)建切面類
@Aspect
@Component
public class LogAspect {
@Pointcut("@annotation(com.qcby.annotation.Log)")
public void pointCut(){}
@Autowired
HttpServletRequest request;
@Autowired
SysOperLogMapper sysOperLogMapper;
@After(value = "pointCut()")
public void afterLogWrite(JoinPoint joinPoint){
// 創(chuàng)建日志對(duì)象
SysOperLog sysOperLog = new SysOperLog();
// 獲取我們調(diào)用的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 獲取方法上的Log注解,因?yàn)槲覀円@取注解中的一些信息
Log log = method.getAnnotation(Log.class);
// 獲取我們調(diào)用的類的名稱
String className = joinPoint.getTarget().getClass().getName();
// 獲取調(diào)用的方法的名稱
String methodName = method.getName();
// 重新修改一下我們調(diào)用的方法 是全路徑的
methodName = className + methodName;
// 獲取方法的參數(shù)
Object[] args = joinPoint.getArgs();
ObjectMapper objectMapper = new ObjectMapper();
String params = "";
try {
params = objectMapper.writeValueAsString(args);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// 獲取注解中的操作名稱
String operation = log.operation();
// 獲取注解中的操作類型
String businessType = log.businessType().toString();
// 這里的操作人員僅靠后端是寫不了的 需要前端的token認(rèn)證 我直接把操作人員改為admin
String username = "admin";
// 獲取ip地址
String ipAddress = IpUtil.getIpAddr(request);
sysOperLog.setBusinessType(businessType);
sysOperLog.setOperation(operation);
sysOperLog.setMethod(methodName);
sysOperLog.setParams(params);
sysOperLog.setIp(ipAddress);
sysOperLog.setOperName(username);
sysOperLog.setCreateTime(LocalDateTime.now());
sysOperLogMapper.insert(sysOperLog);
}
}4.IpUtil
public class IpUtil {
private static final String UNKNOWN = "unknown";
private static final String LOCALHOST = "127.0.0.1";
private static final String SEPARATOR = ",";
public static String getIpAddr(HttpServletRequest request) {
System.out.println(request);
String ipAddress;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (LOCALHOST.equals(ipAddress)) {
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 對(duì)于通過(guò)多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割
// "***.***.***.***".length()
if (ipAddress != null && ipAddress.length() > 15) {
if (ipAddress.indexOf(SEPARATOR) > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
}5.進(jìn)行測(cè)試
我們?cè)跒g覽器上輸入網(wǎng)址:
127.0.0.1:8080/user/find-user
數(shù)據(jù)是沒(méi)問(wèn)題的,接下來(lái)我們只需要查看數(shù)據(jù)的日志文件是否插入了日志就好了。

這里我查找了兩次,一次使用的localhost,另一次使用的127.0.0.1。日志可以成功記錄。

以上就是SpringBoot中使用AOP實(shí)現(xiàn)日志記錄功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot AOP日志記錄的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot 攔截器 (Interceptor)與切面 (AOP)示例、作用及適用場(chǎng)景分析
- AOP在SpringBoot項(xiàng)目中的使用場(chǎng)景解讀
- SpringBoot整合Jasypt使用自定義注解+AOP實(shí)現(xiàn)敏感字段加解密
- Springboot如何正確使用AOP問(wèn)題
- springboot接口服務(wù),防刷、防止請(qǐng)求攻擊,AOP實(shí)現(xiàn)方式
- SpringBoot3利用AOP實(shí)現(xiàn)IP黑名單功能
- springbootAOP定義切點(diǎn)獲取/修改請(qǐng)求參數(shù)方式
- SpringBoot實(shí)現(xiàn)AOP切面的三種方式
- SpringBoot AOP如何配置全局事務(wù)
- JAVA中Spring Boot的AOP切面編程是什么,如何使用?(實(shí)例代碼)
相關(guān)文章
Spring實(shí)現(xiàn)上拉刷新和下拉加載效果
這篇文章主要為大家詳細(xì)介紹了Spring實(shí)現(xiàn)上拉刷新和下拉加載效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Java?自定義注解在登錄驗(yàn)證的應(yīng)用示例
本文主要介紹了Java?自定義注解在登錄驗(yàn)證的應(yīng)用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
mybatis項(xiàng)目實(shí)現(xiàn)動(dòng)態(tài)表名的三種方法
有時(shí)在開發(fā)過(guò)程中java代碼中的表名和數(shù)據(jù)庫(kù)的表名并不是一致的,此時(shí)我們就需要?jiǎng)討B(tài)的設(shè)置表名,本文主要介紹了mybatis項(xiàng)目實(shí)現(xiàn)動(dòng)態(tài)表名的三種方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
springboot1.X和2.X中如何解決Bean名字相同時(shí)覆蓋
這篇文章主要介紹了springboot1.X和2.X中如何解決Bean名字相同時(shí)覆蓋,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
SpringBoot Controller Post接口單元測(cè)試示例
今天小編就為大家分享一篇關(guān)于SpringBoot Controller Post接口單元測(cè)試示例,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
SpringBoot 模糊映射(Ambiguous mapping)報(bào)錯(cuò)解決指南
本文針對(duì) Spring Boot 啟動(dòng)時(shí) “requestMappingHandlerMapping 模糊映射” 引發(fā)的 BeanCreationException,下面就來(lái)詳細(xì)的介紹一下該錯(cuò)誤的解決,感興趣的可以了解一下2025-09-09
Spring jpa和mybatis整合遇到的問(wèn)題解析
有朋友說(shuō)jpa相比mybatis太難用,多表聯(lián)合的查詢寫起來(lái)也比較費(fèi)勁,所以便加入了mybatis的支持,在配置jpa時(shí)遇到各種問(wèn)題,需要修改相關(guān)配置文件,下面小編給大家分享下修改配置文件的思路,感興趣的朋友參考下2016-10-10

