1 缘起
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。模板引擎不属于特定技术领域,它是跨领域跨平台的概念。
Spring MVC可以适配不同的Java模板引擎,将Controller返回的结果使用正确的引擎进行页面渲染并将结果返回给浏览器,那么他是如何做到一套代码适配多种模板引擎的?本文对Spring MVC适配四种不同的模板引擎的方式进行了分析,希望可以提供一些思路,在将来的工作中可以用到。
2 适配过程分析
2.1 Spring MVC中提供的类概览
整个过程的入口在DispatcherServlet中,DispatcherServlet将浏览器传来的请求交给Controller进行处理后,得到了返回结果。这时,他需要将返回结果进行渲染,并返回给浏览器进行展示。渲染时,该使用哪种方式DispatcherServlet会遍历Spring容器中已经注入的视图解析器,找到最适合的一个拿来使用。此处的代码如下:
public class DispatcherServlet extends FrameworkServlet {
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;
}
}
Spring MVC中提供了一些视图解析器,他们的类层级关系如下:
DispatcherServlet往往先从ContentNegotiationViewResolver中进入,然后本质上使用ViewResolver中的resolveViewName方法来寻找实际的视图解析器。而最关键的几个方法已经在图中列出,Spring MVC适配不同的模板引擎就是通过重写图中的几个方法中的一个或几个来实现的。下面来一一进行分析。
2.2 适配Thymeleaf
Thymeleaf 是一种适用于 Web 和独立环境的现代服务器端 Java 模板引擎。它的官方介绍如下:
Thymeleaf is a modern server-side Java template engine for both web and standalone environments.
Thymeleaf’s main goal is to bring elegantnatural templatesto your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams.
With modules for Spring Framework, a host of integrations with your favourite tools, and the ability to plug in your own functionality, Thymeleaf is ideal for modern-day HTML5 JVM web development — although there is much more it can do.
Thymeleaf中的ThymeleafViewResolver类重写了AbstractCachingViewResolver中的createView方法,在创建视图时返回了自己定义的视图,从而实现了和Spring MVC的对接。具体代码如下:
public class ThymeleafViewResolver extends AbstractCachingViewResolver implements Ordered {
@Override
protected View createView(final String viewName, final Locale locale) throws Exception {
// First possible call to check "viewNames": before processing redirects and forwards
if (!this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
// Process redirects (HTTP redirects)
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, REDIRECT_URL_PREFIX);
}
// Process forwards (to JSP resources)
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
// The "forward:" prefix will actually create a Servlet/JSP view, and that's precisely its aim per the Spring
// documentation. See http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-forward-prefix
vrlogger.trace("[THYMELEAF] View \"{}\" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length(), viewName.length());
return new InternalResourceView(forwardUrl);
}
// Second possible call to check "viewNames": after processing redirects and forwards
if (this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a " +
"{} instance will be created for it", viewName, getViewClass().getSimpleName());
return loadView(viewName, locale);
}
}
2.3 适配FreeMarker
FreeMarker是Apache下面的一个项目。Apache FreeMarker™ 是一个模板引擎:一个基于模板和变化的数据生成文本输出(HTML 网页、电子邮件、配置文件、源代码等)的 Java 库。官方介绍如下:
Apache FreeMarker™ is a template engine: a Java library to generate text output (HTML web pages, e-mails, configuration files, source code, etc.) based on templates and changing data. Templates are written in the FreeMarker Template Language (FTL), which is a simple, specialized language (not a full-blown programming language like PHP). Usually, a general-purpose programming language (like Java) is used to prepare the data (issue database queries, do business calculations). Then, Apache FreeMarker displays that prepared data using templates. In the template you are focusing on how to present the data, and outside the template you are focusing on what data to present.
FreeMarker中的FreeMarkerViewResolver继承了AbstractTemplateViewResolver,重写了AbstractTemplateViewResolver中的buildView方法,而且重写了UrlBasedViewResolver中的instantiateView方法,最终返回了FreeMarkerView类型的对象。具体代码如下:
public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {
/**
* 重写UrlBasedViewResolver中的instantiateView,返回自己的视图对象
*/
@Override
protected AbstractUrlBasedView instantiateView() {
return (getViewClass() == FreeMarkerView.class ? new FreeMarkerView() : super.instantiateView());
}
}
2.4 适配Velocity
Velocity也是Apache下面的一个项目。它是一个基于 Java 的模板引擎。 它允许任何人使用简单而强大的模板语言来引用 Java 代码中定义的对象。官网介绍如下:
Velocity is a Java-based template engine. It permits anyone to use a simple yet powerful template language to reference objects defined in Java code.
When Velocity is used for web development, Web designers can work in parallel with Java programmers to develop web sites according to the Model-View-Controller (MVC) model, meaning that web page designers can focus solely on creating a site that looks good, and programmers can focus solely on writing top-notch code. Velocity separates Java code from the web pages, making the web site more maintainable over its lifespan and providing a viable alternative toJava Server Pages(JSPs) orPHP.
Velocity’s capabilities reach well beyond the realm of the web; for example, it can be used to generate SQL, PostScript and XML from templates. It can be used either as a standalone utility for generating source code and reports, or as an integrated component of other systems. For instance, Velocity provides template services forvarious web frameworks, enabling them with a view engine facilitating development of web applications according to a true MVC model.
Velocity也是有一个类VelocityViewResolver继承了AbstractTemplateViewResolver,并重写了buildView,实现了和Spring MVC的对接。关键代码如下:
public class VelocityViewResolver extends AbstractTemplateViewResolver {
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
VelocityView view = (VelocityView) super.buildView(viewName);
view.setDateToolAttribute(this.dateToolAttribute);
view.setNumberToolAttribute(this.numberToolAttribute);
if (this.toolboxConfigLocation != null) {
((VelocityToolboxView) view).setToolboxConfigLocation(this.toolboxConfigLocation);
}
return view;
}
}
2.5 适配JSP
严格来讲,JSP并不能算一种模板引擎。JSP(全称JavaServer Pages)是由Sun Microsystems公司主导创建的一种动态网页技术标准。它的出现比Spring框架要早,而Spring MVC出现以后,自然要适配这一古老的技术。
JSP(全称JavaServer Pages)是由Sun Microsystems公司主导创建的一种动态网页技术标准。JSP部署于网络服务器上,可以响应客户端发送的请求,并根据请求内容动态地生成HTML、XML或其他格式文档的Web网页,然后返回给请求者。JSP技术以Java语言作为脚本语言,为用户的HTTP请求提供服务,并能与服务器上的其它Java程序共同处理复杂的业务需求。
因为JSP本质上并不是一种模板引擎,不需要单独引入包来使用。所以,Spring MVC提供了一个类来适配JSP,这个类是InternalResourceViewResolver,它是UrlBasedViewResolver的子类,它重写了instantiateView方法来返回JSP页面。代码如下:
public class InternalResourceViewResolver extends UrlBasedViewResolver {
@Override
protected AbstractUrlBasedView instantiateView() {
return (getViewClass() == InternalResourceView.class ? new InternalResourceView() :
(getViewClass() == JstlView.class ? new JstlView() : super.instantiateView()));
}
}
3 总结
通过以上对Spring MVC适配四种不同模板引擎的方式分析发现,不同的模板引擎通过在不同的层次重写视图解析的方法实现了自己对Spring MVC的接入。而Spring MVC负责提供ViewResolver接口,供第三方工具实现,只要实现ViewResolver接口中的方法,就可以将自己渲染的页面接入到Spring框架中。当第三方工具接入自己以后,扫描已经接入的类,使用策略模式匹配到相应的工具上,从而实现对不通模板引擎的适配。
这也启示我们,如果我们要写一个框架适配不同的第三方工具,就需要提供标准的接口,只要他们实现了接口,就可以被我们的框架发现并管理,这样就具备了适配不同工具的能力。