HandlerMethodArgumentResolver 参数解析器

引入

开发中有一个特别常用的场景,用户登录凭证 token 通过 header 传递过来,接口中进行解析 user_id,但是解析的步骤是一致的,并且有的接口需要操作 user_id,有的接口是不需要的,为了方便,此时可以使用 HandlerMethodArgumentResolver 参数解析器来实现

HandlerMethodArgumentResolver

public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

HandlerMethodArgumentResolver 是个接口,里面有两个方法需要实现

supportsParameter 方法是条件,只有这个方法返回true的时候。才会在调用resolveArgument方法进行参数解析。resolveArgument方法处理一些业务,将返回值赋值给 Controller 层中的参数。

用户 token 注入方法参数

目标:在接口方法中添加注解,然后 HandlerMethodArgumentResolver 处理注解信息,分析 token,并把分析结果封装到 UserInfo 类中,接口中使用 UserInfo 类

@RestController
@RequestMapping("test")
public class TestController {

    /**
     * 通过 @User 注解把 header 的 token 解析,并封装到 UserInfo 中
     * @param userInfo UserInfo
     * @return String 
     */
    @GetMapping("save")
    public String saveUserInfo(@User UserInfo userInfo) {
        return userInfo.getUserId();
    }
}

定义 UserInfo

分析 token 解析的用户信息,最后保存在这个实体类中

@Data
public class UserInfo {
    private String userId;
    private String from;
}

定义注解

定义注解的目的是在需要用到用户凭证的接口中添加此注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface User {
}

接口使用

在有需求的接口中添加注解,并使用 UserInfo 实体类进行接收

    /**
     * 通过 @User 注解把 header 的 token 解析,并封装到 UserInfo 中
     * @param userInfo UserInfo
     * @return String
     */
    @GetMapping("save")
    public String saveUserInfo(@User UserInfo userInfo) {
        return userInfo.getUserId();
    }

参数解析器

此时添加注解是无任何作用的,框架并不知道添加这个注解要干什么事,这时候就需要使用自定义参数解析器

@Component
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //获取方法的注解,有没有 User 注解
        User annotation = parameter.getParameterAnnotation(User.class);
        if (annotation != null) {
            //有添加 User 注解,判断接收类的类型
            return UserInfo.class.isAssignableFrom(parameter.getParameterType());
        }
        return false;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        UserInfo userInfo = new UserInfo();
        //获取请求头的 token
        String token = webRequest.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            return userInfo;
        }
        //解析token,jwt 解密
        String substring = token.substring(token.indexOf(".") + 1);
        String substring1 = substring.substring(0, substring.indexOf("."));
        String s = Base64Util.decryptBASE64(substring1.getBytes());
        Map map = JSONObject.parseObject(s, Map.class);
        //获取 token 里面的信息
        String userId = map.get("uid").toString();
        String from = map.get("from").toString();
        userInfo.setUserId(userId);
        userInfo.setFrom(from);
        return userInfo;
    }
}

注意要把 自定义注解添加到组件里面

  1. supportsParameter 方法主要判断 参数中有没有 User 注解,有的话判断接收参数的类型

  2. resolveArgument 方法试 supportsParameter 方法验证通过,解析 token 信息,并封装成 UserInfo 对象返回

注册自定义参数解析器

把自定义参数解析器注册到 WebMvcConfigurer.addArgumentResolvers 方法中

@Configuration
public class BaseConfig implements  WebMvcConfigurer {

    @Resource
    private UserHandlerMethodArgumentResolver userHandlerMethodArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userHandlerMethodArgumentResolver);
    }

}

结果

通过请求 test/save 接口成功把用户信息保存到 UserInfo 对象里面

