视图解析与模板引擎
视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
1、视图解析
1、视图解析原理流程
1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址(想要去的路径【重定向、跳转】)
2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
4、【DispatchServlet的方法】processDispatchResult 处理派发结果(页面改如何响应)
- 1、render(mv, request, response); 进行页面渲染逻辑【渲染】
-
- 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
-
-
- 1、所有的视图解析器尝试是否能根据当前返回值得到View对象
- 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
- 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
- 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
-
-
-
-
- RedirectView 如何渲染【重定向到一个页面】
- 1、获取目标url地址
- 2、response.sendRedirect(encodedURL); RedirectView的renderMergedOutputModel--sendRedirect--response.sendRedirect(encodedURL)
- 3、执行了response.sendRedirect(encodedURL)页面就被渲染处理器,然后DispatchServlet执行结束;(最后有些清除数据啊,恢复啊在finally中完成的就不管了)
-
-
例如:视图解析器
ViewNameMethodReturnValueHandler
1、方法返回一个String字符串
return "redirect:/main.html"//重定向一个地址
return "login"//返回某个地址
2、ViewNameMethodReturnValueHandler
@Override
public boolean supportsReturnType(MethodParameter returnType) {
//取出返回值类型
Class<?> paramType = returnType.getParameterType();
//如果是void或者是String类型
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//如果是string类型,取出返回值
if (returnValue instanceof CharSequence) {
//取出返回值
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
//看看这个地址是不是重定向【如何判断】
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
//看看是不是包含:redirect
protected boolean isRedirectViewName(String viewName) {
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}
上图方式的返回值处理过程分析
视图解析:
-
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);【转发:servlet基础里面的转发如何写】
- 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向-->response.sendRedirect(encodedURL) 重定向【也是servlet基础】
- 返回值是普通字符串: new ThymeleafView()--->
【自定义视图】
(导出excel)
import net.sf.jxls.exception.ParsePropertyException;
import net.sf.jxls.transformer.XLSTransformer;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.NotImplementedException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.view.AbstractView;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
/**
* 导出Excel
*
*
*/
public class ExcelView<T extends Partition> extends AbstractView {
private String filename;
private String templateName;
private List<T> objectList = null;
public static final String CONTENT_TYPE = "application/vnd.ms-excel";
public static final String TEMPLATE_PATH = "classpath:template/excel/";
public static final String FORMAT = ".xls";
public ExcelView() {
}
public ExcelView(String templateName) {
this(templateName, templateName);
}
public ExcelView(String templateName, String filename) {
this.templateName = templateName;
this.filename = filename;
}
public ExcelView(String templateName, List<T> objectList) {
this(templateName);
this.objectList = objectList;
}
@Override
protected void renderMergedOutputModel(Map<String, Object> beanParams, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType(CONTENT_TYPE);
//String fileName = new String(filename.getBytes("UTF-8"),"UTF-8");
String fileName = URLEncoder.encode(filename, "UTF-8");
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("Content-Disposition", "attachment; filename="+fileName);
ServletOutputStream os = response.getOutputStream();
File template = ResourceUtils.getFile(TEMPLATE_PATH + templateName + FORMAT);
String path = template.getPath();
if (this.objectList == null) {
if (beanParams.isEmpty()) { // 下载模板
FileUtils.copyFile(template, os);
} else {
sheetExport(beanParams, os, path);
}
} else {
multiSheetsExport(beanParams, os, path);
}
}
@SuppressWarnings("unchecked")
private void multiSheetsExport(Map<String, Object> beanParams, ServletOutputStream os, String path) throws Exception {
final MultipleSheetsList sheetsList = new MultipleSheetsList(path, "query");
PartitionExecutor.execute(this.objectList, new PartitionCallback<T>() {
@Override
public void execute(List<T> partList) {
sheetsList.addSheet("", partList);
}
});
if (!beanParams.isEmpty()) {
sheetsList.addBeanParams(beanParams);
}
sheetsList.storeTo(os);
throw new NotImplementedException("未实现");
}
private void sheetExport(Map<String, Object> beanParams, ServletOutputStream os, String path) throws InvalidFormatException, IOException {
try {
XLSTransformer transformer = new XLSTransformer();
InputStream is = new BufferedInputStream(new FileInputStream(path));
Workbook workbook = transformer.transformXLS(is, beanParams);
workbook.write(os);
is.close();
os.flush();
os.close();
} catch (ParsePropertyException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 如何使用
*
*/
@RequestMapping(value = "/export", method = RequestMethod.POST)
public Object xxx(){
Map<String, Object> model = new HashMap<>();
model.put("earningCount",放内容);
return new ModelAndView(new ExcelView("material_earning", "辅材核算.xls"), model);
}
1、视图解析器(内容协商视图处理器支持解析的类型)
this.viewResolvers = {ArrayList@5104} size = 5
0 = {ContentNegotiatingViewResolver@5139} 内容协商视图解析器
1 = {BeanNameViewResolver@5140} beanName视图解析器
看看容器有没有要处理的这个组件(bean)
2 = {ThymeleafViewResolver@5141} Thymeleaf视图解析器
3 = {ViewResolverComposite@5142} view视图解析器
4 = {InternalResourceViewResolver@5143} InternalResource视图解析器
View返回值--resolveViewName方法执行
(1)ContentNegotiatingViewResolver的resolveViewName
1、获取当前请求的媒体类型,然后根据里面的viewResolvers视图处理器(循环遍历)
List<View> 返回list
List-Add View view = viewResolver.resolveViewName(viewName, locale);找到能处理的视图,返回
//拿到返回的list,找到最佳匹配
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs)
(2)BeanNameViewResolver的resolveViewName
从spring容器中获取看看是否存在beanName=viewName的,代码如下
ApplicationContext.containsBean(viewName)如果不存在返回null
如果存在return context.getBean(viewName, View.class),取出当前视图对应的bean
(3)ViewResolverComposite的resolveViewName
又通过这个view视图解析器组合的viewResolvers
viewResolver.resolveViewName查找能否处理的视图,找到然后return
(4)InternalResourceViewResolver的resolveViewName
InternalResourceViewResolver
--extends--UrlBasedViewResolver
--extends--AbstractCachingViewResolver
【AbstractCachingViewResolver基类中】
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
//判断是否需要开启缓存,如果不需要直接createView,如果需要则设置属性的cacheLimit值>0即可
if (!isCache()) {
return createView(viewName, locale);
}
else {
//看看当前处理视图名称是否存在缓存没的,
//获取缓存的key,通过(viewName + '_' + locale)组装;locale是本地化语言,例如zh_CN等等,这里可以给不同的语音类型缓存不同的视图信息
Object cacheKey = getCacheKey(viewName, locale);
//从缓存中取,如果缓存中没得,那就自己创建
View view = this.viewAccessCache.get(cacheKey);
//如果缓存中没得,那就自己创建呗
if (view == null) {
//由于所有的bean都是单实例,所以这里如果要保证bean的唯一性,首先的做好线程安全
//这里通过synchronized来,然后viewCreationCache就是带存放的集合
synchronized (this.viewCreationCache) {
//为了双重确保(这里在从已创建的view里面取一次)如果还是为空,则需要自己创建了
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
//创建视图
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
//由于这里是InternalResourceViewResolver
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
【ContentNegotiatingViewResolver】内容协商视图解析器
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
//获取当前线程中缓存的RequestAttributes
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
//拿到request后,获取当前请求携带的支持响应的媒体类型
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;//【最终返回了ThymeleafView这个视图对象】
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
getCandidateViews
//这里拿到的是ThymeleafViewResolver 视图解析器
//里面最终的逻辑就是创建一个视图解析器,然后放到内置缓存中
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;
}
2、模板引擎-Thymeleaf
1、thymeleaf简介
Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
现代化、服务端Java模板引擎
2、基本语法
1、表达式
表达式名字 | 语法 | 用途 |
变量取值 | ${...} | 获取请求域、session域、对象等值 |
选择变量 | *{...} | 获取上下文对象值 |
消息 | #{...} | 获取国际化等值 |
链接 | @{...} | 生成链接 |
片段表达式 | ~{...} | jsp:include 作用,引入公共页面片段 |
2、字面量
文本值: 'one text' , 'Another one!' ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,.... 变量不能有空格
3、文本操作
字符串拼接: +
变量替换: |The name is ${name}|
4、数学运算
运算符: + , - , * , / , %
5、布尔运算
运算符: and , or
一元运算: ! , not
6、比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
7、条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
8、特殊操作
无操作: _
3、设置属性值-th:attr
设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}"> <fieldset> <input type="text" name="email" /> <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/> </fieldset> </form>
设置多个值
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
以上两个的代替写法 th:xxxx
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/> <form action="subscribe.html" th:action="@{/subscribe}">
所有h5兼容的标签写法
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
4、迭代
<tr th:each="prod : ${prods}"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr>
5、条件运算
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}"> <p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p> <p th:case="*">User is some other thing</p> </div>
6、属性优先级
3、thymeleaf使用
1、引入Starter
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2、自动配置好了thymeleaf
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class }) @AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class }) public class ThymeleafAutoConfiguration { }
自动配好的策略
- 1、所有thymeleaf的配置值都在 ThymeleafProperties
- 2、配置好了 SpringTemplateEngine
- 3、配好了 ThymeleafViewResolver
- 4、我们只需要直接开发页面
public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; //xxx.html
3、页面开发
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1 th:text="${msg}">哈哈</h1> <h2> <a href="www.atguigu.com" th:href="${link}">去百度</a> <br/> <a href="www.atguigu.com" th:href="@{link}">去百度2</a> </h2> </body> </html>
4、构建后台管理系统
1、项目创建
thymeleaf、web-starter、devtools、lombok
2、静态资源处理
自动配置好,我们只需要把所有静态资源放到 static 文件夹下
3、路径构建
th:action="@{/login}"
4、模板抽取
th:insert/replace/include
官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout
insert/replace/include区别
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
…included three times in host <div>
tags, like this:
<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>
…will result in:
<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
5、页面跳转
@PostMapping("/login") public String main(User user, HttpSession session, Model model){ if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){ //把登陆成功的用户保存起来 session.setAttribute("loginUser",user); //登录成功重定向到main.html; 重定向防止表单重复提交 return "redirect:/main.html"; }else { model.addAttribute("msg","账号密码错误"); //回到登录页面 return "login"; } }
6、数据渲染
@GetMapping("/dynamic_table") public String dynamic_table(Model model){ //表格内容的遍历 List<User> users = Arrays.asList(new User("zhangsan", "123456"), new User("lisi", "123444"), new User("haha", "aaaaa"), new User("hehe ", "aaddd")); model.addAttribute("users",users); return "table/dynamic_table"; }
<table class="display table table-bordered" id="hidden-table-info"> <thead> <tr> <th>#</th> <th>用户名</th> <th>密码</th> </tr> </thead> <tbody> <tr class="gradeX" th:each="user,stats:${users}"> <td th:text="${stats.count}">Trident</td> <td th:text="${user.userName}">Internet</td> <td >[[${user.password}]]</td> </tr> </tbody> </table>
7、视图渲染过程:从前端到后台
SpringBoot分析原理篇-SpringMVC视图渲染过程详细源码
按照一般思考的流程,这里直接从DispatcherServlet前端控制器的方法render()开始分析。
DispatcherServlet入口
- init():DispatcherServlet 继承FrameworkServlet;而FrameworkServlet继承HttpServletBean,并实现 了ApplicationContextAware接口,HttpServletBean实现init()方法,加载web.xml中DispatcherServlet配置,调用FrameworkServlet # initServletBean()。
- initServletBean():FrameworkServlet # initServletBean(),调用FrameworkServlet#initWebApplicationContext()。
- initWebApplicationContext():FrameworkServlet#initWebApplicationContext(),初始化WebApplicationContext 容器(IOC容器);调用FrameworkServlet#createWebApplicationContext(WebApplicationContext)
- createWebApplicationContext(WebApplicationContext):获取或生成容器,调用DispatcherServlet#onRefresh(ApplicationContext)。[^1]
- onRefresh(ApplicationContext): DispatcherServlet 的onRefresh() 方法,用于在ApplicationContext刷新后进行策略组件的初始化。
- initStrategies(ApplicationContext):DispatcherServlet 的initStrategies(ApplicationContext):初始化9个策略组件。
- doService()::设置request的相关属性
- doDispatch():前端控制器DispatcherServlet在doDispatch( )方法中去获取HandlerMapping和HandlerAdapter。
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException):异常分析,存在ModelAndView后调用render方法进行渲染,渲染完成后调用HandlerInterceptor拦截器的afterCompletion方法。
- render(): 解析视图名称获取对应View,调用View的render方法通过Model来渲染视图。
DispatcherServlet#Render()源码如下:
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
... ...
/**
* Render the given ModelAndView.//
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 根据request中的Accept-language请求头信息对应国际化响应方式
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
//设置响应的国际化
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
//ViewName视图名称:由前缀,返回值,后缀组成
//mv.getModelInternal()返回ModelMap,ModelMap继承LinkedHashMap<String, Object>,该参数可以为空
//locale:国际化
//视图名不为空,通过循环viewResolve,获取到对应的视图解析器,如Thymeleaf的视图解析器为:ThymeleafViewResolver,通过视图解析器获取对应的View对象实例
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
//不需要进行向上查询,没有视图名,但ModelAndView object包含这个View Object
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
//设置响应的状态码
response.setStatus(mv.getStatus().value());
}
//开始渲染:调用具体的View对象,进行视图渲染
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
... ...
}
View#render(model,request,response)方法
SpringMVC用于处理视图最重要的两个接口是ViewResolver和View,
View接口最主要的方法是这个Render的方法。
package org.springframework.web.servlet;
/**
* MVC View主要用于web交互,渲染内容,展示model,单个视图可以展示多个model属性
* MVC View for a web interaction. Implementations are responsible for rendering
* content, and exposing the model. A single view exposes multiple model attributes.
* 亚马逊这本书《Expert One-On-One J2EE Design and Development》里面有相关MVC类和函数的分析
* <p>This class and the MVC approach associated with it is discussed in Chapter 12 of
* <a href="https://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">Expert One-On-One J2EE Design and Development</a>
* by Rod Johnson (Wrox, 2002).
*
* <p>View implementations may differ widely. An obvious implementation would be
* JSP-based. Other implementations might be XSLT-based, or use an HTML generation library.
* This interface is designed to avoid restricting the range of possible implementations.
*
* <p>Views should be beans. They are likely to be instantiated as beans by a ViewResolver.
* As this interface is stateless, view implementations should be thread-safe.
*
*/
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
/**
* Return the content type of the view, if predetermined.
* <p>Can be used to check the view's content type upfront,
* i.e. before an actual rendering attempt.
* @return the content type String (optionally including a character set),
* or {@code null} if not predetermined
*/
@Nullable
default String getContentType() {
return null;
}
/**
* Model传进来的时候要去渲染,Model是一个上下文传递的数据源,request是一个请求源,response响应源。
* Render the view given the specified model.
* <p>The first step will be preparing the request: In the JSP case, this would mean
* setting model objects as request attributes. The second step will be the actual
* rendering of the view, for example including the JSP via a RequestDispatcher.
* @param model a Map with name Strings as keys and corresponding model
* objects as values (Map can also be {@code null} in case of empty model)
* @param request current HTTP request
* @param response he HTTP response we are building
* @throws Exception if rendering failed
*/
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}