<
>

使用HandlerMethodArgumentResolver用于统一获取当前登录用户

2022-12-29 09:08:25 来源:易采站长站 作者:

目录
一、最原始直接二、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>

    三、拦截器+方法参数解析器

    使用mvc拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver最合适。

    SpringMVC提供了mvc拦截器HandlerInterceptor,包含以下3个方法:

      preHandlepostHandleafterCompletion

      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.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来“注入”,如下:

          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类中RequestMapping方法的参数添加此注解,如下:

          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。

          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配置类,需继承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);
              }
          }

          总结

          以上为个人经验,希望能给大家一个参考,也希望大家多多支持易采站长站。

暂时禁止评论

微信扫一扫

易采站长站微信账号