1. @ResponseBody
  2. @GetMapping("/xxx")
  3. public User login(
  4. HttpServletRequest request,
  5. HttpServletResponse response,
  6. @AuthenticationPrincipal UserInfo userInfo

比如上面这样一个 controller 方法,含义有:

  1. 返回了 User 自定义对象,使用 @ResponseBody 修饰,表示需要响应 JSON 类型的数据
  2. 入参中有 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

  1. import org.springframework.core.MethodParameter;
  2. import org.springframework.web.bind.support.WebDataBinderFactory;
  3. import org.springframework.web.context.request.NativeWebRequest;
  4. import org.springframework.web.context.request.RequestAttributes;
  5. import org.springframework.web.method.support.HandlerMethodArgumentResolver;
  6. import org.springframework.web.method.support.ModelAndViewContainer;
  7. import org.springframework.web.multipart.support.MissingServletRequestPartException;
  8. public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
  9. @Override
  10. public boolean supportsParameter(MethodParameter parameter) {
  11. // 判断是否支持此参数
  12. // 先看类型是否支持:是否是 UserInfo 类型,或则属于 UserInfo 类型
  13. // 然后查看,该参数上是否有 User 注解
  14. return UserInfo.class.isAssignableFrom(parameter.getParameterType())
  15. &&
  16. parameter.hasParameterAnnotation(User.class);
  17. }
  18. @Override
  19. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  20. // 返回这个对象
  21. retruen new UserInfo();
  22. }
  23. }
  1. @Target(ElementType.PARAMETER) // 作用于参数上的注解
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface User {
  5. }

第二步:注册 HandlerMethodArgumentResolver

注册入口是 SpringMVC 的 WebMvcConfigurer API

  1. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
  2. @Configuration
  3. public class SecurityWebConfig implements WebMvcConfigurer {
  4. @Override
  5. public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
  6. // 将自定义的添加
  7. resolvers.add(new UserHandlerMethodArgumentResolver());
  8. }
  9. }

到此就已经可以实现往 Controller 方法上注入一个自定义的对象了

有什么用?

可以想象下,这个一般最容易想到的场景是:自定义写了一个 token 校验,想要在需要注入 token 相关用户信息的场景下,能自动注入。而不是需要使用者调用 getUser(token)

上面两步已经可以实现这个功能,其他参数可以从 webRequest 中获得。但是还有一个优雅的方式是在 拦截器 中将 token对应的 UserInfo 对象获取到,然后在 HandlerMethodArgumentResolver 中返回即可

在拦截器中的部分代码

  1. public class AuthHandlerInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. // 获取映射到的 controller 方法上的入参信息
  5. final Parameter[] parameters = method.getParameters();
  6. for (Parameter parameter : parameters) {
  7. // 判断入参信息上是否有 @User 自定义注解
  8. if (parameter.isAnnotationPresent(User.class)) {
  9. final String name = parameter.getName();
  10. // 通过 request 中携带的 toekn 参数,获取到 UserInfo
  11. final UserInfo userInfo = userDetailsService.load(token);
  12. // 将获取到的对象放入到 request 中
  13. request.setAttribute(name, userInfo);
  14. }
  15. }

在策略接口中的部分代码

  1. @Override
  2. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  3. // 获取到入参名称,因为在拦截器中放入到 request 中就是以入参名称放入的
  4. final String name = parameter.getParameter().getName();
  5. // 从 request 中获取
  6. final Object userInfo = webRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
  7. if (userInfo != null) {
  8. return userInfo;
  9. }
  10. // 如果获取失败,就抛出异常
  11. throw new MissingServletRequestPartException(name);
  12. }