使用HandlerMethodArgumentResolver用于統(tǒng)一獲取當(dāng)前登錄用戶
- 環(huán)境:SpringBoot 2.0.4.RELEASE
- 需求:很多Controller方法,剛進(jìn)來要先獲取當(dāng)前登錄用戶的信息,以便做后續(xù)的用戶相關(guān)操作。
- 準(zhǔn)備工作:前端每次請求都傳token,后端封裝一方法tokenUtils.getUserByToken(token),根據(jù)token解析得到currentUserInfo。
這是一個常見的業(yè)務(wù)需求,為實現(xiàn)這個需求,有以下幾種解決方案:
一、最原始直接
即,每個Controller開始,先調(diào)用tokenUtils.getUserByToken(token),不夠優(yōu)雅。
二、AOP
AOP可以解決很多切面類問題,思路同Spring AOP來自定義注解實現(xiàn)審計或日志記錄(完整代碼),將currentUser放到request里;比起攔截器稍重。
三、攔截器+方法參數(shù)解析器
使用mvc攔截器HandlerInterceptor+方法參數(shù)解析器HandlerMethodArgumentResolver最合適。
SpringMVC提供了mvc攔截器HandlerInterceptor,包含以下3個方法:
preHandle
postHandle
afterCompletion
HandlerInterceptor經(jīng)常被用來解決攔截事件,如用戶鑒權(quán)等。
另外,Spring也向我們提供了多種解析器Resolver,如用來統(tǒng)一處理異常的HandlerExceptionResolver,以及今天的主角HandlerMethodArgumentResolver。
HandlerMethodArgumentResolver是用來處理方法參數(shù)的解析器,包含以下2個方法:
supportsParameter
(滿足某種要求,返回true,方可進(jìn)入resolveArgument做參數(shù)處理)resolveArgument
知識儲備已到位,接下來著手實現(xiàn),主要分為三步走:
- 自定義權(quán)限攔截器AuthenticationInterceptor攔截所有request請求,并將token解析為currentUser,最終放到request中;
- 自定義參數(shù)注解@CurrentUser,添加至controller的方法參數(shù)user之上;
- 自定義方法參數(shù)解析器CurrentUserMethodArgumentResolver,取出request中的user,并賦值給添加了@CurrentUser注解的參數(shù)user。
3.1 自定義權(quán)限攔截器
自定義權(quán)限攔截器AuthenticationInterceptor,需實現(xiàn)HandlerInterceptor。
在preHandle中調(diào)用tokenUtils.getUserByToken(token),獲取到當(dāng)前用戶,最后塞進(jìn)request中,如下:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import edp.core.utils.TokenUtils; import edp.core.consts.Consts; import edp.davinci.model.User; public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired private TokenUtils tokenUtils; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); User user = tokenUtils.getUserByToken(token); request.setAttribute(Consts.CURRENT_USER, user); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
3.2 自定義參數(shù)注解
自定義方法參數(shù)上使用的注解@CurrentUser,代表被它注解過的參數(shù)的值都需要由方法參數(shù)解析器CurrentUserMethodArgumentResolver來“注入”,如下:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定義 當(dāng)前用戶 注解 * 注解 參數(shù) * 此注解在驗證token通過后,獲取當(dāng)前token包含用戶 */ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentUser { }
給各Controller類中RequestMapping方法的參數(shù)添加此注解,如下:
import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import edp.davinci.core.common.Constants; import edp.core.annotation.CurrentUser; import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping(value = Constants.BASE_API_PATH + "/download") public class DownloadController { @GetMapping(value = "/page", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity getDownloadRecordPage(@CurrentUser User user, HttpServletRequest request) { ... } }
3.3 自定義方法參數(shù)解析器
自定義方法參數(shù)解析器CurrentUserMethodArgumentResolver,需實現(xiàn)HandlerMethodArgumentResolver。
import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import edp.core.annotation.CurrentUser; import edp.core.consts.Consts; import edp.davinci.model.User; /** * @CurrentUser 注解 解析器 */ public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().isAssignableFrom(User.class) && parameter.hasParameterAnnotation(CurrentUser.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { return (User) webRequest.getAttribute(Consts.CURRENT_USER, RequestAttributes.SCOPE_REQUEST); } }
As we all know,攔截器定義好以后,在SpringMVC項目中,需要去SpringMVC的配置文件springmvc.xml添加該攔截器;但是在SpringBoot中,省去了很多配置文件,取而代之的是被注解@Configuration標(biāo)識的配置類,SpringMVC配置文件對應(yīng)的配置類需繼承WebMvcConfigurationSupport。
同理,解析器定義好以后,也需被添加到SpringMVC的配置文件或配置類中。最后,額外的一步,配置mvc。
3.4 配置MVC
定義MVC配置類,需繼承WebMvcConfigurationSupport。分別在addInterceptors和addArgumentResolvers方法中,添加自定義的攔截器和參數(shù)解析器,如下:
import static edp.core.consts.Consts.EMPTY; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.serializer.ValueFilter; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import edp.davinci.core.common.Constants; import edp.davinci.core.inteceptor.AuthenticationInterceptor; import edp.davinci.core.inteceptor.CurrentUserMethodArgumentResolver; @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Value("${file.userfiles-path}") private String filePath; /** * 登錄校驗攔截器 * * @return */ @Bean public AuthenticationInterceptor loginRequiredInterceptor() { return new AuthenticationInterceptor(); } /** * CurrentUser 注解參數(shù)解析器 * * @return */ @Bean public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() { return new CurrentUserMethodArgumentResolver(); } /** * 參數(shù)解析器 * * @param argumentResolvers */ @Override protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(currentUserMethodArgumentResolver()); super.addArgumentResolvers(argumentResolvers); } @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginRequiredInterceptor()) .addPathPatterns(Constants.BASE_API_PATH + "/**") .excludePathPatterns(Constants.BASE_API_PATH + "/login"); super.addInterceptors(registry); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") .addResourceLocations("classpath:/META-INF/resources/") .addResourceLocations("classpath:/static/page/") .addResourceLocations("classpath:/static/templates/") .addResourceLocations("file:" + filePath); } @Override protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.QuoteFieldNames, SerializerFeature.WriteEnumUsingToString, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.DisableCircularReferenceDetect); fastJsonConfig.setSerializeFilters((ValueFilter) (o, s, source) -> { if (null != source && (source instanceof Long || source instanceof BigInteger) && source.toString().length() > 15) { return source.toString(); } else { return null == source ? EMPTY : source; } }); //處理中文亂碼問題 List<MediaType> fastMediaTypes = new ArrayList<>(); fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); fastConverter.setSupportedMediaTypes(fastMediaTypes); fastConverter.setFastJsonConfig(fastJsonConfig); converters.add(fastConverter); } }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Spring 框架中切入點 pointcut 表達(dá)式的常用寫法
這篇文章主要介紹了詳解Spring 框架中切入點 pointcut 表達(dá)式的常用寫法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04Windows中在IDEA上安裝和使用JetBrains Mono字體的教程
這篇文章主要介紹了Windows IDEA上安裝和使用JetBrains Mono字體的教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03Java?數(shù)據(jù)結(jié)構(gòu)深入理解ArrayList與順序表
ArrayList?類是一個可以動態(tài)修改的數(shù)組,與普通數(shù)組的區(qū)別就是它是沒有固定大小的限制,我們可以添加或刪除元素。ArrayList?繼承了?AbstractList?,并實現(xiàn)了?List?接口,順序表是將元素順序地存放在一塊連續(xù)的存儲區(qū)里,元素間的順序關(guān)系由它們的存儲順序自然表示2022-04-04SpringCloud?Feign集成AOP的常見問題與解決
在使用?Spring?Cloud?Feign?作為微服務(wù)通信的工具時,我們可能會遇到?AOP?不生效的問題,這篇文章將深入探討這一問題,給出幾種常見的場景,分析可能的原因,并提供解決方案,希望對大家有所幫助2023-10-10Spring MVC配置雙數(shù)據(jù)源實現(xiàn)一個java項目同時連接兩個數(shù)據(jù)庫的方法
這篇文章主要給大家介紹了關(guān)于Spring MVC如何配置雙數(shù)據(jù)源實現(xiàn)一個java項目同時連接兩個數(shù)據(jù)庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-05-05