前言:
该篇以记录接口调用的传入参数日志为场景,来介绍下使用自定义注解作为切点,AOP切面方式去记录每个接口的传入参数以及可扩展的业务处理。
正文:
项目目录:
先是创建自定义注解, LogTrack:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Author : JCccc * @CreateTime : 2020/4/13 * @Description : **/ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface LogTrack { String value() default "logTracking"; }
上面的自定义注解,我只用了一个默认参,所以不用命名,写value就可以。
然后写切点对应的代码,LogTrackAspect:
用到了fastjson,导入依赖:
<!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency>
/** * @Author : JCccc * @CreateTime : 2020/4/13 * @Description : **/ @Component @Aspect public class LogTrackAspect { private static final Logger log = LoggerFactory.getLogger(LogTrackAspect.class); //这里需要注意了,这个是将自己自定义注解作为切点的根据,路径一定要写正确了 @Pointcut(value = "@annotation(com.jc.mytest.aop.logRecord.LogTrack)") public void access() { } //进来切点世界,先经过的第一个站 @Before("access()") public void doBefore(JoinPoint joinPoint) throws Throwable { System.out.println("-aop 日志记录启动-" + new Date()); } //环绕增强,是在before前就会触发 @Around("@annotation(logTrack)") public Object around(ProceedingJoinPoint pjp, LogTrack logTrack) throws Throwable { System.out.println("-aop 日志环绕阶段-" + new Date()); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // GET 请求其实可以从request里获取出参数 // Map<String,String[]> map=request.getParameterMap(); // System.out.println("获取参数:"+map.get("username")[0]) String url = request.getRequestURL().toString(); String ip = IpUtil.getIpAddr(request); String logTrackValue = logTrack.value(); Object[] pipArrary = pjp.getArgs(); if (pipArrary.length>1){ //多参,不是Map/JsonObject方式 List<Object> argList = new ArrayList<>(); for (Object arg : pjp.getArgs()) { // request/response无法使用toJSON if (arg instanceof HttpServletRequest) { argList.add("request"); } else if (arg instanceof HttpServletResponse) { argList.add("response"); } else { argList.add(JSON.toJSON(arg)); } } Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; // 参数名数组 String[] parameterNames = ((MethodSignature) signature).getParameterNames(); System.out.println("参数名数组:"+new ArrayList(Arrays.asList(parameterNames))); System.out.println("参数是:"+argList.toString()); System.out.println("logTrackValue:"+logTrackValue); System.out.println("url:"+url); System.out.println("ip:"+ip); return pjp.proceed(); } Object param = pipArrary[0]; System.out.println("logTrackValue:"+logTrackValue); System.out.println("url:"+url); System.out.println("ip:"+ip); System.out.println("param:"+param.toString()); return pjp.proceed(); } //进来切点这,最后经过的一个站,也是方法正常运行结束后 @After("access()") public void after(JoinPoint joinPoint) { System.out.println("-aop 日志记录结束-" + new Date()); } }
ps:return pjp.proceed(); 这个是从切点的环绕增强里面脱离出来,接下来会进入before阶段 ,然后回到接口,再回来after阶段。
所以扩展业务逻辑处理的话,可以放在return pjp.proceed();这行代码之前,例如判断用户密码是否正确;判断用户权限等等。
接下来是在Controller编写接口,并用上自定义注解,MyTestController:
/** * @Author : JCccc * @CreateTime : 2020/3/27 * @Description : **/ @Controller @RequestMapping("/test") public class MyTestController { @ResponseBody @GetMapping("/testGet1") @LogTrack("testGet1 接口") public void testGet1(@RequestParam("userId") String userId, @RequestParam("toUserId") String toUserId) { System.out.println("已经进入GET测试接口,参数userId:" + userId+ "参数toUserId:"+toUserId); } @ResponseBody @GetMapping("/testGet2") @LogTrack("testGet2 接口") public void testGet2(@RequestParam Map map) { System.out.println("已经进入GET测试接口,参数:" + map.get("userId")); } @ResponseBody @PostMapping("/testPost1") @LogTrack("testPost1 接口") public void testPost1(@RequestBody Map map) { System.out.println("已经进入POST测试接口,参数:" + map.toString()); } @ResponseBody @PostMapping("/testPost2") @LogTrack("testPost2 接口") public void testPost2(@RequestBody JSONObject jsonObject) { System.out.println("已经进入POST测试接口,参数:" + jsonObject.toString()); } }
然后我们来运行一下,看看控制台,就知道整个切点以及环绕的流程了:
首先是测试GET方式的接口,通过@RequestParam单个参数获取的情况:
调用接口:
运行结果:
接下来还是GET方式 ,通过Map去接收多参:
调用接口:
运行结果:
然后是调用Post请求:
ps: 如果发现按照上面配置了,但是aop切点的方法好像没触发,
那么可以试试
1.检查jar包是否有导入正确
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency>
2.在启动类上加上注解@EnableAspectJAutoProxy 这个其实springboot是默认帮我们已经开启为true状态的。
本篇使用到的一些工具类:
IpUtil:
import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; /** * @Author : JCccc * @CreateTime : 2018-11-23 * @Description : * @Point: Keep a good mood **/ public class IpUtil { public static String getIpAddr(HttpServletRequest request) { String ipAddress = null; 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 (ipAddress.equals("127.0.0.1")) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress=""; } // ipAddress = this.getRequest().getRemoteAddr(); return ipAddress; } }