利用AOP和ThreadLocal处理接口的公共参数

488 阅读2分钟
  1. 背景 当我们项目中好些接口有共同的参数,我们正常都这样抽成
@Data
public class PublicParam {
    /**用户ID*/
    private Long userId;
    
}

@Data
public class CreateCartDTO extends PublicParam {
    /**产品ID*/
    private Long productId;
    /**产品skuID*/
    private Long skuId;
    /**购买数量*/
    private Integer num = 1;
}

但是当我们的调用链路很长的时候,我特么拒绝这么做,来回的传参数太烦了

所以我决定这么做

  1. 解决方案
封装的公共参数上下文

public class ParamContext {
    public static final String USER_ID = "userId";
    public static final String IP = "ip";
    public static final String VERSION = "version";
    public static final String CLIENT = "client";
    /**
     * 参数缓存
     */
    private static final ThreadLocal<HashMap<String, Object>> cache =
            ThreadLocal.withInitial(HashMap::new);

    /**
     * 数据清理
     */
    public static void clean() {
        cache.remove();
    }
    public static Long getUserId() {
        return null != cache.get().get(USER_ID) ? Long.valueOf(toString(cache.get().get(USER_ID))) : null;
    }
    public static void setUserId(String userId) {
        if (!StringUtils.hasLength(userId)) {
            return;
        }
        cache.get().put(USER_ID, Long.valueOf(userId));
    }
    private static String toString(Object o) {
        if (null == o) {
            return null;
        }
        return String.valueOf(o);
    }
}
识别AOP的注解,个人偏向于注解,好用,不要那些*号点来点去,容易出错
@Retention(RUNTIME)
@Target(METHOD)
public @interface PublicParamFlag {
}
封装的切面
注意为了防止内存泄露,我加了finally
@Aspect
@Component
public class PublicParamAspect {

    @Pointcut("@annotation(com.roy.annotation.PublicParamFlag)")
    private void paramPointCut() {
    }

    @Around(value = "paramPointCut()")
    public Object around(ProceedingJoinPoint pjt) throws Throwable {
        try {
            Object[] args = pjt.getArgs();
            PublicParam publicParam = (PublicParam) args[0];
            Long userId = publicParam.getUserId();
            // 设置各参数到ThreadLocal的cache中
            ParamContext.setUserId(String.valueOf(userId));
            return pjt.proceed();
        } finally {
            // very import,必须调用clean方法进行ThreadLocal变量的remove
            ParamContext.clean();
        }
    }
}
  1. controller和service中的运用

@RequestMapping("/user4")
@RestController
@Slf4j
public class UserController4 {

  @RequestMapping("/save")
  @PublicParamFlag
  public String save(@RequestBody CreateCartDTO dto) {
    System.out.println(JSON.toJSONString(dto));
    Long userId = ParamContext.getUserId();
    return String.valueOf(userId);
  }


}
  1. 总结

利用ThreadLocal在一条链路上进行参数的传播,并且不写传参,参数太多、链路太长就可以这么做。

  1. 其实写的代码还挺多的,那我发现了spring框架其实针对这个轮子是有做封装的,接下来给大家演示一下了,上demo
@RequestMapping("/save/{id}")
public String save(@PathVariable  Integer id) {
  RequestContextHolder.currentRequestAttributes().setAttribute("id", id, RequestAttributes.SCOPE_REQUEST);
  return requestService.getStr();
}


@Component
public class RequestService {
    public String getStr(){
        Object id = RequestContextHolder.getRequestAttributes().getAttribute("id", RequestAttributes.SCOPE_REQUEST);
        return "id:" + id;
    }
}
  1. 轮子总结
    虽然我没有把RequestContextHolder.currentRequestAttributes().setAttribute("id", id, RequestAttributes.SCOPE_REQUEST);
    这个写在aop中,但也是实现了controller和service之间进行参数的传递