本篇文章采用 Spring AOP 和自定义日志注解实现了日志的保存。
# 引入需要的依赖
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-aop</artifactId> | |
<version>2.2.6.RELEASE</version> | |
</dependency> | |
<dependency> | |
<groupId>log4j</groupId> | |
<artifactId>log4j</artifactId> | |
<version>1.2.17</version> | |
</dependency> |
# 创建日志相关类
日志实体类
LogEntity
package com.example.demo.entity; | |
import java.time.LocalDateTime; | |
public class LogEntity { | |
/** | |
* uuid | |
*/ | |
private String id; | |
/** | |
* 方法名 | |
*/ | |
private String method; | |
/** | |
* 操作类型 | |
*/ | |
private String operationType; | |
/** | |
* 参数 | |
*/ | |
private String params; | |
/** | |
* 操作消息(成功 / 失败) | |
*/ | |
private String message; | |
/** | |
* 操作时间 | |
*/ | |
private LocalDateTime operationDate; | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public String getMethod() { | |
return method; | |
} | |
public void setMethod(String method) { | |
this.method = method; | |
} | |
public String getOperationType() { | |
return operationType; | |
} | |
public void setOperationType(String operationType) { | |
this.operationType = operationType; | |
} | |
public String getParams() { | |
return params; | |
} | |
public void setParams(String params) { | |
this.params = params; | |
} | |
public String getMessage() { | |
return message; | |
} | |
public void setMessage(String message) { | |
this.message = message; | |
} | |
public LocalDateTime getOperationDate() { | |
return operationDate; | |
} | |
public void setOperationDate(LocalDateTime operationDate) { | |
this.operationDate = operationDate; | |
} | |
} |
日志存储层
LogDao
package com.example.demo.dao; | |
import com.example.demo.entity.LogEntity; | |
import com.example.demo.log.Log; | |
import org.springframework.stereotype.Component; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.UUID; | |
@Component | |
public class LogDao { | |
//TODO 为了方便采用临时存储(可连接数据库) | |
private static final Map<String, LogEntity> LOGS = new HashMap<>(); | |
public LogEntity save(LogEntity logEntity){ | |
logEntity.setId(UUID.randomUUID().toString()); | |
LOGS.put(logEntity.getId(), logEntity); | |
return logEntity; | |
} | |
} |
转化工具类
ConvertUtils
package com.example.demo.util; | |
import com.fasterxml.jackson.core.JsonProcessingException; | |
import com.fasterxml.jackson.databind.DeserializationFeature; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import javax.validation.ValidationException; | |
public class ConvertUtils { | |
public static final Logger LOG = LoggerFactory.getLogger(ConvertUtils.class); | |
private static ObjectMapper objectMapper; | |
public static ObjectMapper getObjectMapper() { | |
if (objectMapper == null) { | |
objectMapper = new ObjectMapper(); | |
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | |
objectMapper.registerModule(new JavaTimeModule()); | |
} | |
return objectMapper; | |
} | |
public static String toJson(Object object) { | |
String json = null; | |
try { | |
json = getObjectMapper().writeValueAsString(object); | |
} catch (JsonProcessingException e) { | |
e.printStackTrace(); | |
LOG.error("序列化失败:" , e); | |
throw new ValidationException("序列化失败"); | |
} | |
return json; | |
} | |
} |
# 自定义日志注解
@Retention(RetentionPolicy.RUNTIME) | |
@Target(ElementType.METHOD) | |
@Documented | |
public @interface Log { | |
// 操作类型 | |
String operationType() default ""; | |
// TODO 可拓展其他参数 | |
} |
# 创建 AOP 切面实现类
package com.example.demo.log; | |
import com.example.demo.dao.LogDao; | |
import com.example.demo.entity.LogEntity; | |
import com.example.demo.util.ConvertUtils; | |
import net.minidev.json.JSONArray; | |
import net.minidev.json.JSONObject; | |
import org.aspectj.lang.JoinPoint; | |
import org.aspectj.lang.Signature; | |
import org.aspectj.lang.annotation.AfterReturning; | |
import org.aspectj.lang.annotation.AfterThrowing; | |
import org.aspectj.lang.annotation.Aspect; | |
import org.aspectj.lang.annotation.Before; | |
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.RequestAttributes; | |
import org.springframework.web.context.request.RequestContextHolder; | |
import org.springframework.web.context.request.ServletRequestAttributes; | |
import javax.servlet.http.HttpServletRequest; | |
import java.io.UnsupportedEncodingException; | |
import java.lang.reflect.Method; | |
import java.net.URLDecoder; | |
import java.nio.charset.StandardCharsets; | |
import java.time.LocalDateTime; | |
import java.util.Arrays; | |
@Aspect | |
@Component(value = "logAspect") | |
public class LogAspect { | |
private static final Logger LOG = LoggerFactory.getLogger(LogAspect.class); | |
private static final String POST = "POST"; | |
private static final String GET = "GET"; | |
@Autowired | |
private LogDao logDao; | |
/** | |
* 配置切入点 | |
*/ | |
@Pointcut("@annotation(com.example.demo.log.Log)") | |
public void logPointCut() { | |
} | |
/** | |
* 前置通知 | |
*/ | |
@Before(value = "logPointCut()") | |
public void before(JoinPoint joinPoint) { | |
doBefore(joinPoint); | |
} | |
/** | |
* 返回通知,在方法返回结果之后执行 | |
*/ | |
@AfterReturning(pointcut = "logPointCut()") | |
public void afterReturning(JoinPoint joinPoint) { | |
doAfter(joinPoint, null); | |
} | |
/** | |
* 异常通知,在方法抛出异常之后执行 | |
*/ | |
@AfterThrowing(pointcut = "logPointCut()", throwing = "e") | |
public void afterThrowing(JoinPoint joinPoint, Exception e) { | |
doAfter(joinPoint, e); | |
} | |
private void doBefore(JoinPoint joinPoint) { | |
LOG.info("前置操作开始..."); | |
try { | |
// 打印参数 | |
printParams(joinPoint); | |
} catch (Exception e) { | |
// 打印异常日志 | |
LOG.error("前置通知异常:" + e.getMessage()); | |
} | |
LOG.info("前置操作结束..."); | |
} | |
private void doAfter(JoinPoint joinPoint, Exception e) { | |
LOG.info("后置操作开始..."); | |
LogEntity logEntity = new LogEntity(); | |
try { | |
// 得到注解 | |
Log log = getAnnotation(joinPoint); | |
if (log == null) { | |
return; | |
} | |
LOG.info("日志保存开始..."); | |
// 类名 | |
String className = joinPoint.getTarget().getClass().getName(); | |
// 方法名 | |
String methodName = joinPoint.getSignature().getName(); | |
// 操作类型 | |
String operatorType = log.operationType(); | |
logEntity.setMethod(className + "." + methodName); | |
logEntity.setOperationType(operatorType); | |
logEntity.setParams(ConvertUtils.toJson(joinPoint.getArgs())); | |
logEntity.setMessage(null == e ? "处理成功" : "处理失败:" + e.getMessage()); | |
logEntity.setOperationDate(LocalDateTime.now()); | |
LOG.info(ConvertUtils.toJson(logEntity)); | |
} catch (Exception exception) { | |
// 记录本地异常日志 | |
LOG.error("后置通知异常:" + exception.getMessage()); | |
logEntity.setMessage("处理失败:" + exception); | |
} finally { | |
logDao.save(logEntity); | |
LOG.info("日志保存结束..."); | |
} | |
LOG.info("后置操作结束..."); | |
} | |
private void printParams(JoinPoint joinPoint) { | |
//request 请求 | |
RequestAttributes ra = RequestContextHolder.getRequestAttributes(); | |
ServletRequestAttributes sra = (ServletRequestAttributes) ra; | |
HttpServletRequest request = sra.getRequest(); | |
// 请求 url | |
String url = request.getRequestURL().toString(); | |
// 请求方式 | |
String method = request.getMethod(); | |
// 请求参数 | |
String queryString = request.getQueryString(); | |
Object[] args = joinPoint.getArgs(); | |
StringBuilder requestParams = new StringBuilder("请求参数为:{"); | |
if (args.length > 0) { | |
if (POST.equals(method)) { | |
requestParams.append(Arrays.asList(args)).append("}"); | |
} else if (GET.equals(method)) { | |
String params = URLDecoder.decode(queryString, StandardCharsets.UTF_8); | |
requestParams.append(Arrays.toString(params.split("&"))).append("}"); | |
} | |
} | |
LOG.info(requestParams.toString()); | |
} | |
private Log getAnnotation(JoinPoint joinPoint) { | |
Signature signature = joinPoint.getSignature(); | |
MethodSignature methodSignature = (MethodSignature) signature; | |
Method method = methodSignature.getMethod(); | |
if (method != null) { | |
// 拿到自定义注解的信息 | |
return method.getAnnotation(Log.class); | |
} | |
return null; | |
} | |
} |
# 测试
# 定义一个测试实体
DemoEntity
package com.example.demo.entity; | |
import java.io.Serializable; | |
public class DemoEntity implements Serializable { | |
private String id; | |
private String name; | |
private String age; | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public String getAge() { | |
return age; | |
} | |
public void setAge(String age) { | |
this.age = age; | |
} | |
@Override | |
public String toString() { | |
return "DemoEntity{" + | |
"id='" + id + '\'' + | |
", name='" + name + '\'' + | |
", age='" + age + '\'' + | |
'}'; | |
} | |
} |
# 定义一个测试控制层
package com.example.demo.controller; | |
import com.example.demo.entity.DemoEntity; | |
import com.example.demo.log.Log; | |
import com.example.demo.service.DemoService; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.web.bind.annotation.RequestBody; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
@RestController | |
public class DemoController { | |
@Autowired | |
private DemoService demoService; | |
@RequestMapping(value = "/submit") | |
@Log(operationType = "提交") | |
public String submit(@RequestBody DemoEntity demoEntity){ | |
return demoEntity.toString(); | |
} | |
} |
# 发起请求测试
localhost:8080/submit
请求参数
{ | |
"id": "123456", | |
"name": "测试", | |
"age": "18" | |
} |
日志打印
2022-05-31 13:40:20.140 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : 前置操作开始...
2022-05-31 13:40:20.140 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : 请求参数为:{[DemoEntity {id='123456', name=' 测试 ', age='18'}]}
2022-05-31 13:40:20.140 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : 前置操作结束...
2022-05-31 13:40:20.141 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : 后置操作开始...
2022-05-31 13:40:20.141 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : 日志保存开始...
2022-05-31 13:40:20.141 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : {"id":null,"method":"com.example.demo.controller.DemoController.submit","operationType":"提交","params":"[{"id":"123456","name":" 测试 ","age":"18"}]","message":"处理成功","operationDate":[2022,5,31,13,40,20,141891900]}
2022-05-31 13:40:20.141 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : 日志保存结束...
2022-05-31 13:40:20.141 INFO 20340 --- [nio-1111-exec-2] com.example.demo.log.LogAspect : 后置操作结束...