HandlerMethodArgumentResolver用於統一獲取當前登錄用戶
- 環境:SpringBoot 2.0.4.RELEASE
- 需求:很多Controller方法,剛進來要先獲取當前登錄用戶的信息,以便做後續的用戶相關操作。
- 準備工作:前端每次請求都傳token,後端封裝一方法tokenUtils.getUserByToken(token),根據token解析得到currentUserInfo。
這是一個常見的業務需求,爲實現這個需求,有以下幾種解決方案:
一、最原始直接
即,每個Controller開始,先調用tokenUtils.getUserByToken(token),不夠優雅。
二、AOP
AOP可以解決很多切面類問題,思路同Spring AOP來自定義註解實現審計或日誌記錄(完整代碼),將currentUser放到request裏;比起攔截器稍重。
三、攔截器+方法參數解析器
使用mvc攔截器HandlerInterceptor+方法參數解析器HandlerMethodArgumentResolver最合適。
SpringMVC提供了mvc攔截器HandlerInterceptor,包含以下3個方法:
- preHandle
- postHandle
- afterCompletion
HandlerInterceptor經常被用來解決攔截事件,如用戶鑑權等。另外,Spring也向我們提供了多種解析器Resolver,如用來統一處理異常的HandlerExceptionResolver,以及今天的主角HandlerMethodArgumentResolver。HandlerMethodArgumentResolver是用來處理方法參數的解析器,包含以下2個方法:
- supportsParameter(滿足某種要求,返回true,方可進入resolveArgument做參數處理)
- resolveArgument
知識儲備已到位,接下來着手實現,主要分爲三步走:
- 自定義權限攔截器AuthenticationInterceptor攔截所有request請求,並將token解析爲currentUser,最終放到request中;
- 自定義參數註解@CurrentUser,添加至controller的方法參數user之上;
- 自定義方法參數解析器CurrentUserMethodArgumentResolver,取出request中的user,並賦值給添加了@CurrentUser註解的參數user。
3.1 自定義權限攔截器
自定義權限攔截器AuthenticationInterceptor,需實現HandlerInterceptor。在preHandle中調用tokenUtils.getUserByToken(token),獲取到當前用戶,最後塞進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.davinci.core.common.Constants;
- 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(Constants.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 自定義參數註解
自定義方法參數上使用的註解@CurrentUser,代表被它註解過的參數的值都需要由方法參數解析器CurrentUserMethodArgumentResolver來“注入”,如下:
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- /**
- * 自定義 當前用戶 註解
- * 註解 參數
- * 此註解在驗證token通過後,獲取當前token包含用戶
- */
- @Target({ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface CurrentUser {
- }
給各Controller類的參數添加此註解,如下:
public ResponseEntity getDownloadRecordPage(@CurrentUser User user, HttpServletRequest request){...}
3.3 自定義方法參數解析器
自定義方法參數解析器CurrentUserMethodArgumentResolver,需實現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標識的配置類,SpringMVC配置文件對應的配置類需繼承WebMvcConfigurationSupport。同理,解析器定義好以後,也需被添加到SpringMVC的配置文件或配置類中。最後,額外的一步,配置mvc。
3.4 配置MVC
定義MVC配置類,需繼承WebMvcConfigurationSupport。分別在addInterceptors和addArgumentResolvers方法中,添加自定義的攔截器和參數解析器,如下:
- 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 註解參數解析器
- *
- * @return
- */
- @Bean
- public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
- return new CurrentUserMethodArgumentResolver();
- }
-
- /**
- * 參數解析器
- *
- * @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);
- }
- }
以上。