尚硅谷springboot2day5

今天是2021-1-27。

一。自定义的实体类参数解析

  1. 在解析自定义的实体类参数时,都是调用ServletModelAttributeMethodProcessor这个参数解析器。
  2. 解析流程:
@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

2.1创建一个对应类型的实体类对象,这个对象的所有属性都是初始值,准备将这个对象的各个属性与请求该方法传入的参数值对应。
2.2创建参数绑定器WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
其中就传入了创建好的初始对象
2.3获取请求传递过来的所有kv对,封装到集合中
2.4遍历kv对集合,开始为每个kv对将value值设置到初始对象的对应属性上。
2.4.1利用反射得到初始对象的属性设置器,简单来说就是包装了一下初始对象
2.4.2因为http请求中的值默认都是文本,所以在给初始对象设置值之前需要转换。获取转换器GenericConversionService中的转换器集合。遍历集合,用本次遍历的kv对的value类型,以及目标实体类的对应属性的类型,来匹配合适的转换器
2.4.3将value值转换为目标类型
2.4.4将转换后的value值通过属性设置器赋给初始对象的对应属性
2.5实体类参数解析完成
注:springboot也支持我们自定义converter,比如将字符串转为指定日期格式的date对象的转换器
只用匿名实现Converter<String, T>接口,并实现其中的convert方法即可:

    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @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;
                    }
                });
            }
        };
    }

二。返回值处理器处理原理

1.方法执行完成后,获取所有的返回值处理器集合,遍历集合,判断哪个返回值处理器能处理当前方法的返回值
可支持的类型

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;


2.准备用消息转换器来转换返回值:

RequestResponseBodyMethodProcessor  	
@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
    

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
        // 使用消息转换器进行写出操作
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

2.1判断返回值是否是json或者是资源流inputstreamresource类型,是,就进行相应处理
2.2如果都不是,准备进行内容协商。浏览器在发送请求时,在请求头会告诉服务器响应时能接受的参数类型:Accept=…,比如Accept=application/json
2.2.1获取浏览器能接受的内容类型集合a和服务器支持的响应内容类型集合b,两个集合用for循环匹配,将匹配结果放入服务器最终会支持的响应内容集合c。
2.2.2获取所有的消息转换器集合,遍历查看是否支持将当前类型转换为目标响应类型。找到合适的消息转换器后,该转换器调用write()方法,将当前类型的返回值转换成响应类型,写到response响应体中。
2.3 注意!

	mavContainer.setRequestHandled(true);

在将返回值通过消息转换器写出之前,将requestHandled这个属性设置为true,这代表方法的响应将由自己控制。

	@Nullable
	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    
    

		modelFactory.updateModel(webRequest, mavContainer);
		if (mavContainer.isRequestHandled()) {
    
    
			return null;
		}

所有方法执行完成,都会返回modelandview对象。但是在上面,我们将requestHandled这个属性设置为true,这里modelandview直接返回null

// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
    
    
			render(mv, request, response);
			if (errorView) {
    
    
				WebUtils.clearErrorRequestAttributes(request);
			}
		}

所有方法执行完成后,都会在dispatchservlet的processDispatchResult()方法中准备渲染视图,但此时我们的modelandview对象已经为空,就不会渲染视图了。这就是@reponsebody或者@restcontroller注解可以"绕过"视图解析器直接返回json等格式的数据的原因

三。内容协商详解

1.开启:引入

 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

1.1开始进行内容协商时,先判断当前的响应头中是否有已经确定能够支持的媒体类型,如果有,就使用已经确定好的媒体类型。
1.2使用内容协商管理器解析request对象,获取浏览器能接受的内容类型数组,然后转为集合a,就是获取请求头中Accept字段的值。
1.3获取所有的消息转换器,循环遍历调用每个转换器的canwrite()方法,匹配支持把当前类型转为目标类型的转换器,如果当前转换器支持,把当前转换器支持的所有媒体类型–目标类型添加到集合b
1.4集合a与集合b嵌套for循环匹配,得到服务器最终会支持的响应内容集合c。然后循环判断最终使用的那一个响应类型d,虽然集合可能有多个类型,但是根据权重优先匹配原则,会匹配权重最大的那个,匹配到了就break结束匹配。
1.5获取所有的消息转换器集合,遍历查看是否支持将当前类型转换为最终目标响应类型d。找到合适的消息转换器,该转换器调用write()方法,将当前类型的返回值转换成响应类型,写到response响应体中。

