本文不介绍spring aop的基础知识,只演示实现一个自定义注解的流程,但是读者应该对注解的基本概念,切面、切点、通知等的基本概念有所了解。
项目基于Spring boot+maven+java8
我想要在某些controller方法中记录请求日志,包括ip,方法,请求入参,返回结果,执行耗时等等。
那么我们可以通过自定义一个注解实现,加在需要打印日志的方法上即可。
各个公司内部的实现逻辑可能有些差别,但大同小异。
首先展示一下项目的基本结构:
项目的pom文件:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.3
com.ztt
aop_demo
0.0.1-SNAPSHOT
aop_demo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-aop
org.projectlombok
lombok
com.alibaba
fastjson
1.2.68
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
首先,我们需要自定义一个注解,代码如下。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface EagleEye {
// 注解的属性。属性不是必须的,可以没有,根据公司的实际业务需要去定义
// String value();
// String name() default "";
}
@Retention(RetentionPolicy.RUNTIME) :
这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
@Target
定义注解的作用目标。@Target(ElementType.METHOD)表示该注解可以作用于方法上。
@Documented
用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
这个时候,注解就定义完成了,可以加在方法上使用了:
import com.alibaba.fastjson.JSONObject;
import com.ztt.annotation.EagleEye;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping(value = "/hello")
public class HelloController {
@GetMapping(value = "/testEagleEye")
// 使用自定义注解
@EagleEye
public Object testEagleEye(@RequestBody JSONObject params) {
log.info("params:{}", params);
return "success";
}
}
然后浏览器或者postman调用这个方法,会发现ip,返回结果,执行耗时什么的根本没有打印出来。
那肯定是不会打印的,因为我们只是定义了一个注解,并没有任何的代码去解析这个注解(所谓解析,就是说在编译期或运行时检测到注解,并进行特殊操作)。因此,单单定义一个注解是没有任何卵用的,我们必须去实现aop做相关操作才有意义。
那么怎么去解析这个注解呢?当然是需要我们写aop代码了。
上一步我们自定义了一个注解@EagleEye ,并把它加到了controller的方法testEagleEye上,但是发现这个注解并没有啥卵用。
只有实现了aop,注解才能真正的起作用。
这一步,我们就去写代码实现aop,解析这个注解,代码如下:
import com.ztt.annotation.EagleEye;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public class EagleEyeAop {
/**
* 切点表达式。此处的切点表达式会,匹配所有@EagleEye注解修饰的方法
*/
@Pointcut("@annotation(com.ztt.annotation.EagleEye)")
public void eagleEye() {
}
/**
* 环绕通知
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("eagleEye()")
public Object eagleEyeAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTimeMillis = System.currentTimeMillis();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
EagleEye eagleEyeAnnotation = method.getAnnotation(EagleEye.class);
log.info(">>>>>> request start >>>>>>");
log.info("请求链接:{}", httpServletRequest.getRequestURL().toString());
log.info("请求类型:{}", httpServletRequest.getMethod());
log.info("请求方法:{}", signature.getDeclaringTypeName(), signature.getName());
log.info("请求ip:{}", httpServletRequest.getRemoteAddr());
log.info("请求入参:{}", joinPoint.getArgs());
Object proceedResult = null;
try {
// 执行目标方法
proceedResult = joinPoint.proceed();
} catch (Exception e) {
log.error("目标方法执行异常:", e);
throw e;
}
//
long endTimeMillis = System.currentTimeMillis();
log.info("请求耗时:{}ms", endTimeMillis - startTimeMillis);
//
log.info("目标方法执行结果:{}", proceedResult);
log.info(">>>>>> request end >>>>>>");
return proceedResult;
}
}
实现aop之后,我们再去调用上面的controller方法,可以看到控制台打印了日志: