SpringBoot入门(五)请求参数处理(二)

目录

第七章 Web开发

7.3 请求参数处理

7.3.2 普通参数与基本注解

        7.3.2.2 Servlet API

        7.3.2.3 复杂参数

        7.3.2.4 自定义对象参数

7.3.3 POJO封装过程

        7.3.3.1 自定义Converter转换器

7.3.4 参数处理原理

        7.3.4.1 HandlerAdapter适配器

        7.3.4.2 执行目标方法

        7.3.4.3 参数解析器HandlerMethodArgumentResolver

        7.3.4.4 返回值处理器

        7.3.4.5 如何确定目标方法每一个参数的值

        7.3.4.6 目标方法处理完成

        7.3.4.7 处理派发结果


第七章 Web开发

7.3 请求参数处理

7.3.2 普通参数与基本注解

7.3.2.2 Servlet API

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

  • ServletRequestMethodArgumentResolver 以上的部分参数,比如HttpServletRequest request就是由这个参数解析器来进行解析的。
    @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) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

7.3.2.3 复杂参数

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

@GetMapping("/params")
public String testParam(Map<String,Object> map,
                        Model model,
                        HttpServletRequest request,
                        HttpServletResponse response){
    //给map、model添加数据就相当于在request域中添加数据
    map.put("hello","world");
    model.addAttribute("world","hello");
    request.setAttribute("message","hello world");

    Cookie cookie = new Cookie("c1","v1");
    response.addCookie(cookie);
    return "forward:/success";
}

//在"/success"的实现方法中可以通过request.getAttribute()方法来获取这些数据
  • Map类型的参数解析器是MapMethodProcessor,Model类型的参数解析器是ModelMethodProcessor。都会返回 mavContainer.getModel();---> BindingAwareModelMap是Model也是Map。

  • ModelAndViewContainer类下:

 

  • 通过doDispatch()方法中对方法调用结束后的后续处理processDispatchResult方法将map和model的数据放到request域中。

7.3.2.4 自定义对象参数

   可以把用户提交的数据,直接封装成自定义的对象。

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}

@Data
public class Pet {

    private String name;
    private String age;

}
@PostMapping("/saveuser")
public Person saveuser(Person person) {
    return person;
}
  • 数据绑定:页面提交的请求数据(Get、Post)都可以和对象属性进行绑定。

7.3.3 POJO封装过程

   参数解析器ServletModelAttributeMethodProcessor:解析自定义类型的参数。

  • 先进入ModelAttributeMethodProcessor类内的supportsParameter方法判断是否是简单类型的参数

public static boolean isSimpleValueType(Class<?> type) {
        return Void.class != type 
        && Void.TYPE != type 
        && (ClassUtils.isPrimitiveOrWrapper(type) 
            || Enum.class.isAssignableFrom(type) 
            || CharSequence.class.isAssignableFrom(type) 
            || Number.class.isAssignableFrom(type) 
            || Date.class.isAssignableFrom(type) 
            || Temporal.class.isAssignableFrom(type) 
            || URI.class == type 
            || URL.class == type 
            || Locale.class == type 
            || Class.class == type);
}
  • 使用对应的resolver对参数进行处理。
  • ModelAttributeMethodProcessor类内的resolveArgument方法调用ServletModelAttributeMethodProcessor的createAttribute方法,会先创建出一个空实例对象person。

  • 后续就要给这个空实例对象封装请求传过来的值,即数据绑定过程
  • ModelAttributeMethodProcessor类内的resolveArgument方法中的核心代码:
        if (bindingResult == null) {
            //创建网页数据绑定器
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                    //这一步就将请求里的值赋给了之前创建的空实例对象
                    this.bindRequestParameters(binder, webRequest);
                }

                this.validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }

            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }

            bindingResult = binder.getBindingResult();
        }

        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);
        return attribute;
  • 先创建一个web数据绑定器,将请求参数的值绑定到指定的JavaBean里面,而这个JavaBean就是attribute,即之前创建的空实例对象
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

  • WebDataBinder 利用它里面的 Converters请求数据转成指定的数据类型。再次封装到JavaBean中

    • GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(如JavaBean的Integer类型),也有如何把字节流传换成文件byte -- > file

    • 可以自定义Converters给WebDataBinder

