SpringBoot请求参数处理 原理刨析 restful风格支持

一、 请求映射

1. rest使用与原理

如今大多主流网站都运用的Restful编程风格,不同于传统的,根据请求参数名区分,rest风格是通过请求方法作为区分; 简单来说,就是用HTTP请求方式动词来表示对资源的操作。

  • 以前操作
    /getUer:获取用户 /deleteUser:删除用户 /updateUser/更新用户
  • resut风格
    /user GET-获取用户 POST-新增用户 DELETE-删除用户 PUT-更新用户
  • 核心:HiddenHttpMethodFilter
    • 表单:method=post 隐藏域_method=put
    • Springboot中手动开启

1.resut的使用

@RestController
public class RestFulControllerTest {
    @RequestMapping(path = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }
}
  • 开启Filter支持
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true #开启表单rest处理
<form method="post" action="user">
    <input name="_method" type="hidden" value="PUT">
    <input type="submit"  value="PUT-张三" >
</form>

这样表单就可以提交put请求

2. rest原理

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
	return new OrderedHiddenHttpMethodFilter();
}

/**
核心代码
**/

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

HttpServletRequest requestToUse = request;

//判断该请求是否是post和请求是否异常
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
	String method = paramValue.toUpperCase(Locale.ENGLISH);
	if (ALLOWED_METHODS.contains(method)) {
	    //返回包装类对象
		requestToUse = new HttpMethodRequestWrapper(request, method);
	}
}
}

filterChain.doFilter(requestToUse, response);
}
  • 当页面表单向后端发送请求,会携带_method参数;

    1. 该请求会被HiddenHttpMehtodFilter拦截下来:
    2. 先判断该请求是否正常和是否是POST请求
    3. 获取_method表单提交的参数
    4. 判断再ALLOWED_METHODS中是否有包含_method的value值
    5. 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值
    6. 过滤器放行的时候调用requesWrappe,所以一直都是调用wrapper重写的方法。
  • 兼容以下请求;PUT.DELETE.PATCH

  • 要想更改隐藏表单的name,我们可以重写

//自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }

在使用客户端工具时候,无需要Filter(PostMan)

2. 请求映射原理

  • 当客户端浏览器发送第一次(GET)请求,回去调用doget方法,doget()又去调用procesRequest
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	processRequest(request, response);
}
  • procesRequest()完成了一些初始化操作后,继续调用doService(),而这是一个抽象方法,默认由子类实现。
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
			throws Exception;
  • 子类(DispatchServlet)实现了doservice(),完成了初始化操作后,又调用doDispatch()方法
/**
    doService()中
**/
try {
	doDispatch(request, response);
}
  • 核心方法:每个请求都会去调用
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

        //异步请求管理器
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
		    	//检测request是否正常
				processedRequest = checkMultipart(request);
				//是否是文件上传请求
				multipartRequestParsed = (processedRequest != request);
				// Determine handler for the current request.
				//找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
			
	}
  • 所有的处理器映射器
    在这里插入图片描述

  • RequestMappingHandlerMapping

    • 保存了所有requestMapping和handler的映射规则
      在这里插入图片描述
  • 所有的请求关系映射都封装在HandlerMapping当中

  • SpringBoot自动配置WelcomPageHandlerMapping,是我们可以访问Index.html;

  • SpringBoot自动配置RequestMappingHandlerMapping

  • 当请求进来的时候,会挨个寻找对应的HandlerMapping;

    • 如果找到,就返回。
    • 没有则继续遍历下一个。
@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

到后期我们需要全面接管SpringMvc,需要我们自定义HandlerMapping

二、普通参数与基本注解

常用注解

@PathVariable: 路径变量
@RequestHeader: 请求头
@requestAttribute: 请求域
@RequestParam: 请求参数
@CookieValue: cookie值
@MatrixVariable: 矩阵变量
@RequestBody: 请求体内容
  1. @PathVariable
  • 既可以根据预留的{name}获取
  • 也可以直接使用Map接收
//请求URL car/1/owner/zhangsan
@GetMapping("/car/{id}/owner/{username}")
public Map getPath(@PathVariable("id") Integer id,
                   @PathVariable("username")String username,
                   @PathVariable Map<String,String> pv){
    Map<String,Object> map = new HashMap<>();
    map.put("id",id);
    map.put("username",username);
    map.put("pv",pv);
    return map;
}
  1. @RequestHander
  • 获取头信息
  • 一样可以使用Map封装所有
@GetMapping("/car/{id}/owner/{username}")
    public Map getPath(@RequestHeader("user-agent")String userAgent,
                       @RequestHeader Map<String,String> headerMap){
        Map<String,Object> map = new HashMap<>();
        map.put("userAgent",userAgent);
        map.put("headerMap",headerMap);
        return map;
    }
  1. @RequestAttribute
  • 获取Request域当中的参数
