@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 {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断是否支持此参数
// 先看类型是否支持:是否是 UserInfo 类型,或则属于 UserInfo 类型
// 然后查看,该参数上是否有 User 注解
return UserInfo.class.isAssignableFrom(parameter.getParameterType())
&&
parameter.hasParameterAnnotation(User.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 返回这个对象
retruen new UserInfo();
}
}
@Target(ElementType.PARAMETER) // 作用于参数上的注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface User {
}
第二步:注册 HandlerMethodArgumentResolver
注册入口是 SpringMVC 的 WebMvcConfigurer API
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration
public class SecurityWebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 将自定义的添加
resolvers.add(new UserHandlerMethodArgumentResolver());
}
}
到此就已经可以实现往 Controller 方法上注入一个自定义的对象了
有什么用?
可以想象下,这个一般最容易想到的场景是:自定义写了一个 token 校验,想要在需要注入 token 相关用户信息的场景下,能自动注入。而不是需要使用者调用 getUser(token)
上面两步已经可以实现这个功能,其他参数可以从 webRequest
中获得。但是还有一个优雅的方式是在 拦截器 中将 token对应的 UserInfo 对象获取到,然后在 HandlerMethodArgumentResolver 中返回即可
在拦截器中的部分代码
public class AuthHandlerInterceptor implements HandlerInterceptor {
@Override
public 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 参数,获取到 UserInfo
final UserInfo userInfo = userDetailsService.load(token);
// 将获取到的对象放入到 request 中
request.setAttribute(name, userInfo);
}
}
在策略接口中的部分代码
@Override
public 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);
}