出處: HandlerMethodArgumentResolver用於統一獲取當前登錄用戶
目錄
一、最原始直接
二、AOP
三、攔截器+方法參數解析器
3.1 自定義權限攔截器
3.2 自定義參數注解
3.3 自定義方法參數解析器
3.4 配置MVC
- 環境: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
知識儲備已到位,接下來着手實現,主要分為三步走:
1. 自定義權限攔截器AuthenticationInterceptor攔截所有request請求,並將token解析為currentUser,最終放到request中;
2. 自定義參數注解@CurrentUser,添加至controller的方法參數user之上;
3. 自定義方法參數解析器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 { }
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); } }