定义切面注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 切面注解
* @Description 用于打印请求和响应日志的注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLog {
String[] inIgnoreValues() default {};
String[] outIgnoreValues() default {};
String apiName() default "";
}
方法增强处理器
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 方法增强处理器
* @param <R> 目标方法返回值的类型
*/
public interface MethodAdviceHandler<R> {
/**
* 目标方法执行之前的判断,判断目标方法是否允许执行。默认返回 true,即 默认允许执行
*
* @param point 目标方法的连接点
* @return 返回 true 则表示允许调用目标方法;返回 false 则表示禁止调用目标方法。
* 当返回 false 时,此时会先调用 getOnForbid 方法获得被禁止执行时的返回值,然后
* 调用 onComplete 方法结束切面
*/
default boolean onBefore(ProceedingJoinPoint point) {
return true;
}
/**
* 禁止调用目标方法时(即 onBefore 返回 false),执行该方法获得返回值,默认返回 null
*
* @param point 目标方法的连接点
* @return 禁止调用目标方法时的返回值
*/
default R getOnForbid(ProceedingJoinPoint point) {
return null;
}
/**
* 目标方法抛出异常时,执行的动作
*
* @param point 目标方法的连接点
* @param e 抛出的异常
* @throws Throwable
*/
void onThrow(ProceedingJoinPoint point, Throwable e) throws Throwable;
/**
* 获得抛出异常时的返回值,默认返回 null
*
* @param point 目标方法的连接点
* @param e 抛出的异常
* @return 抛出异常时的返回值
*/
default R getOnThrow(ProceedingJoinPoint point, Throwable e) {
return null;
}
/**
* 目标方法完成时,执行的动作
*
* @param point 目标方法的连接点
* @param startTime 执行的开始时间
* @param permitted 目标方法是否被允许执行
* @param thrown 目标方法执行时是否抛出异常
* @param result 执行获得的结果
*/
default void onComplete(ProceedingJoinPoint point, long startTime, boolean permitted, boolean thrown, Object result) {
}
}
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* 方法增强器基础类
* @param <R>
*/
@Slf4j
public abstract class BaseMethodAdviceHandler<R> implements MethodAdviceHandler<R> {
/**
* 抛出异常时候的默认处理
*/
@Override
public void onThrow(ProceedingJoinPoint point, Throwable e) throws Throwable {
String methodDesc = getMethodDesc(point);
Object[] args = point.getArgs();
log.error("{} 执行时出错,入参={}", methodDesc, args, e);
}
/**
* 获得被代理的方法
*
* @param point 连接点
* @return 代理的方法
*/
protected Method getTargetMethod(ProceedingJoinPoint point) {
// 获得方法签名
Signature signature = point.getSignature();
// Spring AOP 只有方法连接点,所以 Signature 一定是 MethodSignature
return ((MethodSignature) signature).getMethod();
}
/**
* 获得方法描述,目标类名.方法名
*
* @param point 连接点
* @return 目标类名.执行方法名
*/
protected String getMethodDesc(ProceedingJoinPoint point) {
// 获得被代理的类
Object target = point.getTarget();
String className = target.getClass().getSimpleName();
Signature signature = point.getSignature();
String methodName = signature.getName();
return className + "." + methodName;
}
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.MDC;
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;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
/**
* 日志方法增强处理器的实现
*
*/
@Slf4j
@Component
public class ApiLogAdviceHandler extends BaseMethodAdviceHandler<Object> {
/**
* 会话ID
*/
private final static String SESSION_KEY = "sessionId";
/**
* 在进入controller之前拦截并打印请求报文日志
*
* @param point 目标方法的连接点
* @return
*/
@Override
public boolean onBefore(ProceedingJoinPoint point) {
String token = UUID.randomUUID().toString().replace("-", "");
MDC.put(SESSION_KEY, token);
Object[] args = point.getArgs();
Method method = getTargetMethod(point);
ApiLog apiLog = method.getAnnotation(ApiLog.class);
String[] ignoreValues = apiLog.inIgnoreValues();
SimplePropertyPreFilter propertyPreFilter = new SimplePropertyPreFilter();
if (ignoreValues.length > 0) {
Arrays.stream(ignoreValues).forEach(item -> propertyPreFilter.getExcludes().add(item));
}
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (Objects.isNull(requestAttributes)) {
log.info(
"==> 非http请求:" + "==> 请求报文:" + (args.length > 0 ? JSON.toJSONString(args[0], propertyPreFilter) : ""));
return true;
}
HttpServletRequest request = requestAttributes.getRequest();
String ip = HttpUtils.getIpAddress(request);
log.info("==> 请求者IP:" + ip + "\n" + "==> 请求接口:" + request.getMethod() + " " + request.getRequestURL() + "\n"
+ "==> 请求报文:" + (args.length > 0 ? JSON.toJSONString(args[0], propertyPreFilter) : ""));
return true;
}
@Override
public void onThrow(ProceedingJoinPoint point, Throwable e) throws Throwable {
throw e;
}
/**
* 返回信息后,打印响应报文的日志
*
* @param point 目标方法的连接点
* @param startTime 执行的开始时间
* @param permitted 目标方法是否被允许执行
* @param thrown 目标方法执行时是否抛出异常
* @param result 执行获得的结果
*/
@Override
public void onComplete(ProceedingJoinPoint point, long startTime, boolean permitted, boolean thrown,
Object result) {
long costTime = System.currentTimeMillis() - startTime;
Method method = getTargetMethod(point);
ApiLog apiLog = method.getAnnotation(ApiLog.class);
String[] ignoreValues = apiLog.outIgnoreValues();
SimplePropertyPreFilter propertyPreFilter = new SimplePropertyPreFilter();
if (ignoreValues.length > 0) {
Arrays.stream(ignoreValues).forEach(item -> propertyPreFilter.getExcludes().add(item));
}
log.info("<== 响应报文:{}, 耗时:{}ms", JSON.toJSONString(result, propertyPreFilter), costTime);
MDC.remove(SESSION_KEY);
}
}
切面类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* 切面父类
*/
public abstract class BaseMethodAspect implements ApplicationContextAware {
/**
* 切点,通过 @Pointcut 指定相关的注解
*/
protected abstract void pointcut();
/**
* 对目标方法进行环绕增强处理,子类需通过 pointcut() 方法指定切点
*
* @param point 连接点
* @return 方法执行返回值
*/
@Around("pointcut()")
public Object advice(ProceedingJoinPoint point) throws Throwable {
// 获得切面绑定的方法增强处理器的类型
Class<? extends MethodAdviceHandler<?>> handlerType = getAdviceHandlerType();
// 从 Spring 上下文中获得方法增强处理器的实现 Bean
MethodAdviceHandler<?> adviceHandler = appContext.getBean(handlerType);
// 使用方法增强处理器对目标方法进行增强处理
return advice(point, adviceHandler);
}
/**
* 获得切面绑定的方法增强处理器的类型
*
* @return
*/
protected abstract Class<? extends MethodAdviceHandler<?>> getAdviceHandlerType();
/**
* 使用方法增强处理器增强被注解的方法
*
* @param point 连接点
* @param handler 切面处理器
* @return 方法执行返回值
*/
private Object advice(ProceedingJoinPoint point, MethodAdviceHandler<?> handler) throws Throwable {
// 执行之前,返回是否被允许执行
boolean permitted = handler.onBefore(point);
// 方法返回值
Object result;
// 是否抛出了异常
boolean thrown = false;
// 开始执行的时间
long startTime = System.currentTimeMillis();
// 目标方法被允许执行
if (permitted) {
try {
// 执行目标方法
result = point.proceed();
} catch (Throwable e) {
// 抛出异常
thrown = true;
// 处理异常
handler.onThrow(point, e);
// 抛出异常时的返回值
result = handler.getOnThrow(point, e);
}
} else {
// 目标方法被禁止执行,禁止执行时的返回值
result = handler.getOnForbid(point);
}
// 结束
handler.onComplete(point, startTime, permitted, thrown, result);
return result;
}
private ApplicationContext appContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}
}
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
*
* 日志切面类
*/
@Aspect
@Component
@Slf4j
@Order(1)
public class ApiLogAspect extends BaseMethodAspect {
/**
* 定义空方法用于切点表达式
*/
@Override
@Pointcut("@annotation(com.baizhiedu.aop.ApiLog)")
public void pointcut() {
}
@Override
protected Class<? extends MethodAdviceHandler<?>> getAdviceHandlerType() {
return ApiLogAdviceHandler.class;
}
}
HttpUtils
import javax.servlet.http.HttpServletRequest;
public class HttpUtils {
private static final String UNKNOWN = "unknown";
/**
* 获取真实ip地址,避免获取代理ip
*/
public static 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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}