SpringBoot2核心技术-核心功能-02-Web开发-04

视图解析与模板引擎

视图解析: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()--->

 

 

 

image.png

【自定义视图】

(导出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、视图解析器(内容协商视图处理器支持解析的类型)

image.png

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、属性优先级

image.png

 

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">
  &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>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>

  <div>
    &copy; 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、视图渲染过程:从前端到后台

引用

1.SpingMVC架构设计时序图

SpringBoot分析原理篇-SpringMVC视图渲染过程详细源码

按照一般思考的流程,这里直接从DispatcherServlet前端控制器的方法render()开始分析。

DispatcherServlet入口

  1. init():DispatcherServlet 继承FrameworkServlet;而FrameworkServlet继承HttpServletBean,并实现 了ApplicationContextAware接口,HttpServletBean实现init()方法,加载web.xml中DispatcherServlet配置,调用FrameworkServlet # initServletBean()。
  2. initServletBean():FrameworkServlet # initServletBean(),调用FrameworkServlet#initWebApplicationContext()。
  3. initWebApplicationContext():FrameworkServlet#initWebApplicationContext(),初始化WebApplicationContext 容器(IOC容器);调用FrameworkServlet#createWebApplicationContext(WebApplicationContext)
  4. createWebApplicationContext(WebApplicationContext):获取或生成容器,调用DispatcherServlet#onRefresh(ApplicationContext)。[^1]
  5. onRefresh(ApplicationContext): DispatcherServlet 的onRefresh() 方法,用于在ApplicationContext刷新后进行策略组件的初始化。
  6. initStrategies(ApplicationContext):DispatcherServlet 的initStrategies(ApplicationContext):初始化9个策略组件。
  7. doService()::设置request的相关属性
  8. doDispatch():前端控制器DispatcherServlet在doDispatch( )方法中去获取HandlerMapping和HandlerAdapter。
  9. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException):异常分析,存在ModelAndView后调用render方法进行渲染,渲染完成后调用HandlerInterceptor拦截器的afterCompletion方法。
  10. 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;
}

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值