注:如果标注了@requestbody或者@responsebody,在解析请求参数和处理方法返回值时都会使用到httpmessageconverter进行转换

扫描二维码关注公众号,回复: 12684322 查看本文章

四。基于浏览器的内容协商

配置

spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

这样配置了以后,浏览器发请求时在url后面携带format参数,通过format参数与服务器协商响应值的类型。

五。自定义httpmessageconverter

1.如果我们希望返回自定义格式的数据,那么就需要注入我们自定义的httpmessageconverter给返回值处理器使用。
2.配置类中注入webmvcconfigure,在其中配置即可:

 @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

            }
        }
    }

3.自定义转换器,需要实现HttpMessageConverter接口。
3.1对于一个转换器,有读写两种支持,如果只希望支持写功能,那么就重写canread()方法使其直接返回false即可。对于写功能,canwrite()中判断一下返回值类型是否是我们希望支持的类型。
3.2重写读写判断后,别忘了返回值处理器在进行内容协商时要获取所有消息转换器支持的媒体类型,所以我们的自定义转换器还要重写getSupportMediaTypes,定义自己可支持的媒体类型
3.3接着就是重写读写判断成功后的实际读写方法了,自定义转换规则即可

六。自定义内容协商策略

1.配置类中注入webmvcconfigure,实现

void configureContentNegotiation(ContentNegotiationConfigure configure)

自定义服务器的内容协商管理器,
2.创建需要的协议对象,注意自定义的协议对象需要指定本协议支持的媒体类型:Map<String,MediaType>,放入自定义的映射关系即可
3.configure.strategies(Arrays.asList(xxx,…))将自定义的协议放入内容协商管理器配置器,xxx就是我们自定义的协议对象。

七。总结

p32-p41坚持看下来,确实收获不小,大部分都尝试用自己的话总结出来。不过我还是觉得看完springboot的源码解析,最后应该了解从接受请求到返回响应,这个过程中springboot2支持我们自定义多少组件,才算是能应用到实际中:
1.解析请求:自定义请求映射管理器HandlerMapping来保存路径与处理方法的映射,主要可以实现相同请求路径的不同版本,比如api/getuser/v1。
2.请求参数/返回参数的转换:自定义Converter<String, T>/HttpMessageConverter接口,自定义参数转换的规则。HttpMessageConverter主要是用于转换@RequestBody和@ResponseBody相关的参数,Converter<String, T>则适用于大多数其他类型,比如自定义实体类类型参数。
2.1.自定义Converter:本文一。2.5;自定义HttpMessageConverter:本文五。
3.处理返回参数之前的内容协商:在返回值处理器对方法的返回值进行转换时,也要先确定浏览器可以接受哪些类型的参数,再查找对应的转换器进行转换,这个确定转换类型的过程就是内容协商。
3.1我们可以自定义内容协商管理器,来自定义我们获取浏览器支持的响应类型的方式,即自定义内容协商管理器中管理的内容协商协议:
3.2.配置类中注入webmvcconfigure,实现自定义服务器的内容协商管理器:

void configureContentNegotiation(ContentNegotiationConfigure configure)

3.3创建需要的协议对象,注意自定义的协议对象需要指定本协议支持的媒体类型:Map<String,MediaType>,放入自定义的映射关系即可
3.4.configure.strategies(Arrays.asList(xxx,…))将自定义的协议放入内容协商管理器配置器,xxx就是我们自定义的协议对象。这样,在获取浏览器支持的响应类型的方式时,就可以使用我们自己的协商协议,以自定义方式来获取浏览器支持的类型

猜你喜欢

转载自blog.csdn.net/qq_44727091/article/details/113250433