在这篇文章中,我们认识了参数解析器和消息转换器,今天我们来自定义一个参数解析器。
自定义参数解析器
实现HandlerMethodArgumentResolver的类,并注册到Spring容器。
@Component//注册到Spring
public class UserArgResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {// 如果参数上有@User注解,并且参数类型是User或者其子类,则可以使用这个参数解析器return parameter.hasParameterAnnotation(User.class) && parameter.getParameterType().isAssignableFrom(UserInfo.class);}@Overridepublic 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 {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new UserArgResolver());}
}
定义接口
@GetMapping("/test2")@ResponseBodypublic 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")@ResponseBodypublic String test3(@RequestHeader("name") String headerName) {System.out.println(headerName);return "ok";}@GetMapping("/test3")@ResponseBodypublic String test3(HttpHeaders headers) {System.out.println(headers.get("name"));return "ok";}
上面连中获取请求头的写法中,第一种使用注解方式是正确的,第二种写法使用的是MapMethodProcessor,是获取不到完整的请求头的。我们自定义一个基于类型的请求头参数解析器也没用,因为MapMethodProcessor优先级高于自定义的优先级。此时可以对RequestMappingHandlerAdapter的argumentResolvers
@Configuration
public class Config implements WebMvcConfigurer {@Beanpublic 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")@ResponseBodypublic String name3(HttpServletRequest request) {request.getHeader("name");return "ok";}