前面我寫了一篇《Token認證,如何快速方便獲取用戶信息》的文章,引發了各位讀者的積極參與,除了文章中我提出的三種方式,各位讀者大佬們也貢獻了其餘多種實現方式。web
今天決定基於你們提供的思路再寫一篇文章,主要是有讀者留言說想要知道其餘的實現方式,沒辦法,只能本身先研究下,而後分享出來,我就是這麼寵讀者,哈哈。bash
總結起來就是ThreadLocal,AOP,HandlerMethodArgumentResolver這三種方式,固然這些都是別人提供的方案,也許他們在實際工做中使用過,我本人是沒接觸過這塊,可是我臨時去實現了一下,不知道是否是跟各位留言中的實現一致,可是效果確定是實現了的。app
僅供你們參考,寫的很差不要嘲笑我哈。框架
若是用ThreadLocal的話也挺簡單的,在過濾器中解析Token以後將用戶ID set 到ThreadLocal中,在Controller中get就能夠獲取到了,以下:ide
// 定義
public static ThreadLocal<Long> loginUserThreadLocal = new ThreadLocal<>();
// 設置
loginUserThreadLocal.set(userId);
// 獲取
loginUserThreadLocal.get()
複製代碼
須要注意的是:若是你的Controller方法用了@HystrixCommand註解,意味着這個方法執行的線程就是hystrix的線程了,過濾器中是容器的線程,這個時候用ThreadLocal是獲取不到值的,這就涉及到了一個跨線程傳遞的問題了,我以前也有寫過相似的文章,用的是transmittable-thread-local這個框架來解決的。ui
文章能夠參考這2篇:spa
還有一位朋友提到了ThreadLocal+AOP的方式,我想他的意思應該是從Filter中解析出用戶ID, 而後存儲到ThreadLocal中,在AOP中獲取ThreadLocal中的用戶ID, 而後注入到參數中,這樣感受整個操做流程都變長了。code
咱們仍是按照這個思路來實現下吧:
咱們直接在切面中對參數進行修改,最簡單的方式是直接獲取參數列表,而後修改,好比:
Object[] args = joinPoint.getArgs();
args[1] = 用戶ID;
return joinPoint.proceed(args);
複製代碼
這段代碼很明顯很差,由於經過下標的方式去修改參數,也就意味着全部的接口方法都得將參數放在固定的位置,以下:
@GetMapping("/article/callHello")
public String callHello(String name, Long userId) {
// userId 能夠獲取到值
}
複製代碼
正如前面有位朋友提到的,能夠自定義註解來標識,除了普通的參數,還有實體類這種參數,因此自定義註解是一個比較好的方式。若是不自定義註解,那麼就是基於約定的方式,約定好變量名也行,前面咱們講的都是基於約定來的。
咱們基於約定好的變量名來說解,反射獲取方法名不是很方便,當jdk1.8中其實已經支持了,爲了簡化,咱們能夠用註解的方式來獲取參數名稱,固然這個註解你能夠自定義,也能夠用一些現成的,好比@RequestParam:
@GetMapping("/article/callHello")
public String callHello(String name, @RequestParam(name="userId",required=false)Long userId) {
}
複製代碼
這樣咱們在切面中能夠獲取當前訪問方法中的參數註解列表,而後獲取到對應的名稱進行匹配,再進行參數值的替換:
Object[] args = joinPoint.getArgs();
Object target = joinPoint.getTarget();
// 方法名
String methodName = joinPoint.getSignature().getName();
Class<?> clz = target.getClass();
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
// 匹配當前訪問的方法
if (methodName.equals(method.getName())) {
// 獲取參數註解
Annotation[][] parameterAnnotaions = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotaions.length; i++) {
Annotation[] oneParameterAnnotaions = parameterAnnotaions[i];
for (int j = 0; j < oneParameterAnnotaions.length; j++) {
// 匹配註解
if (oneParameterAnnotaions[j].annotationType() == RequestParam.class) {
RequestParam param = (RequestParam) oneParameterAnnotaions[j];
// 匹配參數名稱
if (param.name().equals("userId")) {
// 設置參數值
args[i] = 用戶ID;
}
}
}
}
}
}
result = joinPoint.proceed(args);
複製代碼
這邊須要注意的是我這邊比對當前方法是直接經過方法名去對比的,會存在一個問題就是若是有相同名稱的方法就會出問題,建議你們仍是要加上參數的對比,獲取直接根據class和方法名稱和參數列表進行反射動態獲取。這邊只爲了演示跟你們說明下,我仍是建議用過濾器的方式實現,更簡單點。
SpringMVC提供了HandlerMethodArgumentResolver接口來處理咱們的自定義參數的解析。 咱們能夠利用這個功能將用戶登陸的信息綁定到參數中。
最好的方式是單獨加一個用戶信息實體類,直接做爲一個參數進行注入,使用也方便,首先咱們定義一個參數類:
@Data
public class LoginUser {
private Long userId;
}
複製代碼
而後定義一個註解,用來標識是否要注入用戶參數信息:
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUserAnno {
}
複製代碼
實現HandlerMethodArgumentResolver接口,自定義參數注入的邏輯:
public class LoginUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(LoginUserAnno.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
LoginUser user = new LoginUser();
user.setUserId(用戶ID);
return user;
}
}
複製代碼
配置HandlerMethodArgumentResolver:
@Configuration
public class Config implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new LoginUserMethodArgumentResolver());
}
}
複製代碼
使用的話就很簡單了,以下:
@PostMapping("/add")
public User add(@LoginUserAnno LoginUser loginUser, User user) {
return user;
}
@GetMapping("/article/callHello")
public String callHello(String name, @LoginUserAnno LoginUser loginUser) {
}
複製代碼
loginUser會自動進行注入,而後就能夠拿到咱們想要的數據了,這個實際上是屬於參數注入這塊的,在這邊作驗證顯示不合適,驗證仍是得在過濾器中作,那麼問題就是驗證完後,拿到用戶ID還得傳遞到HandlerMethodArgumentResolver中才能夠徹底注入的效果,咱們能夠用ThreadLocal傳遞,或者請求頭,或者參數等方式均可以,由於在HandlerMethodArgumentResolver中能夠獲取到這些信息。
webRequest.getParameter("name");
webRequest.getHeader("xxx");
複製代碼
若是真要傳遞的話推薦下面的方式:
// Filter中
httpRequest.setAttribute("userId", 100);
// HandlerMethodArgumentResolver中
webRequest.getAttribute("userId", WebRequest.SCOPE_REQUEST);
複製代碼
文章導到這裏就所有結束了,講解了這麼多方式,我我的認爲最優的仍是在Filter中實現。
推薦理由: