Spring自定义参数解析器
在这篇文章中,我们认识了参数解析器和消息转换器,今天我们来自定义一个参数解析器。
自定义参数解析器
实现HandlerMethodArgumentResolver的类,并注册到Spring容器。
@Component//注册到Spring
public class UserArgResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 如果参数上有@User注解,并且参数类型是User或者其子类,则可以使用这个参数解析器
return parameter.hasParameterAnnotation(User.class) && parameter.getParameterType().isAssignableFrom(UserInfo.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
final HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
final String userName = request.getHeader("userName");
if (userName == null) {
throw new RuntimeException("请求头中缺少用户信息");
}
final UserInfo user = new UserInfo();
user.setName(userName);
//返回值直接给Controller了
return user;
}
}
Spring Boot直接把解析器定义为Bean即可,如果是SpringMVC则需要这样注册
@Configuration
public class Config implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new UserArgResolver());
}
}
定义接口
@GetMapping("/test2")
@ResponseBody
public String test2(@User UserInfo userInfo) {
System.out.println(userInfo.getName());
return "ok";
}
UserInfo
public class UserInfo {
private String name;
// get and set
}
注解
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({PARAMETER})
@Retention(RUNTIME)
@Documented
public @interface User {
}
我们的参数解析器是从请求头中解析信息,请求头中要有userName属性,不然会抛异常。请求方式如下:
此时Controller中的接口上可以成功接收参数解析器中解析到的UserInfo参数。
值得注意的是Spring Boot的参数解析是否生效和添加顺序也有关系,下面是RequestMappingHandlerAdapter中默认的添加顺序
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// 基于注解
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// 基于参数类型
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// 自定义
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// 兜底
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
可以看到有两个RequestParamMethodArgumentResolver,第一个必须明确使用@RequestParam才会起作用,第二个优先级在自定义之后.
请求头参数
@GetMapping("/test3")
@ResponseBody
public String test3(@RequestHeader("name") String headerName) {
System.out.println(headerName);
return "ok";
}
@GetMapping("/test3")
@ResponseBody
public String test3(HttpHeaders headers) {
System.out.println(headers.get("name"));
return "ok";
}
上面连中获取请求头的写法中,第一种使用注解方式是正确的,第二种写法使用的是MapMethodProcessor,是获取不到完整的请求头的。我们自定义一个基于类型的请求头参数解析器也没用,因为MapMethodProcessor优先级高于自定义的优先级。此时可以对RequestMappingHandlerAdapter的argumentResolvers
@Configuration
public class Config implements WebMvcConfigurer {
@Bean
public RequestMappingHandlerAdapter adapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
//设置参数解析器
final List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> list1 = new ArrayList<>(argumentResolvers.size() + 1);
// 自定义解析器添加到第一个位置
list1.add(0, new UserArgResolver());
list1.addAll(argumentResolvers);
requestMappingHandlerAdapter.setArgumentResolvers(list1);
return requestMappingHandlerAdapter;
}
}
不过一般没必要这样,我们可以通过其他方式获取请求头,比如从请求对象中获取
@GetMapping("/name3")
@ResponseBody
public String name3(HttpServletRequest request) {
request.getHeader("name");
return "ok";
}