private static final class StringToNumber<T extends Number> implements Converter<String, T>

泛型指的是把请求带来的String类型,转成自己想要的类型T

  • 把请求中的数据绑定到对象上

bindRequestParameters(binder, webRequest);

7.3.3.1 自定义Converter转换器

   假设此时不使用级联属性提交宠物,而是规定提交方式,逗号前是宠物名字,逗号后是宠物年龄

    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

7.3.4 参数处理原理

   DispatcherServlet类中的方法doDispatcher是处理所有请求的起点:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                //这一步就是之前说的找到了处理请求的映射规则,即哪个方法可以处理这个请求
                //mappedHandler封标了目标方法的信息
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
                //mappedHandler.getHandler()得到哪个方法可以处理这个请求
                //找到处理当前方法适合的适配器
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = HttpMethod.GET.matches(method);
                if (isGet || HttpMethod.HEAD.matches(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                //真正指定handle,即执行当前请求对应的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}
  • HandlerMapping中找到能处理请求的Handler(Controller.method(),控制器中的哪个方法来处理当前请求)
  • 为当前Handle找一个适配器HandlerAdapter。RequestMappingHandlerAdapter

7.3.4.1 HandlerAdapter适配器

   HandlerAdapter是SpringMVC底层设计的一个接口,里面有几个方法:

public interface HandlerAdapter {
    //是否支持处理这个handler
    boolean supports(Object handler);
    
    //支持的话就调用方法来处理
    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    /** @deprecated */
    @Deprecated
    long getLastModified(HttpServletRequest request, Object handler);
}

   所有的HandlerAdapter如下:

  • 0 - 支持方法上标注@RequestMapping
  • 1 - 支持函数式编程的

   遍历所有的HandlerAdapter,利用supports方法找到合适的那个HandlerAdapter:

 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        Iterator var2 = this.handlerAdapters.iterator();
        //遍历寻找合适的适配器
        while(var2.hasNext()) {
            HandlerAdapter adapter = (HandlerAdapter)var2.next();
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }

    throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

7.3.4.2 执行目标方法

//DispatcherServlet--->doDispatch()

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法


//在类ServletInvocableHandlerMethod中

//执行当前请求的目标方法,获得一个返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

7.3.4.3 参数解析器HandlerMethodArgumentResolver

   HandlerMethodArgumentResolver确定将要执行的目标方法的每一个参数的值是什么。SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

   参数解析器是一个接口:

  • 先利用方法supportsParameter判断当前解析器是否支持解析这种参数
  • 支持的话就调用方法resolveArgument

7.3.4.4 返回值处理器

   决定目标方法能写多少种类型的返回值

7.3.4.5 如何确定目标方法每一个参数的值

   InvocableHandlerMethod类中的方法:

    ============InvocableHandlerMethod==========================
    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;
	}
  • 挨个判断所有参数解析器哪个支持解析这个参数

    @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;
	}
  • 解析这个参数的值

调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可

7.3.4.6 目标方法处理完成

   将所有的数据都放在ModelAndViewContainer包含要去的页面地址View,还包含Model数据。

7.3.4.7 处理派发结果

   核心方法就是doDispatch()方法里的:

processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

  •  doDispatch()内的render方法相当于接下来要去哪个页面,开始渲染。

  •  渲染新的视图,进入AbstractView下的render()方法:

  • renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

    • 进入InternalResourceView:内的renderMergedOutputModel方法:

 暴露模型作为请求域属性
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);

  • 此时就可以验证上面的结论,参数map、model内的数据最后会存在request域中。并且这个过程是在渲染视图的过程中操作完成的。

PS:根据尚硅谷视频整理,如有侵权,联系删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值