# 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 结果为

在这里插入图片描述

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

# 源码解析

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

RequestMappingHandlerAdapter#afterPropertiesSet 在这里插入图片描述 在这里插入图片描述 在RequestMappingHandlerAdapter的初始化阶段,初始化默认的参数解析器和用户自定义的参数解析器

InvocableHandlerMethod#invokeForRequest 在这里插入图片描述

在这里插入图片描述 resolvers是HandlerMethodArgumentResolverComposite对象,组合了多个HandlerMethodArgumentResolver,如果没有对应的参数解析器可以解析则报错,否则用对应的参数解析器解析值

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

参数类型 参数解析器
@PathVariable PathVariableMethodArgumentResolver
@RequestParam RequestParamMethodArgumentResolver
@RequestHeader RequestHeaderMethodArgumentResolver
@RequestBody RequestResponseBodyMethodProcessor
HttpServletRequest ServletRequestMethodArgumentResolver
HttpServletResponse ServletResponseMethodArgumentResolver

RequestResponseBodyMethodProcessor实现了HandlerMethodArgumentResolver接口和HandlerMethodReturnValueHandler接口

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