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进行写出