spring-cloud HandlerMethodArgumentResolver用于统一获取当前登录用户 代长亚 2024-10-31 2025-09-05 目录
一、最原始直接
二、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可以解决很多切面类问题,将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中,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 自定义参数注解 自定义方法参数上使用的注解@CurrentUser,代表被它注解过的参数的值都需要由方法参数解析器CurrentUserMethodArgumentResolver 来“注入”,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target; @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentUser {}
给各Controller类中RequestMapping方法的参数添加此注解,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 自定义方法参数解析器 自定义方法参数解析器CurrentUserMethodArgumentResolver ,需实现HandlerMethodArgumentResolver 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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; 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方法中,添加自定义的拦截器和参数解析器,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 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; @Bean public AuthenticationInterceptor loginRequiredInterceptor () { return new AuthenticationInterceptor (); } @Bean public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver () { return new CurrentUserMethodArgumentResolver (); } @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); } }
以上。