7、SpringBoot2 Web开发之响应及其原理

1、数据响应

1.1 响应数据方式

方法上标准@ResponseBody,方法以数据的格式进行响应,SpringBoot2默认以JSON的格式进行返回,导入web包时,同时会导入jackson.jar

1.2 数据返回执行逻辑

使用supportsParameter方法循环比对所有的HandlerMethodReturnValueHandler 是否可以处理,查找到符合处理的RequestResponseBodyMethodProcessor,使用handleReturnValue进行写出

//DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	.......
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
	.......
}

//RequestMappingHandlerAdapter
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	........
	mav = this.invokeHandlerMethod(request, response, handlerMethod);
	.......
}

//ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	........
	this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
	.......
}

在这里插入图片描述

//HandlerMethodReturnValueHandlerComposite
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	//查询符合要求的handler
    HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    } else {
    	//执行返回的逻辑
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
}

1.3 RequestResponseBodyMethodProcessor处理逻辑

//RequestResponseBodyMethodProcessor.class
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
     mavContainer.setRequestHandled(true);
     ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
     ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
     this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
 }

1.3.1 查找内容协议

//AbstractMessageConverterMethodProcessor
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    .......
    MediaType selectedMediaType = null;
    //查找是否指定MediaType
    MediaType contentType = outputMessage.getHeaders().getContentType();
    boolean isContentTypePreset = contentType != null && contentType.isConcrete();
    if (isContentTypePreset) {
        selectedMediaType = contentType;
    } else {
        HttpServletRequest request = inputMessage.getServletRequest();
        //查询符合客户端可接收的MediaType 根据Accept头指定
        List<MediaType> acceptableTypes = this.getAcceptableMediaTypes(request);
        //查询服务器可返回的MediaType
        List<MediaType> producibleTypes = this.getProducibleMediaTypes(request, valueType, (Type) targetType);
        List<MediaType> mediaTypesToUse = new ArrayList();
        Iterator var15 = acceptableTypes.iterator();

        //比对可用的MediaType
        MediaType mediaType;
        while (var15.hasNext()) {
            mediaType = (MediaType) var15.next();
            Iterator var17 = producibleTypes.iterator();

            while (var17.hasNext()) {
                MediaType producibleType = (MediaType) var17.next();
                if (mediaType.isCompatibleWith(producibleType)) {
                    mediaTypesToUse.add(this.getMostSpecificMediaType(mediaType, producibleType));
                }
            }
        }

        MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
        var15 = mediaTypesToUse.iterator();
        //选择一个可用的MediaType
        while (var15.hasNext()) {
            mediaType = (MediaType) var15.next();
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            }
            if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }
        ......
    }
}

遍历所有的messageConverters,通过Converter中canWrite方法,判断是否支持,若支持,将Converter支持的MediaType进行返回

protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
      .......
      //遍历所有的messageConverters
      Iterator var6 = this.messageConverters.iterator();

       while(true) {
           while(var6.hasNext()) {
               HttpMessageConverter<?> converter = (HttpMessageConverter)var6.next();
               if (converter instanceof GenericHttpMessageConverter && targetType != null) {
                   if (((GenericHttpMessageConverter)converter).canWrite(targetType, valueClass, (MediaType)null)) {
                       result.addAll(converter.getSupportedMediaTypes());
                   }
               } else if (converter.canWrite(valueClass, (MediaType)null)) {
                   result.addAll(converter.getSupportedMediaTypes());
               }
           }

           return result;
       }
   }
}

在这里插入图片描述

1.3.2 进行写出操作

根据上面选择的MediaType以及判断messageConverters是否可写来获取到处理的messageConverters,通过messageConverters的writer将返回结果写出

//AbstractMessageConverterMethodProcessor
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    .....
    HttpMessageConverter converter;
    GenericHttpMessageConverter genericConverter;
    label159:
    {
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            Iterator var22 = this.messageConverters.iterator();

            while (var22.hasNext()) {
                converter = (HttpMessageConverter) var22.next();
                genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter) converter : null;
                if (genericConverter != null) {
                    if (((GenericHttpMessageConverter) converter).canWrite((Type) targetType, valueType, selectedMediaType)) {
                        break label159;
                    }
                } else if (converter.canWrite(valueType, selectedMediaType)) {
                    break label159;
                }
            }
        }
    }
    ........
    converter.write(body, selectedMediaType, outputMessage);
    .......
}

1.4、内容协商

根据客户端接收能力不同,返回不同媒体类型的数据

1.4.1 支持xml依赖

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

添加以上依赖,springboot会自动给容器中添加支持xml的messageConverters

1.4.2 内容协商参数功能

为了方便内容协商,开启基于请求参数的内容协商功能,根据参数format为json或xml进行判定返回

spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式
  • 支持更多的参数协议,可通过自定义内容协商策略进行修改
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    /**
     * 自定义内容协商策略
     * @param configurer
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        Map<String, MediaType> mediaTypes = new HashMap<>();
        mediaTypes.put("json",MediaType.APPLICATION_JSON);
        mediaTypes.put("xml",MediaType.APPLICATION_XML);
        mediaTypes.put("qww",MediaType.parseMediaType("application/x-qww"));
        //指定支持解析哪些参数对应的哪些媒体类型
        ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
        HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
        configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
    }
}

1.4.3 内容协商原理总结

  • 判断当前响应头中是否已经有确定的媒体类型;
  • 获取客户端(PostMan、浏览器)支持接收的内容类型;
  • 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象;
  • 找到支持操作对象的converter,并把converter支持的媒体类型统计出来;
  • 进行内容协商的最佳匹配媒体类型;
  • 用 支持 将对象转为 最佳匹配媒体类型的converter,调用它进行转化 。

1.5 自定义内容解析器

往容器中添加实现HttpMessageConverter即可,通过mvc自定义配置进行配置修改

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    /**
     * 添加自定义的内容解析器
     * @param converters
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new HttpMessageConverter<Object>() {
            @Override
            public boolean canRead(Class<?> aClass, MediaType mediaType) {
                return false;
            }
            @Override
            public boolean canWrite(Class<?> aClass, MediaType mediaType) {
                return false;
            }
            @Override
            public List<MediaType> getSupportedMediaTypes() {
                return null;
            }
            @Override
            public Object read(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
                return null;
            }
            @Override
            public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {

            }
        });
    }
}

2、响应页面

2.1 thymeleaf使用

2.1.1 引用包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.1.2 SpringBoot自动配置

引用包后,springboot默认会进行thymeleaf配置

@Configuration(
    proxyBeanMethods = false
)
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
public class ThymeleafAutoConfiguration {
}
  • 可通过修改ThymeleafProperties的值就行修改thymeleaf配置
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
    private boolean cache;
    private Integer templateResolverOrder;
    private String[] viewNames;
    private String[] excludedViewNames;
    private boolean enableSpringElCompiler;
    private boolean renderHiddenMarkersBeforeCheckboxes;
    private boolean enabled;
    private final ThymeleafProperties.Servlet servlet;
    private final ThymeleafProperties.Reactive reactive;
    ...........
}

2.1.3 默认使用

在templates创建login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2 th:text="${name}">hhhh</h2>
</body>
</html>

创建controller

@Controller
public class LoginController {

    @GetMapping("/login")
    public String main(@RequestParam Integer a, Model model){

        if(a == 3){
            //登录成功重定向到main.html;  重定向防止表单重复提交
            return "redirect:/test/person";
        }else {
            model.addAttribute("name","zhangsan");
            //回到登录页面
            return "login";
        }
    }
}

当a不等于3时,会将login.html进行展示。

2.2 响应原理

2.2.1 视图解析原理流程

同上数据返回逻辑,查询到处理视图的处理器ViewNameMethodReturnValueHandler,并将所有数据都会被放在 ModelAndViewContainer 里面,包括数据和视图地址

//ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	........
	this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
	.......
}

任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)

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

在这里插入图片描述

调用processDispatchResult 处理派发结果(进行页面响应)

2.2.2 processDispatchResult 调用流程

这行render方法,其中标记@ResponseBody方法返回的ModelAndView为空,不执行此处的方法,数据已经在内容解析器中写出。

//DispatcherServlet
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
	//.....................
	render(mv, request, response);
	//.....................
}

根据resolveViewName获取到视图,通过视图render方法进行渲染

//DispatcherServlet
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	//..................
	view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
	//..................
	view.render(mv.getModelInternal(), request, response);
	//..................
}

循环遍历所有的视图解析器来创建View

	@Nullable
	protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {

		if (this.viewResolvers != null) {
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
		return null;
	}

在这里插入图片描述

ContentNegotiatingViewResolver视图解析器中包含了所有其他四种解析器

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		List<View> candidateViews = new ArrayList<>();
		if (this.viewResolvers != null) {
			Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
			for (ViewResolver viewResolver : this.viewResolvers) {
				//查询适合处理的视图
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					candidateViews.add(view);
				}
				//根据返回类型匹配合适的视图
				for (MediaType requestedMediaType : requestedMediaTypes) {
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					for (String extension : extensions) {
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}

在这里插入图片描述

ThymeleafViewResolver创建view逻辑

    protected View createView(String viewName, Locale locale) throws Exception {
        if (!this.alwaysProcessRedirectAndForward && !this.canHandle(viewName, locale)) {
            vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
            return null;
        } else {
            String forwardUrl;
            // 使用redirect:开头创建RedirectView 
            if (viewName.startsWith("redirect:")) {
                vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
                forwardUrl = viewName.substring("redirect:".length(), viewName.length());
                RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
                return (View)this.getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
              // 使用forward:开头创建InternalResourceView
            } else if (viewName.startsWith("forward:")) {
                vrlogger.trace("[THYMELEAF] View \"{}\" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
                forwardUrl = viewName.substring("forward:".length(), viewName.length());
                return new InternalResourceView(forwardUrl);
            } else if (this.alwaysProcessRedirectAndForward && !this.canHandle(viewName, locale)) {
                vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
                return null;
            } else {
            //字符串返回ThymeleafView
                vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a {} instance will be created for it", viewName, this.getViewClass().getSimpleName());
                return this.loadView(viewName, locale);
            }
        }
    }

2.2.3 view render方法大概逻辑

  • RedirectView

使用servlet原生方法response.sendRedirect(encodedURL)

  • InternalResourceView

使用servlet原生方法 request.getRequestDispatcher(path).forward(request, response)

  • ThymeleafView

使用模板引擎对数据进行解析,通过response进行写出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值