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;
}
}
注意要把 自定义注解添加到组件里面
supportsParameter 方法主要判断 参数中有没有 User 注解,有的话判断接收参数的类型
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 协议》,转载必须注明作者和本文链接