Spring MVC源码解析:参数解析器,简化参数取值过程_解析器

参数解析器简化取值过程

参数解析器有什么作用呢?就是将你通过http提交的参数,转化成controller方法的参。超级方便,我们看看用Servlet开发是如何取参数值的?

@WebServlet(urlPatterns="/user")
public class UserServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userId = req.getParameter("userId");
String token = req.getHeader("token");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("userId " + userId + " token " + token);
}
}

我们得自己手动从HttpServletRequest获取需要的信息,而在Spring MVC中只需要加上相应的参数注解即可,是不是超级方便!

@RestController
public class UserController {

@RequestMapping("user")
public String index(@RequestParam("userId") String userId,
@RequestHeader("token") String token) {
return "userId " + userId + " token " + token;
}
}

Spring MVC中提供了很多这样的参数注解,方便了你的开发

javax.servlet.http.HttpServletRequest
javax.servlet.http.HttpServletResponse
javax.servlet.http.HttpSession
@PathVariable
@RequestParam
@RequestHeader
@RequestBody
@RequestAttribute

如果觉得Spring提供的参数解析器不能满足要求的话,我们还可以自定义参数解析器

自定义参数解析器

SpringMVC参数绑定的注解有很多,如@RequestParam,@RequestBody,@PathVariable,@RequestHeader,@CookieValue等。这些注解的实现方式很类似,都是有一个对应的解析器,解析完返回一个对象,放在方法的参数上。

如@RequestParam的解析器为RequestParamMethodArgumentResolver,@RequestBody的解析器为PathVariableMethodArgumentResolver等。假如说现在有一个场景,前端每次从前面传入一个userId,而后端呢,每次都去查数据库,然后拿到用户的信息。如果有很多个controller,每个controller上来都是一样的逻辑,去查数据库,然后拿用户信息,这样的代码就很烂。如何精简呢?答案就是自定义注解实现参数绑定

定义注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {

}

自定义参数解析器

参数解析器接口定义如下,总共有2个方法。supportsParameter判断是否支持该参数,如果支持该参数则通过resolveArgument把参数值解析出来

HandlerMethodArgumentResolver

public interface HandlerMethodArgumentResolver {

// 是否支持该参数
boolean supportsParameter(MethodParameter parameter);


// 解析参数值
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}
public class CurrentUserUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

/** 用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(User.class) && parameter.hasParameterAnnotation(CurrentUser.class);
}

/** 真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest request, WebDataBinderFactory factory) throws Exception {

String userId = request.getParameter("userId");

// 为了方便不模拟查数据库的过程,直接new一个username和password都是userId的对象
return new User(userId, userId);
}
}

加入配置

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserUserHandlerMethodArgumentResolver());
}
}

测试controller

@RestController
public class UserController {

@RequestMapping(value = "userInfo", method = RequestMethod.GET)
public Map<String, String> userInfo(@CurrentUser User user) {
Map<String, String> result = new HashMap<>();
result.put("username", user.getUsername());
result.put("password", user.getPassword());
return result;
}
}

访问: http://localhost:8080/userInfo?userId=1 结果为

Spring MVC源码解析:参数解析器,简化参数取值过程_自定义_02


我这里举的是一个很简单的例子,在方法上直接拿用户的信息,方便大家理解。开发过程中的需求比这个复杂很多,大家可以发挥想象力应用参数绑定注解,如判断用户是否登录,将前端传过来的数据直接转成一个List之类,放到方法的参数上面等。

源码解析

我们来看一下参数解析器是如何设置的

RequestMappingHandlerAdapter#afterPropertiesSet

Spring MVC源码解析:参数解析器,简化参数取值过程_mvc_03


Spring MVC源码解析:参数解析器,简化参数取值过程_java_04


在RequestMappingHandlerAdapter的初始化阶段,初始化默认的参数解析器和用户自定义的参数解析器InvocableHandlerMethod#invokeForRequest

Spring MVC源码解析:参数解析器,简化参数取值过程_解析器_05

Spring MVC源码解析:参数解析器,简化参数取值过程_spring_06


resolvers是HandlerMethodArgumentResolverComposite对象,组合了多个HandlerMethodArgumentResolver,如果没有对应的参数解析器可以解析则报错,否则用对应的参数解析器解析值

我们常用的参数对应的参数解析器如下,源码不负责,就不解析了

参数类型

参数解析器

@PathVariable

PathVariableMethodArgumentResolver

@RequestParam

RequestParamMethodArgumentResolver

@RequestHeader

RequestHeaderMethodArgumentResolver

@RequestBody

RequestResponseBodyMethodProcessor

HttpServletRequest

ServletRequestMethodArgumentResolver

HttpServletResponse

ServletResponseMethodArgumentResolver

RequestResponseBodyMethodProcessor实现了HandlerMethodArgumentResolver接口和HandlerMethodReturnValueHandler接口

HandlerMethodArgumentResolver(参数解析器)接口用来处理@RequestBody注解,
HandlerMethodReturnValueHandler(返回值处理器)接口用来处理@ReponseBody注解

参考博客