@
- 概述:
- 特色
- 使用方式
- 注意点
- 代码
- 第一步:pom引入AOP
- 第二步:创建自定义注解、Bean实体、枚举、常量类
- 第三步:Controller层方法使用自定义注解标识
- 第四步:新建一个日志操作类LogAopAction,专门用来处理操作保存日志
- 第五步:postman模拟调用接口,输出AOP中ProceedingJoinPoint获取目标方法,参数,注解
概述:
该SpringBoot项目使用AOP的环绕@Around注解及自定义注解保存操作日志到数据库,自定义注解中会配置日志模板类型logModelType字段,通过该字段去匹配是创建、删除、修改...等等功能,本案例就是为了模拟现实项目中通过AOP及自定义注解如何保存操作详情日志功能。
特色
form表单除了input输入框,也会有一些按钮【Disable/Enable】,这些按钮就需要转换数字值然后动态拼接详情日志参数,日志操作类LogAopAction中拼接参数不只是简单的一堆get、set、if else去拼接,而是根据类型logModelType字段 =》 去找枚举LogDetailEnums =》通过枚举值找常量类LogDetailConstants,常量类中定义了各种类型操作的占位符,动态拼接参数使用MessageFormat.format(),这样使用更加简单、看起来更加优雅、实现可扩展性不用写一堆代码。
使用方式
- 第一步:pom引入AOP
- 第二步:创建自定义注解、Bean实体、枚举、常量类
- 第三步:Controller层方法使用自定义注解标识
- 第四步:新建一个日志操作类LogAopAction,专门用来处理操作保存日志
- 第五步:postman模拟调用接口,输出AOP中ProceedingJoinPoint获取目标方法,参数,注解
注意点
- 注意点1:日志操作类LogAopAction必须加两个注解@Aspect和@Component,其中@Aspect注解代表该类为切面,而@Component为了使该类能让spring容器扫描到
- 注意点2:@Around注解中配置@annotation注解用来指定生效的自定义注解名字
- 注意点3:该案例描述AOP中ProceedingJoinPoint获取目标方法,参数,注解
- 注意点4:接收实体Bean要重新toString方法,不然无法转成json,因为未重写toString方法中用的是等号 "=" 而不是冒号 ":"
- 注意点5:格式化常量类定义好的占位符请使用MessageFormat.format(),拼接起来更加方便
代码
第一步:pom引入AOP
<!--aop相关的依赖引入-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.78</version>
</dependency>
第二步:创建自定义注解、Bean实体、枚举、常量类
自定义注解LogAnnotation
package com.example.demo.config;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LogAnnotation {//日志模板类型String logModelType();
}
实体类
package com.example.demo.bean;import lombok.Data;/*** @Author 211145187* @Date 2022/2/23 09:32**/
@Data
public class OperateTeacherReq {//idprivate Integer id;//姓名private String name;//开关【0:Disable、1:Enable】private Integer pstnFlag;@Overridepublic String toString() {return "{" +"id:" + id +", name:'" + name + '\'' +", pstnFlag:" + pstnFlag +'}';}
}
枚举类LogDetailEnums
package com.example.demo.enums;public enum LogDetailEnums {/*** 开户*/CREATE_ACCOUNT("CREATE_ACCOUNT", LogDetailConstants.CREATE_ACCOUNT),/*** PSTN Flag值转换*/PSTN_ENABLE_FLAG("pstnFlag_1","Enable"),PSTN_DISABLE_FLAG("pstnFlag_0","Disable");private String code;private String message;LogDetailEnums(String code, String message) {this.code = code;this.message = message;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public static String getDetailByCode(String messageCode){try {for (LogDetailEnums value : LogDetailEnums.values()) {if (value.code.equals(messageCode)){return value.message;}}} catch (Exception e) {e.printStackTrace();}return "";}public static LogDetailEnums getLogDetailEnum(String code){if (code==null){return null;}for (LogDetailEnums value : LogDetailEnums.values()) {if (code.equals(value.code)){return value;}}return null;}
}
常量类LogDetailConstants
package com.example.demo.enums;public class LogDetailConstants {public static final String CREATE_ACCOUNT = "【Add Account】id:[{0}], User Name:[{1}],pstnFlag:[{2}]";
}
第三步:Controller层方法使用自定义注解标识
package com.example.demo.controller;import com.example.demo.bean.TeacherReq;
import com.example.demo.config.LogAnnotation;
import com.example.demo.mapper.TeacherMapper;
import com.example.demo.response.Response;
import org.apache.commons.codec.binary.Base64;
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.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;/*** @Author 211145187* @Date 2022/5/7 11:34**/
@RestController
public class Controller {
@RequestMapping(value = "/operateLog", method = RequestMethod.POST)@LogAnnotation(logModelType = "CREATE_ACCOUNT")public Response operateLog(@RequestBody OperateTeacherReq req) {return Response.success(req);}
}
第四步:新建一个日志操作类LogAopAction,专门用来处理操作保存日志
package com.example.demo.config;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.bean.OperateTeacherReq;
import com.example.demo.enums.LogDetailEnums;
import com.example.demo.response.Response;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.text.MessageFormat;@Aspect
@Component
public class LogAopAction {/*** AOP切面保存操作日志* @Author 211145187* @Around 注解描述的方法为一个环绕通知方法,在此方法中可以添加扩展业务逻辑,可以调用下一个切面对象或目标方法* @param point 连接点(此连接点只应用@Around描述的方法)* @Date 2022/5/16 14:38* @param point* @Return Response**/@Around("@annotation(com.example.demo.config.LogAnnotation) && execution(* com.example.demo.controller.*.*(..))")public Response logOperate(ProceedingJoinPoint point) throws NoSuchMethodException {//获取类的字节码对象,通过字节码对象获取方法信息Class<?> targetCls=point.getTarget().getClass();//获取方法签名(通过此签名获取目标方法信息)MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();//获取目标方法上的注解指定的操作名称Method targetMethod=targetCls.getDeclaredMethod(signature.getName(),signature.getParameterTypes());System.out.println("获取目标方法上的注解指定的操作名称:"+targetMethod);LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);String logModelType=logAnnotation.logModelType();System.out.println("获取自定义注解参数值logModelType:" + logModelType);//获取目标方法名(目标类型+方法名)String targetClsName=targetCls.getName();String targetObjectMethodName=targetClsName+"."+method.getName();System.out.println("获取目标方法名:" + targetObjectMethodName);//获取请求参数Object[] args = point.getArgs();//TODO 操作日志保存到数据库中String logDetailInfo = LogDetailEnums.getDetailByCode(logModelType);switch (LogDetailEnums.getLogDetailEnum(logModelType)) {case CREATE_ACCOUNT:OperateTeacherReq req = JSONObject.parseObject(JSON.toJSONString(args[0]), OperateTeacherReq.class);System.out.println("获取请求参数:" + req);JSONObject jsonObject = JSONObject.parseObject(req.toString());logDetailInfo = MessageFormat.format(logDetailInfo, req.getId(), req.getName(), LogDetailEnums.getDetailByCode("pstnFlag_" + jsonObject.getString("pstnFlag")));break;default:System.out.println("无该类型!");break;}return Response.success(logDetailInfo);}
}
第五步:postman模拟调用接口,输出AOP中ProceedingJoinPoint获取目标方法,参数,注解
常量类详情日志占位符:
public static final String CREATE_ACCOUNT = "【Add Account】id:[{0}], User Name:[{1}],pstnFlag:[{2}]";
postman调用接口
postman结果打印
控制台打印:
获取目标方法上的注解指定的操作名称:public com.example.demo.response.Response com.example.demo.controller.Controller.operateLog(com.example.demo.bean.OperateTeacherReq)
获取自定义注解参数值logModelType:CREATE_ACCOUNT
获取目标方法名:com.example.demo.controller.Controller.operateLog
获取请求参数:{id:1, name:'教师1', pstnFlag:1}
项目代码路径图片