背景:
公司代码要求必须将远程客户端接口使用Wrapper封装,且只能由AppWapper调用。
封装类上需带有@Wrapper或者@AppWapper注解。
源代码如下:看起来是不是很头疼,可以忽略不看下面有详细讲解
package com.cainiao.owms.operate.infra.interceptor;
import com.cainiao.owms.operate.common.errorcode.LayerEnum;
import com.cainiao.owms.operate.common.exception.OwmsSystemException;
import com.cainiao.owms.operate.common.log.LogUtil;
import com.cainiao.owms.operate.infra.annotition.AppWrapper;
import com.google.common.collect.Sets;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.Set;
/**
* wrapper层拦截器
*/
@Component
@Aspect
public class WrapperInterceptor {
@Value("${ACTIVE_PROFILE}")
public String env;
private static final Set validAnnotationSet = Sets.newConcurrentHashSet();
@Pointcut("@within(com.cainiao.owms.operate.infra.annotition.Wrapper)")
public void wrapperPointcut() {
}
@Around("com.cainiao.owms.operate.infra.interceptor.WrapperInterceptor.wrapperPointcut()")
public Object aroundWrapper(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
String typeName = joinPoint.getTarget().getClass().getTypeName();
String clazz = joinPoint.getTarget().getClass().getSimpleName();
String method = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// 有性能问题,仅仅在非生产环境才执行,目的就是为了保证代码分层规范
AnnotationReasonableCheck(typeName + method);
try {
Object result = joinPoint.proceed();
long cost = System.currentTimeMillis() - start;
LogUtil.salLog(clazz, method, cost, args, result);
LogUtil.monitorLog(LayerEnum.WRAPPER, clazz, method, cost, true, null);
return result;
} catch (Throwable t) {
long cost = System.currentTimeMillis() - start;
LogUtil.salErrorLog(clazz, method, cost, args, t);
LogUtil.monitorLog(LayerEnum.WRAPPER, clazz, method, cost, false, null);
throw t;
}
}
/**
* appWrapper注解合理性校验
*/
private void AnnotationReasonableCheck(String classMethod) {
// 有性能问题,仅仅在非生产环境才执行,目的就是为了保证代码分层规范
if (!Objects.equals(env, "pubsig")) {
if (validAnnotationSet.contains(classMethod)) {
return;
}
try {
throw new RuntimeException();
} catch (Exception e) {
int i = 1;
boolean hasAppWrapper = false;
for (StackTraceElement stackTraceElement : e.getStackTrace()) {
if (stackTraceElement.getClassName().contains("sun.reflect.GeneratedMethodAccessor")) {
continue;
}
Class<?> aClass = null;
try {
aClass = Class.forName(stackTraceElement.getClassName());
} catch (Throwable t) {
LogUtil.printLog("类:{},异常:{}", stackTraceElement.getClassName(), t);
}
if (aClass == null) {
continue;
}
if (aClass.getAnnotation(AppWrapper.class) != null) {
hasAppWrapper = true;
break;
}
// 强制规范 是 appWrapper 直接调用 wrapper ,以目前的情况,算上各种代理,调用深度不会超过20层,真的那天有什么特殊情况超过20层,再改
if (i > 20) {
break;
}
i++;
}
if (!hasAppWrapper) {
throw new OwmsSystemException("wrapper层只能appWrapper层调用,其他层禁止调用");
} else {
validAnnotationSet.add(classMethod);
}
}
}
}
}
重点在这:
其实代码的主要想表达,如果你是一个带有@Wrapper注解的类,那么你的方法只能由带有@AppWrapper注解的类调用,否则失败。
ps:仅限线上环境,所以这是一个坑,预发测试没问题,发布后,结果线上炸掉了。
研究源码发现:
学到的知识点:
1.
图中代码含义:如果目前代码在线上环境,谁调我此时我就要拿到该线程下面所有的堆栈信息。并且遍历这些堆栈信息。
2.
图中代码含义:如果是反射调用方法,忽略。接下来,看类的头上有没有加@AppWrapper注解,如果没有,直接抛出一场,那么此处远程调用失败。