Spring Boot的AOP(面向切面编程)原理是基于动态代理实现的。
在Spring Boot中,AOP通过代理模式对目标对象进行包装,实现在目标对象的方法执行前后增加额外的逻辑。AOP可以在不修改目标对象的情况下,通过代理对象对目标对象进行方法的增强。
Spring Boot中的AOP主要使用了两种代理方式:JDK动态代理和CGLIB动态代理。
当目标对象的方法被调用时,AOP框架会根据切面定义和通知定义来决定是否需要对目标对象的方法进行增强。如果需要增强,AOP框架会通过代理对象来调用目标对象的方法,并在调用前后执行通知中定义的增强逻辑。
在开发应用系统的时候,我需要了解什么接口是什么IP在访问,用时多少,用什么参数来请求,请求之后的结果返回的事情全部记录下来,用来帮助自己分析本系统的热点数据、接口访问频率,访问地址等等信息,可以根据这些信息做系统的改进策略。
CREATE TABLE `api_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`ip_addr` varchar(100) DEFAULT NULL COMMENT '访问IP地址',
`uri` varchar(500) DEFAULT NULL COMMENT '请求接口地址',
`method_name` varchar(255) DEFAULT NULL COMMENT '请求接口方法名',
`description` varchar(255) DEFAULT NULL COMMENT '请求接口描述',
`duration` bigint(20) DEFAULT NULL COMMENT '请求接口用时',
`start_time` datetime DEFAULT NULL COMMENT '请求开始时间',
`end_time` datetime DEFAULT NULL COMMENT '请求结束时间',
`params` text COMMENT '请求接口参数',
`log_result` text COMMENT '请求接口返回结果',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=84110 DEFAULT CHARSET=utf8;
此类用于注解在控制器的方法类上。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
/**
* 日志信息描述
* @return
*/
String value() default "";
/**
* 是否保存到数据库
* @return
*/
boolean save() default true;
/**
* 是否输出到控制台
* @return
*/
boolean console() default true;
}
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.NetUtil;
import com.alibaba.fastjson.JSON;
import com.api.annotation.Loggable;
import com.api.entity.ApiLog;
import com.api.service.ApiLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 操作开始时间
*/
private Long startTime = 0L;
@Resource
private ApiLogService apiLogService;
/**
* Controller层切点
*/
@Pointcut("execution(* com.api.controller..*.*(..))")
public void controllerAspect() {
}
/**
* 前置通知 在方法前使用
*/
@Before("controllerAspect()")
public void beforeControllerLog(JoinPoint joinpoint){
}
@Around("controllerAspect()")
public Object aroundControllerLog(ProceedingJoinPoint point) throws Throwable{
//获取Request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//获取目标方法
Method method = ((MethodSignature) point.getSignature()).getMethod();
//判断是否有@Loggable注解
if(!method.isAnnotationPresent(Loggable.class)){
//如果没有直接返回结果
return point.proceed();
}
//记录日志信息
ApiLog logMessage = new ApiLog();
/**
* 获取方法上的注解实例
*/
Loggable loggable = method.getAnnotation(Loggable.class);
//处理接口请求日志
handleRequestLog(point, loggable, request, logMessage);
//执行目标方法内容,获取执行结果
Object result = point.proceed();
//处理接口响应日志
handleResponseLog(logMessage, loggable, result);
return result;
}
private void handleRequestLog(ProceedingJoinPoint point, Loggable loggable, HttpServletRequest request, ApiLog logMessage) {
//类名
String targetName = point.getTarget().getClass().toString();
//方法名称
String methodName = point.getSignature().getName();
Map<String, Object> params = getMethodParamNames(point);
//判断是否输出日志
if(loggable.console()){
log.info("targetName:{},methodName:{},params:{}",targetName,methodName,params);
}
startTime = System.currentTimeMillis();
logMessage.setStartTime(DateUtil.date());
logMessage.setDescription(loggable.value());
logMessage.setMethodName(methodName);
logMessage.setAppKey(request.getParameter("appKey"));
logMessage.setUri(request.getRequestURI());
logMessage.setIpAddr(getIpAddress(request)==null?request.getRemoteAddr():getIpAddress(request));
logMessage.setParams(JSON.toJSONString(params));
}
private void handleResponseLog(ApiLog logMessage, Loggable loggable, Object result) {
/**
* 操作结束时间
*/
Long endTime = System.currentTimeMillis();
Long duration = endTime - startTime;
logMessage.setDuration(duration);
logMessage.setEndTime(DateUtil.date());
logMessage.setLogResult(JSON.toJSONString(result));
if(loggable.save()){
//这里考虑用异步操作进行优化
apiLogService.save(logMessage);
}
if(loggable.console()){
log.info("方法执行所需时间 : {} , 输出的结果 : {}",duration,result);
}
}
//获取请求方法的参数
private static Map<String, Object> getMethodParamNames(ProceedingJoinPoint point) {
Map<String, Object> param = new HashMap<>();
Object[] paramValues = point.getArgs();
String[] paramNames = ((CodeSignature) point.getSignature()).getParameterNames();
for (int i = 0; i < paramNames.length; i++) {
param.put(paramNames[i], paramValues[i]);
}
return param;
}
// 获取代理前的IP
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip.contains(",")) {
return ip.split(",")[0];
} else {
return ip;
}
}
}
上述代码即可实现保存接口访问的信息。