@ResponseBody@GetMapping("/xxx")public User login(HttpServletRequest request,HttpServletResponse response,@AuthenticationPrincipal UserInfo userInfo
比如上面这样一个 controller 方法,含义有:
- 返回了 User 自定义对象,使用 
@ResponseBody修饰,表示需要响应 JSON 类型的数据 - 入参中有 request、response 还有一个自定义注解修饰的 
@AuthenticationPrincipal UserInfo userInfo 
意味着框架需要处理上面这两个功能,那么是如何处理的?就是本文章的内容了。
Spring mvc 提供了以下两个接口:
org.springframework.web.method.support.HandlerMethodArgumentResolver:入参处理策略org.springframework.web.method.support.HandlerMethodReturnValueHandler:返回值处理策略
HandlerMethodArgumentResolver
处理器入参解析策略,比如现在要实现注入一个自定义的  @User UserInfo userInfo 对象,有 @User 注解标识的,就 new 一个空的 UserInfo 注入
第一步:实现一个 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 org.springframework.web.multipart.support.MissingServletRequestPartException;public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {// 判断是否支持此参数// 先看类型是否支持:是否是 UserInfo 类型,或则属于 UserInfo 类型// 然后查看,该参数上是否有 User 注解return UserInfo.class.isAssignableFrom(parameter.getParameterType())&¶meter.hasParameterAnnotation(User.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {// 返回这个对象retruen new UserInfo();}}
@Target(ElementType.PARAMETER) // 作用于参数上的注解@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface User {}
第二步:注册 HandlerMethodArgumentResolver
注册入口是 SpringMVC 的 WebMvcConfigurer API
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer@Configurationpublic class SecurityWebConfig implements WebMvcConfigurer {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {// 将自定义的添加resolvers.add(new UserHandlerMethodArgumentResolver());}}
到此就已经可以实现往 Controller 方法上注入一个自定义的对象了
有什么用?
可以想象下,这个一般最容易想到的场景是:自定义写了一个 token 校验,想要在需要注入 token 相关用户信息的场景下,能自动注入。而不是需要使用者调用 getUser(token) 
上面两步已经可以实现这个功能,其他参数可以从 webRequest 中获得。但是还有一个优雅的方式是在 拦截器 中将 token对应的 UserInfo 对象获取到,然后在 HandlerMethodArgumentResolver 中返回即可
在拦截器中的部分代码
public class AuthHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取映射到的 controller 方法上的入参信息final Parameter[] parameters = method.getParameters();for (Parameter parameter : parameters) {// 判断入参信息上是否有 @User 自定义注解if (parameter.isAnnotationPresent(User.class)) {final String name = parameter.getName();// 通过 request 中携带的 toekn 参数,获取到 UserInfofinal UserInfo userInfo = userDetailsService.load(token);// 将获取到的对象放入到 request 中request.setAttribute(name, userInfo);}}
在策略接口中的部分代码
@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {// 获取到入参名称,因为在拦截器中放入到 request 中就是以入参名称放入的final String name = parameter.getParameter().getName();// 从 request 中获取final Object userInfo = webRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST);if (userInfo != null) {return userInfo;}// 如果获取失败,就抛出异常throw new MissingServletRequestPartException(name);}