@GetMapping("/res")
public String getRes(HttpServletRequest res){
    res.setAttribute("code",200);
    return "forward:getRes"; //转发
}

@GetMapping("/getRes")
public String getResw(@RequestAttribute("code") String code){
    return code;
}
  1. @RequestParam
  • 获取请求参数
// car/1/owner/zhangsan?age=20&username=88
@GetMapping("/car/{id}/owner/{username}")
public Map getPath(@RequestParam("age") String age,
                   @RequestParam Map<String,String> paramMap){
    Map<String,Object> map = new HashMap<>();
   map.put("paramAge",age);
   map.put("paramMap",paramMap);
    return map;
    }
  1. @CookieValue
  • 获取Cookie的值
  • 获取Cookie的信息.
@GetMapping("/car/{id}/owner/{username}")
public Map getPath(@CookieValue("Idea-191d5581") String cookieValue,
                   @CookieValue("Idea-191d5581")Cookie cookie){
    Map<String,Object> map = new HashMap<>();
   map.put("cookieValue",cookieValue);
   log.debug(cookie.toString());
    return map;
}
  1. @RequestBody
  • 接收请求体所有内容
@PostMapping("/code")
public Map<String,Object> postUser(@RequestBody String content){
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("content",content);
    return map;
}
  1. @MatrixVariable
  • SpringBoot默认关闭了对矩阵变量的支持;
  • 手动开启;
    • 所有路径解析都交给了UrlPathHelper进行处理
    • 注入一个新的URLPathHelper
@Bean
public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            UrlPathHelper urlPathHelper = new UrlPathHelper();
            urlPathHelper.setRemoveSemicolonContent(false); //不删除;
            configurer.setUrlPathHelper(urlPathHelper);
        }
    };
}
  • 使用矩阵变量URL
    car/sell;low=80;brand=byd,bmw
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
                    @Matrix Variable("brand") List<String> brand,
                    @PathVariable("path") String path){
    Map<String,Object> map = new HashMap<>();

    map.put("low",low);
    map.put("brand",brand);
    map.put("path",path);
    return map;
}

ServletAPI参数解析

  • 原生ServletAPI

webRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

  • ServletRequestMethodArgumentResolver
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
		ServletRequest.class.isAssignableFrom(paramType) ||
		MultipartRequest.class.isAssignableFrom(paramType) ||
		HttpSession.class.isAssignableFrom(paramType) ||
		(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
		(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
		InputStream.class.isAssignableFrom(paramType) ||
		Reader.class.isAssignableFrom(paramType) ||
		HttpMethod.class == paramType ||
		Locale.class == paramType ||
		TimeZone.class == paramType ||
		ZoneId.class == paramType);
}

复杂参数解析

  • 相对复杂的参数

Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

  • MapModel
    • 无论Map或者Model类型的参数,在解析的时候,会返回msbContainer.getModel();
  • private final ModelMap defaultModel = new BindingAwareModelMap();,时Model也是Map
@Override
@Nullable 
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
	//返回BindingAwareModelMap
	return mavContainer.getModel();
}

在这里插入图片描述

  • 这里存放了ModelAndViewContatiner,视图与数据,再处理结果的时候会将他们解析和渲染到页面
    在这里插入图片描述

目标方法执行完成后

  • 数据与返回值View都会存到这里面
    在这里插入图片描述

处理派发结果

  • processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		//暴露模型作为请求域属性
		exposeModelAsRequestAttributes(model, request);

		// Expose helpers as request attributes, if any.
		exposeHelpers(request);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(request, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including [" + getUrl() + "]");
			}
			rd.include(request, response);
		}

		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to [" + getUrl() + "]");
			}
			rd.forward(request, response);
		}
	}

请求参数的解析原理

  1. SpringBoot先从HandlerMapping中找到可以处理请求Handler(Controller);

  2. 再根据Handler找到与指对应的HandlerAdapter(处理器适配器)

  3. 适配器用于执行对应的方法,并且酶切确定方法参数中对应的值。

//找到能处理当前请求的对应handler
mappedHandler = getHandler(processedRequest);

//找到对应的处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

1. HandlerAdapter

  • RequestMappingHandlerAdapter:初始方法上标注requestMapping
  • HandlerFunctionAdapter: 支持函数编程
    在这里插入图片描述

2. 执行目标方法

	// Actually invoke the handler.
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);

//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

3. 方法参数解析器

  • 用于确定目标方法参数的值;
  • SpringMvc能写多少种参数类型,取决于参数解析器
    在这里插入图片描述

在这里插入图片描述

  • 如果supportsParamter为True;就调用resolveArgument解析参数;

4. 获取参数当中的值

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}
4.1 挨个判断那个参数解析器能解析当前值
@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
4.2 再调用方法进行解析
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);


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

		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
			throw new IllegalArgumentException("Unsupported parameter type [" +
					parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值