Toasobi
AOP---增强日志记录功能
本文最后更新于2023年08月29日,已超过494天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!
AOP---增强日志记录功能
先定义两个注解LogAnnotation和LogAroundAnnotation
@Target({ElementType.METHOD}) //只能运用于方法上 @Retention(RetentionPolicy.RUNTIME) //指定该注解在运行时仍然可用,可以通过反射获取注解信息 @Documented //该注解应包含在生成的Java文档 public @interface LogAnnotation { /** * @description 增强方式 * @author kongxiangneng * @time 2023/8/9 16:00 */ String adviceAction() default "before"; /** * @description 操作请求 * @author kongxiangneng * @time 2023/8/15 14:20 */ String info(); }
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAroundAnnotation { /** * @description 操作请求 * @author kongxiangneng * @time 2023/8/15 14:20 */ String info(); }
- 规范化objectMapper的mapper对象格式(objectMapper创建的objectNode可以用于被增强方法的数据存放),所有序列化的对象都按规则进行序列化和反序列化
public class LogObjectMapper {
/**
* 默认日期时间格式
*/
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 默认日期格式
*/
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
/**
* 默认时间格式
*/
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
private static final String REGEX_DATE_TIME = "^([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-((" +
"(0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-" +
"(0[1-9]|[1][0-9]|2[0-8])))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$";
private static final String REGEX_DATE = "^([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-((" +
"(0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-" +
"(0[1-9]|[1][0-9]|2[0-8])))$";
private static final String REGEX_TIME = "^([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$";
private static final String REGEX_TIME_STAMP_MILLI = "^\\d{13}$";
private static final String REGEX_TIME_STAMP_SEC = "^\\d{11}$";
private static ObjectMapper objectMapper;
static {
initObjectMapper();
}
public static ObjectMapper get() {
return objectMapper;
}
private static ObjectMapper initObjectMapper() {
objectMapper = new ObjectMapper();
// 通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化
// Include.Include.ALWAYS 默认
// Include.NON_DEFAULT 属性为默认值不序列化
// Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的
// Include.NON_NULL 属性为NULL 不序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 字段保留,将null值转为""
// objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
// @Override
// public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
// throws IOException {
// jsonGenerator.writeString("");
// }
// });
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
JavaTimeModule javaTimeModule = new JavaTimeModule();
// Date序列化和反序列化
javaTimeModule.addSerializer(Date.class, new JsonSerializer<Date>() {
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
String formattedDate = formatter.format(date);
jsonGenerator.writeString(formattedDate);
}
});
javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<Date>() {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String date = jsonParser.getText();
if (Pattern.matches(REGEX_DATE_TIME, date)) {
SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
} else if (Pattern.matches(REGEX_DATE, date)) {
SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
} else if (Pattern.matches(REGEX_TIME, date)) {
SimpleDateFormat format = new SimpleDateFormat(DEFAULT_TIME_FORMAT);
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
} else if (Pattern.matches(REGEX_TIME_STAMP_MILLI, date)) {
return new Date(Long.valueOf(date));
} else if (Pattern.matches(REGEX_TIME_STAMP_SEC, date)) {
return new Date(Long.valueOf(date + "000"));
} else if (StringUtils.isEmpty(date)) {
return null;
} else {
throw new RuntimeException("时间序列化格式不支持: " + date);
}
}
});
/**
* LocalDateTime序列化返回时间戳
*/
// javaTimeModule.addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
// @Override
// public void serialize(LocalDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
// arg1.writeString(String.valueOf(arg0.toInstant(ZoneOffset.of("+8")).toEpochMilli()));
// }
// });
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
javaTimeModule.addSerializer(LocalTime.class,
new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
javaTimeModule.addDeserializer(LocalTime.class,
new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module());
return objectMapper;
}
}
测试案例的返回格式定义
SpringBeanUtil
package com.summit.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @author kongxiangneng * @time 2023/8/15 11:30 */ @Component public class SpringBeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringBeanUtil.applicationContext = applicationContext; } public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } }
JsonUtil
package com.summit.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class JsonUtil {
public static ObjectMapper objectMapper = SpringBeanUtil.getBean(ObjectMapper.class);
/**
* json字符串转Json对象,如果字符串为null,返回空的Json对象
*
* @param json
* @return
*/
public static ObjectNode toJson(String json) {
if (json == null) {
return objectMapper.createObjectNode();
}
try {
return objectMapper.readValue(json, ObjectNode.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T toObject(String json, Class<T> clazz) {
if (json == null) {
return null;
}
try {
return objectMapper.readValue(json, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T toObject(String json, TypeReference<T> valueTypeRef) {
if (json == null) {
return null;
}
try {
return objectMapper.readValue(json, valueTypeRef);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* 对象转json字符串,如果对象为null,返回null
*
* @param o
* @return
*/
public static String toJsonString(Object o) {
if (o == null) {
return null;
}
try {
return objectMapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T toObject(ObjectNode objectNode, Class<T> clazz) {
if (objectNode == null || clazz == null) {
return null;
}
try {
return objectMapper.treeToValue(objectNode, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
/**
* 对象转json对象,如果对象为null,返回空的json对象
*
* @param o
* @return
*/
public static ObjectNode toJson(Object o) {
if (o == null) {
return objectMapper.createObjectNode();
}
return objectMapper.valueToTree(o);
}
/**
* json字符串转json数组对象,如果字符串为null,返回空的json数组对象
*
* @param json
* @return
*/
public static ArrayNode toJsonArray(String json) {
if (json == null) {
return objectMapper.createArrayNode();
}
try {
return objectMapper.readValue(json, ArrayNode.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T toObject(String jsonStr, Type targetType) {
try {
JavaType javaType = TypeFactory.defaultInstance().constructType(targetType);
return objectMapper.readValue(jsonStr, javaType);
} catch (IOException e) {
throw new IllegalArgumentException("将JSON转换为对象时发生错误:" + jsonStr, e);
}
}
public static <T> List<T> toList(String text, Class<T> valueType) {
try {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, valueType);
return objectMapper.readValue(text, javaType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
ResultCodeEnum
package com.summit.common;
import java.util.HashMap;
import java.util.Map;
public enum ResultCodeEnum {
SUCCESS(0, "success");
/**
* 处理结果返回编码
*/
private Integer code;
/**
* 处理结果描述
*/
private String desc;
ResultCodeEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public static boolean isFail(Result result){
return !result.getCode().equals(SUCCESS.getCode());
}
public static void main(String[] args) {
Map map = new HashMap<>();
for (ResultCodeEnum ret : ResultCodeEnum.values()) {
// if(map.get(ret.code) != null){
// System.out.println(ret);
// }else{
// map.put(ret,ret.getCode());
// }
// 输出js数组格式
// map.put("[" +ret.getCode()+"]", ret);
// 输出国际化语言数组
// map.put(ret, ret.getDesc());
// System.out.println(ret.code);
// System.out.println(ret.getDesc());
// System.out.println("[" + ret.getCode() + "]: " + "\'" + ret + "\',");
// System.out.println("\"" + ret + "\": " + "\"" + ret.getDesc() + "\",");
}
}
}
Result
package com.summit.common;
import com.summit.util.JsonUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collection;
@Data
@NoArgsConstructor
public class Result<T> {
/**
* 处理结果返回编码
*/
private Integer code;
/**
* 处理结果描述
*/
private String desc;
/**
* 处理结果
*/
private T data;
public Result(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Result(Integer code, String desc, T data) {
this.code = code;
this.desc = desc;
this.data = data;
}
public Result(ResultCodeEnum resultCodeEnum) {
this(resultCodeEnum.getCode(), resultCodeEnum.getDesc());
}
public Result(ResultCodeEnum resultCodeEnum, T data) {
this(resultCodeEnum.getCode(), resultCodeEnum.getDesc(), data);
}
public static Result success() {
return new Result(ResultCodeEnum.SUCCESS);
}
public static <T> Result success(T data) {
return new Result(ResultCodeEnum.SUCCESS, data);
}
public static <T> Result success(Collection<T> collection) {
return new Result(ResultCodeEnum.SUCCESS, collection);
}
public static Result error(ApiException exception) {
return new Result(exception.getCode(), exception.getDesc());
}
public static Result error(ResultCodeEnum resultCodeEnum) {
return new Result(resultCodeEnum);
}
public static Result error(Throwable cause){
return Result.error((ApiException)cause);
}
public String toJsonString(){
return JsonUtil.toJsonString(this);
}
}
- 使用Spring AOP 和 AspectJ 两个方案实现AOP-对注解方法进行增强
Spring AOP实现 ---> ApiLogAspect
@Aspect
@Component
public class ApiLogAspect { //使用 Spring AOP 实现的日志切面(ApiLogAspect),用于记录 API 请求的相关信息
private static final Logger log = LoggerFactory.getLogger(ApiLogAspect.class);
private ObjectMapper objectMapper = LogObjectMapper.get();
@Pointcut("@within(org.springframework.web.bind.annotation.RestController) " +
"|| @within(org.springframework.stereotype.Controller)") //切入点匹配标有 @RestController 或 @Controller 注解的类
// @Pointcut("execution(* com.summit.util.JsonUtil.*(..))")
public void controllerLog() {
}
@Around("controllerLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //一个环绕通知,会在切入点方法执行前后进行拦截和处理
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
long startTime = System.nanoTime(); //获取当前请求的 HttpServletRequest 对象和开始时间
Object result;
boolean isException = false;
Throwable exception = null;
//是否404
boolean isNotFound = false;
try {
result = proceedingJoinPoint.proceed(); //执行切入点方法
if (result instanceof ResponseEntity && ((ResponseEntity) result).getStatusCode().value() == HttpStatus.NOT_FOUND.value()) {
isNotFound = true;
}
} catch (Throwable throwable) {
isException = true;
exception = throwable;
throw throwable;
} finally {
ObjectNode node = objectMapper.createObjectNode();
node.put("logType", "apiReqLog");
node.put("url", request.getRequestURL().toString());
node.put("method", request.getMethod());
String xForwardedFor = request.getHeader("x-forwarded-for");
if (xForwardedFor == null) {
node.put("ip", request.getRemoteAddr());
} else {
node.put("ip", request.getHeader("x-forwarded-for"));
}
if (ServletFileUpload.isMultipartContent(request)) {
Object[] args = proceedingJoinPoint.getArgs();
if (!isNotFound) {
if (args.length == 0) {
node.set("reqParam", null);
} else {
for (Object item : args) {
if (item instanceof MultipartFile) {
continue;
}
if (item instanceof MultipartFile[]) {
continue;
}
node.set("reqParam", JsonUtil.toJson(item));
}
}
}
} else {
Object[] args = proceedingJoinPoint.getArgs();
if (!isNotFound) {
if (args.length == 0) {
node.set("reqParam", null);
} else {
try {
node.set("reqParam", JsonUtil.toJson(args[0]));
} catch (Exception e) {
//非json不处理
}
}
}
}
if (isException) {
if (exception instanceof ApiException) {
node.put("exception", exception.toString() + "|"
+ ((ApiException) exception).getCode() + "|" + ((ApiException) exception).getDesc());
} else {
node.put("exception", exception.toString() + "|" + exception.getMessage());
node.put("stack", ExceptionUtil.stacktraceToString(exception));
}
node.put("isSuccess", false);
} else {
node.put("isSuccess", true);
}
if (isNotFound) {
node.put("isSuccess", false);
node.put("exception", "Not Found");
}
node.put("execTime", (System.nanoTime() - startTime) / 1000000);
log.info(node.toPrettyString());
}
return result;
}
}
AspectJ ---> LogAspect
@Aspect
@Component
public class LogAspect { //使用 AspectJ 实现的 AOP 方式
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private ObjectMapper objectMapper = LogObjectMapper.get();
private static final String ACTION_BEFORE = "before";
private static final String ACTION_AFTER = "after";
private static final String ACTION_AROUND = "around";
private static final String ACTION_AFTER_RETURNING = "afterReturning";
private static final String ACTION_AFTER_THROWING = "afterThrowing";
/**
* @description 声明一个公用的切入点,满足此条件都会被植入增强-使用AspectAnnotation注解修饰的方法都将进入此方法
* @author
* @time 2023/8/8 16:36
*/
@Pointcut("@annotation(com.summit.annotation.LogAnnotation)")
private void pointCut(){}
/**
* @description 环绕增强
* @author
* @time 2023/8/9 17:35
*/
@Pointcut("@annotation(com.summit.annotation.LogAroundAnnotation)")
private void aroundPointCut(){}
/**
* @description 前置增强
* @author
* @time 2023/8/8 16:33
*/
@Before("pointCut() && @annotation(annotation)")
public void before(JoinPoint joinPoint, LogAnnotation annotation){
// 获取注解属性
String action = annotation.adviceAction();
if (action.equals(ACTION_BEFORE)){
long startTime = System.nanoTime();
ObjectNode node = initNode(joinPoint, annotation);
// 操作时间
node.put("execTime", (System.nanoTime() - startTime) / 1000000);
log.info(node.toPrettyString());
}
}
/**
* @description 后置增强-无论是否异常
* @author
* @time 2023/8/8 16:33
*/
@After("pointCut() && @annotation(annotation)")
public void after(JoinPoint joinPoint, LogAnnotation annotation){
// 获取注解属性
String action = annotation.adviceAction();
if (action.equals(ACTION_AFTER)){
long startTime = System.nanoTime();
ObjectNode node = initNode(joinPoint, annotation);
// 操作时间
node.put("execTime", (System.nanoTime() - startTime) / 1000000);
log.info(node.toPrettyString());
}
}
/**
* @description 环绕增强
* @author
* @time 2023/8/8 16:33
*/
@Around("aroundPointCut() && @annotation(logger)")
public Object around(ProceedingJoinPoint joinPoint, LogAroundAnnotation logger) throws Throwable { //只有LogAroundAnnotation 会拦截
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Result result = Result.success();
long startTime = System.nanoTime();
ObjectNode before = objectMapper.createObjectNode();
ObjectNode after = objectMapper.createObjectNode();
try {
// 请求URL
before.put("url", request.getRequestURL().toString());
// 请求方法
before.put("method", request.getMethod());
// 操作请求-注解
before.put("info",logger.info());
// 请求IP
before.put("operatorIp",request.getRemoteAddr());
// 类名
before.put("className",joinPoint.getTarget().getClass().getName());
// 类方法名
before.put("methodName",joinPoint.getSignature().getName());
// 前置增强
log.info(before.toPrettyString());
// 核心代码
result = (Result)joinPoint.proceed();
} catch (Throwable throwable) {
after.put("exception",throwable.toString());
after.put("exceptionMsg",throwable.getMessage());
throw throwable;
} finally {
after.put("returnCode",result.getCode());
after.put("returnMsg",result.getDesc());
after.put("execTime", (System.nanoTime() - startTime) / 1000000);
// 后置增强
log.info(after.toPrettyString());
}
return result;
}
/**
* @description 在目标正常执行返回前增强
* @author
* @time 2023/8/8 16:33
*/
@AfterReturning(value = "pointCut() && @annotation(annotation)",returning = "returning")
public void afterReturning(JoinPoint joinPoint, LogAnnotation annotation, Result returning){
// 获取注解属性
String action = annotation.adviceAction();
if (action.equals(ACTION_AFTER_RETURNING)){
long startTime = System.nanoTime();
ObjectNode node = initNode(joinPoint, annotation);
// 操作时间
node.put("execTime", (System.nanoTime() - startTime) / 1000000);
if (!ObjectUtils.isEmpty(returning)){
node.put("returnCode",returning.getCode());
node.put("returnMsg",returning.getDesc());
}
log.info(node.toPrettyString());
}
}
/**
* @description 异常增强处理,在目标方法抛异常后植入增强
* @author kongxiangneng
* @time 2023/8/8 16:34
*/
@AfterThrowing(value = "pointCut() && @annotation(annotation)",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,LogAnnotation annotation,Exception exception){
// 获取注解属性
String action = annotation.adviceAction();
if (action.equals(ACTION_AFTER_THROWING)){
long startTime = System.nanoTime();
ObjectNode node = initNode(joinPoint, annotation);
// 操作时间
node.put("execTime", (System.nanoTime() - startTime) / 1000000);
if (!ObjectUtils.isEmpty(exception)){
node.put("exception",exception.toString());
node.put("exceptionMsg",exception.getMessage());
}
log.info(node.toPrettyString());
}
}
private ObjectNode initNode(JoinPoint joinPoint,LogAnnotation logger){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
ObjectNode node = objectMapper.createObjectNode();
// 请求URL
node.put("url", request.getRequestURL().toString());
// 请求方法
node.put("method", request.getMethod());
// 操作请求-注解
node.put("info",logger.info());
// 请求IP
node.put("operatorIp",request.getRemoteAddr());
// 类名
node.put("className",joinPoint.getTarget().getClass().getName());
// 类方法名
node.put("methodName",joinPoint.getSignature().getName());
return node;
}
}
测试可行性
@RestController @RequestMapping("/v1/aop/test") public class TestController { @LogAnnotation(adviceAction = "before",info = "前置增强") @GetMapping("before") public Result before(){ System.out.println("before controller"); return Result.success(); } @LogAnnotation(adviceAction = "after",info = "后置增强") @GetMapping("after") public Result after(){ System.out.println("after controller"); return Result.success(); } @LogAroundAnnotation(info = "环绕增强") @GetMapping("around") public Result around(){ System.out.println("around controller"); return Result.success(); } @LogAnnotation(adviceAction = "afterReturning",info = "正常后置增强") @GetMapping("afterReturning") public Result afterReturning(){ System.out.println("afterReturning controller"); return Result.success(); } @LogAnnotation(adviceAction = "afterThrowing",info = "异常后置增强") @GetMapping("afterThrowing") public Result afterThrowing(){ int a = 1 / 0; System.out.println("afterThrowing controller"); return Result.success(); } }