上一篇我们结束了HandlerAdapter
各个组件的开发任务,本篇我们将开始研发视图的渲染;先看看类图
本篇我们先完成jsp视图的渲染以及重定向视图的渲染;在整个项目开发完成之后,我们再通过自定义视图的方式开发Excel视图和JSON视图。
11.1 开发步骤讲解
本节源代码的分支:view
View
public interface View {
default String getContentType() {
return null;
}
void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
首先我们开定义出视图的接口View
- getContentType: 控制视图支持的ContentType是什么,默认是返回空
- render: 通过response把model中的数据渲染成视图返回给用户
AbstractView
因为视图可以有很多的实现类,比如:JSON、JSP、HTML、各类模板等等,所以我们定义一个抽象类AbstractView
,通过模板方法定义出渲染的基本流程
public abstract class AbstractView implements View {
@Override
public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
this.prepareResponse(request, response);
this.renderMergedOutputModel(model, request, response);
}
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
}
protected abstract void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
- prepareResponse: 在实施渲染之前需要做的一些工作放入到这个方法中,比如:设置响应的头信息
- renderMergedOutputModel: 执行渲染的逻辑都将放入到这个方法中
RedirectView
当在控制器中返回的视图名是以redirect:
开头的都将视为重定向视图;
public class RedirectView extends AbstractView {
private String url;
public RedirectView(String url) {
this.url = url;
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
String targetUrl = createTargetUrl(model, request);
response.sendRedirect(targetUrl);
}
/**
* model中的数据添加到URL后面作为参数
*
* @param model
* @param request
* @return
*/
private String createTargetUrl(Map<String, Object> model, HttpServletRequest request) {
Assert.notNull(this.url, "url can not null");
StringBuilder queryParams = new StringBuilder();
model.forEach((key, value) -> {
queryParams.append(key).append("=").append(value).append("&");
});
if (queryParams.length() > 0) {
queryParams.deleteCharAt(queryParams.length() - 1);
}
StringBuilder targetUrl = new StringBuilder();
if (this.url.startsWith("/")) {
// Do not apply context path to relative URLs.
targetUrl.append(getContextPath(request));
}
targetUrl.append(url);
if (queryParams.length() > 0) {
targetUrl.append("?").append(queryParams.toString());
}
return targetUrl.toString();
}
private String getContextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
while (contextPath.startsWith("//")) {
contextPath = contextPath.substring(1);
}
return contextPath;
}
public String getUrl() {
return url;
}
}
- 重定向视图需要继承于
AbstractView
- 定义url,表示重定向的地址,实际也就是控制器中返回的视图名截取
redirect:
之后的字符串 - createTargetUrl: 根据url拼接出重定向的地址,如果有设置
contentPath
,需要把contentPath
拼接到链接的前面;如果Model中有属性值,需要把model中的属性值拼接到链接后面
InternalResourceView
public class InternalResourceView extends AbstractView {
private String url;
public InternalResourceView(String url) {
this.url = url;
}
@Override
public String getContentType() {
return "text/html";
}
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
exposeModelAsRequestAttributes(model, request);
RequestDispatcher rd = request.getRequestDispatcher(this.url);
rd.forward(request, response);
}
/**
* 把model中的数据放入到request
*
* @param model
* @param request
*/
private void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) {
model.forEach((name, value) -> {
if (Objects.nonNull(value)) {
request.setAttribute(name, value);
} else {
request.removeAttribute(name);
}
});
}
public String getUrl() {
return url;
}
}
InternalResourceView
需要支持JSP、HTML的渲染- url: 表示JSP文件的路径
- exposeModelAsRequestAttributes: 该方法把Model中的数据全部设置到了request中,方便在JSP中通过el表达式取值
11.2 单元测试
本次单元测试我们先只测试RedirectView
,InternalResourceView
放在后面整体测试;
@Test
public void test() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContextPath("/path");
MockHttpServletResponse response = new MockHttpServletResponse();
Map<String, Object> model = new HashMap<>();
model.put("name", "silently9527");
model.put("url", "http://silently9527.cn");
RedirectView redirectView = new RedirectView("/redirect/login");
redirectView.render(model, request, response);
response.getHeaderNames().forEach(headerName ->
System.out.println(headerName + ":" + response.getHeader(headerName)));
}
- 检查重定向地址是否有拼接上ContextPath
- 检查重定向地址是否有拼接上model中的数据
输出结果:
11.3 总结
本篇我们完成了RedirectView
、InternalResourceView
视图,后期通过自定义视图的方式实现excel视图;下一篇我们将开始开发视图的解析器ViewResolver
11.4 延展
springMVC中的视图MappingJackson2JsonView
、FreeMarkerView
、MustacheView
实现逻辑类似,可以对照着看看源码