请求token

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6MTAwMDF9.eyJleHBpcmF0aW9uX3RpbWUiOjg2NDAwLCJyZWZyZXNoX3RpbWUiOjEyOTYwMCwidXNlcl9uYW1lIjoiMTg1Mzg1NTcwNjIiLCJ1aWQiOiIyNjA0NTc0Iiwic2VydmVyX25hbWUiOiJrdWtldXNlciIsImZyb20iOiJhbmRyb2lkIiwiZXhwIjoxNjQwNzQxOTUwLCJyZWZyZXNoX2V4cCI6MTY0MDc4NTE1MCwibGVld2F5X3RpbWUiOjMwfQ.xD_Q-UP64bGN3SLPMquSfh8Pc8QKGsSTKReHbK1H7QI

解读

HandlerMethodArgumentResolver 技术主要分为两个部分,一个是 HandlerMethodArgumentResolver 接口,它用于定义如何解析控制器方法参数,另一个是 HandlerMethodArgumentResolverComposite 类,它主要用于管理和调用所有的 HandlerMethodArgumentResolver 实例。

HandlerMethodArgumentResolverComposite

这个类管理和调用所有的 HandlerMethodArgumentResolver,通过属性 argumentResolvers 来存储

    private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();

首先,我们需要实现 HandlerMethodArgumentResolver 接口,定义如何解析控制器方法参数。

接下来,将实现的 HandlerMethodArgumentResolver 实例注册到 HandlerMethodArgumentResolverComposite 类中,它会管理和调用所有的 HandlerMethodArgumentResolver实例。

HandlerMethodArgumentResolverComposite 类也提供了两个重要的方法:

resolveArgument

getArgumentResolver 方法就是获取 HandlerMethodArgumentResolver 的实例,并调用 supportsParameter 方法,看那个符合

@Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

从代码中可以看到获取 HandlerMethodArgumentResolver 实例,是先从 this.argumentResolverCache 属性里面获取,这个 argumentResolverCache 是一个 map 类型的属性

    private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
            new ConcurrentHashMap<>(256);

所以只要服务不重启,当第一次确定是哪个 HandlerMethodArgumentResolver 实现处理后,就会加到 argumentResolverCache 里面,下次来就不执行 HandlerMethodArgumentResolver.supportsParameter() 方法了

getResolvers()

获取所有的HandlerMethodArgumentResolver 实例。

    /**
     * Return a read-only list with the contained resolvers, or an empty list.
     */
    public List<HandlerMethodArgumentResolver> getResolvers() {
        return Collections.unmodifiableList(this.argumentResolvers);
    }

执行

HandlerMethodArgumentResolverComposite.resolveArgument 的方法有有个判断 resolver == null,当不存在的时候就会直接抛异常

@Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" +
                    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

所以我们的方法参数到最后都会对应一个 HandlerMethodArgumentResolver 实例

先看无参的方法

    /**
     * 通过 @User 注解把 header 的 token 解析,并封装到 UserInfo 中
     * @param userInfo UserInfo
     * @return String
     */
    @GetMapping("save")
    public String saveUserInfo() {
        return "a";
    }

查看源码可以看到,在 InvocableHandlerMethod 这个类中,当参数为空的时候会直接返回一个 new Object[0]

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        }

        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                // Leave stack trace for later, exception may actually be resolved and handled...
                if (logger.isDebugEnabled()) {
                    String exMsg = ex.getMessage();
                    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                        logger.debug(formatArgumentError(parameter, exMsg));
                    }
                }
                throw ex;
            }
        }
        return args;
    }

如果存在参数,会 for 循环,然后对每个参数执行符合条件的参数解析器

大家可以试下参数的不同形式,命中的具体实例

@PostMapping("save")
public String saveUserInfo(@RequestParam String id) {
    return "a";
}
@PostMapping("save")
public String saveUserInfo(String id) {
  return "a";
}

命中 RequestParamMethodArgumentResolver

@PostMapping("save")
public String saveUserInfo(@RequestBody UserInfo userInfo) {
  return "a";
}

命中 RequestResponseBodyMethodProcessor

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!