FreeMarkerView
下面就以老牌模版引擎FreeMarker
为例,窥探一下实现的思路:
public class FreeMarkerView extends AbstractTemplateView { // FreeMarker Configuration: "ISO-8859-1" if not specified otherwise @Nullable private String encoding; // FreeMarker的配置文件 里面极其多的配置信息~~比如文件后缀名、编码等 @Nullable private Configuration configuration; @Nullable private TaglibFactory taglibFactory; @Nullable private ServletContextHashModel servletContextHashModel; // 就是检查这个模版存不存在~~~ @Override public boolean checkResource(Locale locale) throws Exception { String url = getUrl(); Assert.state(url != null, "'url' not set"); try { // Check that we can get the template, even if we might subsequently get it again. getTemplate(url, locale); return true; } catch (FileNotFoundException ex) { // Allow for ViewResolver chaining... return false; } catch (ParseException ex) { throw new ApplicationContextException("Failed to parse [" + url + "]", ex); } catch (IOException ex) { throw new ApplicationContextException("Failed to load [" + url + "]", ex); } } ... // 最终会根据此模版去渲染~~~这是FreeMarker真正去做的事~~~~ protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response) throws IOException, TemplateException { template.process(model, response.getWriter()); } }
此处我贴一个直接使用FreeMarker
的使用案例,方便小伙伴对它的使用步骤有个感性的认识~~~
@Test public void testFreeMarker() throws Exception{ // 第0步,创建模板文件(自己找个目录创建,文件一般都以.ftl结尾) // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是freemarker对于的版本号。 Configuration configuration = new Configuration(Configuration.getVersion()); // 第二步:设置模板文件所在的路径。 configuration.setDirectoryForTemplateLoading(new File("D:\\workspace\\e3-item-web\\src\\main\\webapp\\WEB-INF\\ftl")); // 第三步:设置模板文件使用的字符集。一般就是utf-8. configuration.setDefaultEncoding("utf-8"); // 第四步:加载一个模板,创建一个模板对象。 Template template = configuration.getTemplate("hello.ftl"); // 第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。 Map data = new HashMap<>(); //向数据集中添加数据 data.put("hello", "this is my first freemarker test!"); // 第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。 Writer out = new FileWriter(new File("D:\\Freemarker\\hello.txt")); // 第七步:调用模板对象的process方法输出文件,生成静态页面。 template.process(data, out); // 第八步:关闭流。 out.close(); }
TilesView
略
XsltView
略
InternalResourceView:最重要的一个视图
Internal:内部的。所以该视图表示:内部资源视图。
// @since 17.02.2003 第一版就有了 public class InternalResourceView extends AbstractUrlBasedView { // 指定是否始终包含视图而不是转发到视图 //默认值为“false”。打开此标志以强制使用servlet include,即使可以进行转发 private boolean alwaysInclude = false; // 设置是否显式阻止分派回当前处理程序路径 表示是否组织循环转发,比如自己转发自己 // 我个人认为这里默认值用true反而更好~~~因为需要递归的情况毕竟是极少数~ // 其实可以看到InternalResourceViewResolver的buildView方法里是把这个属性显示的设置为true了的~~~ private boolean preventDispatchLoop = false; public InternalResourceView(String url, boolean alwaysInclude) { super(url); this.alwaysInclude = alwaysInclude; } @Override protected boolean isContextRequired() { return false; } // 请求包含、请求转发是它特有的~~~~~ @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. // 把model里的数据都request.setAttribute里 // 因为最终JSP里面取值其实都是从request等域对象里面取~ exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. // JstlView有实现此protected方法~ exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). 注意:此处特指JSP // 就是一句话:request.getRequestDispatcher(path) RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. //useInclude:若alwaysInclude==true或者该request是incluse请求或者response.isCommitted()==true // 那就走incluse,否则走forward~~~~~ if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } rd.forward(request, response); } } // 拿到URL,做一个循环检查~~~ 若是循环转发就报错~~ protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) throws Exception { String path = getUrl(); Assert.state(path != null, "'url' not set"); if (this.preventDispatchLoop) { String uri = request.getRequestURI(); if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) { throw new ServletException("Circular view path [" + path + "]: would dispatch back " + "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " + "(Hint: This may be the result of an unspecified view, due to default view name generation.)"); } } return path; } }
这样我们的InternalResourceView这个视图就渲染完成了,为何这么简单呢?因为它最终要么是include,要么forward掉了。交给别的Servlet去处理了。
而我们知道JSP的本质其实就是一个servlet,所以转发给它处理其实就是定位到了我们的JSP页面,它完成的对response写入动作。
比如:
@GetMapping("/index") public Object index() { InternalResourceView view = new InternalResourceView(); view.setUrl("/index.jsp"); view.setPreventDispatchLoop(true); return view; }
注意:直接返回一个View是不会经过
ViewResolver
处理的
这样是能够正常展示出我们的jsp
页面的。但是,但是,但是如果我们是一个html页面呢?比如如下:
@GetMapping("/index") public Object index() { InternalResourceView view = new InternalResourceView(); view.setUrl("/index.html"); view.setPreventDispatchLoop(true); return view; }
访问直接报错:
原因很简单,因为你是HTML页面,所以它并没有对应的Servlet,所以你转发的时候肯定就报错了。所以接下里的问题变成了:
如何让我们的Controller跳转到HTML页面呢???其实这个涉及到Spring MVC中对静态资源的访问问题
说在前面:因为html属于静态数据,所以一般我们需要访问的话都是通过mvc:resources等这种配置去达到目的让可议直接访问。但是不乏业务中可能也存在通过controller方法跳转到html页面的需求(虽然你可以JSP里面全是html页面),本文就实现这个效果,能加深对此视图的了解~~
参考:【小家Spring】Spring MVC控制器中Handler的四种实现方式:Controller、HttpRequestHandler、Servlet、@RequestMapping
的最后半段来了解Spring MVC对静态资源的处理
JstlView
它继承自InternalResourceView,所以还是和JSP相关的。jstl相关的jar为:jstl.jar和standard.jar。它哥俩已经老久都没有更新过了,不过可以理解。毕竟JSP都快寿终正寝了。
它还可以和国际化有关,若使用Jstl的fmt标签,需要在SpringMVC的配置文件中配置国际化资源文件。
public class JstlView extends InternalResourceView { ... public JstlView(String url, MessageSource messageSource) { this(url); this.messageSource = messageSource; } // 导出一些JSTL需要的东西 @Override protected void exposeHelpers(HttpServletRequest request) throws Exception { if (this.messageSource != null) { JstlUtils.exposeLocalizationContext(request, this.messageSource); } else { JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext())); } } }
因为JSTL技术比较古老了,现在很少人使用(当然JSP的使用人群还是有不少的,需要较重点的了解一下,毕竟是java嫡系技术,在历史进程中还是很重要的存在的),所以这里也不做演示了~
ScriptTemplateView
这个是脚本渲染引擎,从Spring4.2开始提供了一个ScriptTemplateView作为脚本模版视图。
脚本渲染引擎,据我目前了解,是为Kotlin而准备的,此处一个大写的:略
总结
视图就是展示给用户看的结果。可以是很多形式,例如:html、JSP、excel表单、Word文档、PDF文档、JSON数据、freemarker模板视图等等。
视图(解析器)作为Spring MVC设计中非常优秀的一环,最重要的是这种设计思想、作者的设计意图,值得我们深思和学习