Asciidoc是一种基于文本的文档格式,因此如果要将文档提交到版本控制系统中并跟踪不同版本之间的更改,它非常有用。 这使Asciidoc成为编写书籍,技术文档,常见问题解答或用户手册的理想工具。
创建Asciidoc文档后,很可能要发布该文档,而做到这一点的一种方法是将该文档发布到我们的网站上。 今天,我们将学习如何使用AsciidoctorJ将Asciidoc文档转换为HTML,以及如何使用Spring MVC渲染创建HTML。
我们的应用程序的要求是:
- 它必须支持从类路径中找到的Asciidoc文档。
- 它必须支持作为String对象给出的Asciidoc标记。
- 它必须将Asciidoc文档转换为HTML并呈现创建HTML。
- 它必须将创建HTML“嵌入”到我们的应用程序布局中。
让我们从获取Maven所需的依赖关系开始。
使用Maven获取所需的依赖关系
通过执行以下步骤,我们可以使用Maven获得所需的依赖关系:
- 启用Spring IO平台 。
- 配置所需的依赖项。
首先 ,我们可以通过将以下代码段添加到我们的POM文件中来启用Spring IO平台:
<dependencyManagement><dependencies><dependency><groupId>io.spring.platform</groupId><artifactId>platform-bom</artifactId><version>1.0.2.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>
其次 ,我们可以按照以下步骤配置所需的依赖项:
- 在pom.xml文件中配置日志记录依赖性。
- 将spring-webmvc依赖项添加到pom.xml文件。
- 将Servlet API依赖项添加到POM文件中。
- 在POM文件中配置Sitemesh(版本3.0.0)依赖性。 Sitemesh确保我们应用程序的每个页面使用一致的外观。
- 将asciidoctorj依赖性(版本1.5.0)添加到pom.xml文件。 AsciidoctorJ是Asciidoctor的Java API,我们使用它来将Asciidoc文档转换为HTML。
pom.xml文件的相关部分如下所示:
<dependencies><!-- Logging --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></dependency><!-- Spring --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId></dependency><!-- Java EE --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><scope>provided</scope></dependency><!-- Sitemesh --><dependency><groupId>org.sitemesh</groupId><artifactId>sitemesh</artifactId><version>3.0.0</version></dependency><!-- AsciidoctorJ --><dependency><groupId>org.asciidoctor</groupId><artifactId>asciidoctorj</artifactId><version>1.5.0</version></dependency>
</dependencies>
因为我们使用Spring IO Platform,所以不必指定Spring IO Platform中的工件的依赖版本。
让我们继续并开始实现我们的应用程序。
使用Spring MVC渲染Asciidoc文档
通过执行以下步骤,我们可以满足应用程序的要求:
- 配置我们的Web应用程序和Sitemesh过滤器。
- 实现视图类,这些视图类负责将Asciidoc文档转换为HTML并呈现创建HTML。
- 实现使用创建的视图类的控制器方法。
让我们开始吧。
配置Sitemesh
我们要做的第一件事是配置Sitemesh。 我们可以按照以下三个步骤配置Sitemesh:
- 在Web应用程序配置中配置Sitemesh过滤器。
- 创建用于为我们的应用程序创建一致外观的装饰器。
- 配置Sitemesh过滤器使用的装饰器。
首先 ,我们必须在Web应用程序配置中配置Sitemesh过滤器。 我们可以按照以下步骤配置Web应用程序:
- 创建一个实现WebApplicationInitializer接口的WebAppConfig类。
- 通过执行以下步骤来实现WebApplicationInitializer接口的onStartup()方法 :
- 创建一个AnnotationConfigWebApplicationContext对象并将其配置为处理我们的应用程序上下文配置类。
- 配置调度程序servlet 。
- 配置Sitemesh过滤器以处理应用程序的JSP页面和使用url模式'/ asciidoctor / *'的所有控制器方法返回HTML
- 将新的ContextLoaderListener对象添加到ServletContext中 。 ContextLoaderListener负责启动和关闭Spring WebApplicationContext 。
WebAppConfig类的源代码如下所示(突出显示了Sitemesh配置):
import org.sitemesh.config.ConfigurableSiteMeshFilter;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import java.util.EnumSet;public class WebAppConfig implements WebApplicationInitializer {private static final String DISPATCHER_SERVLET_NAME = "dispatcher";private static final String SITEMESH3_FILTER_NAME = "sitemesh";private static final String[] SITEMESH3_FILTER_URL_PATTERNS = {"*.jsp", "/asciidoctor/*"};@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();rootContext.register(WebAppContext.class);configureDispatcherServlet(servletContext, rootContext);configureSitemesh3Filter(servletContext);servletContext.addListener(new ContextLoaderListener(rootContext));}private void configureDispatcherServlet(ServletContext servletContext, WebApplicationContext rootContext) {ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME,new DispatcherServlet(rootContext));dispatcher.setLoadOnStartup(1);dispatcher.addMapping("/");}private void configureSitemesh3Filter(ServletContext servletContext) {FilterRegistration.Dynamic sitemesh = servletContext.addFilter(SITEMESH3_FILTER_NAME, new ConfigurableSiteMeshFilter());EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);sitemesh.addMappingForUrlPatterns(dispatcherTypes, true, SITEMESH3_FILTER_URL_PATTERNS);}
}
- 如果要看一下示例应用程序的应用程序上下文配置类,可以从Github中获得它 。
其次 ,我们必须创建为我们的应用程序提供一致外观的装饰器。 我们可以按照以下步骤进行操作:
- 将装饰器文件创建到src / main / webapp / WEB-INF目录。 我们的示例应用程序的装饰器文件称为layout.jsp 。
- 将提供一致外观HTML添加到创建的装饰器文件中。
- 确保Sitemesh将从返回HTML中找到的标题添加到Web浏览器呈现HTML中。
- 配置Sitemesh,将从返回HTML的开头找到HTML元素添加到呈现HTML的开头。
- 确保Sitemesh将在返回HTML中找到的正文添加到显示给用户HTML中。
装饰器文件( layout.jsp )的源代码如下所示(与Sitemesh相关的部分已突出显示):
<!doctype html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title><sitemesh:write property="title"/></title><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" type="text/css" href="${contextPath}/static/css/bootstrap.css"/><link rel="stylesheet" type="text/css" href="${contextPath}/static/css/bootstrap-theme.css"/><script type="text/javascript" src="${contextPath}/static/js/jquery-2.1.1.js"></script><script type="text/javascript" src="${contextPath}/static/js/bootstrap.js"></script><sitemesh:write property="head"/>
</head>
<body>
<nav class="navbar navbar-inverse" role="navigation"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle" data-toggle="collapse"data-target="#bs-example-navbar-collapse-1"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button></div><div class="collapse navbar-collapse"><ul class="nav navbar-nav"><li><a href="${contextPath}/">Document list</a></li></ul></div></div>
</nav>
<div class="container-fluid"><sitemesh:write property="body"/>
</div>
</body>
</html>
第三 ,我们必须配置Sitemesh以使用在第二步中创建的装饰器文件。 我们可以按照以下步骤进行操作:
- 在src / main / webapp / WEB-INF目录中创建一个sitemesh3.xml文件。
- 将Sitemesh配置为使用我们的装饰器处理Sitemesh过滤器处理的所有请求。
sitemesh3.xml文件如下所示:
<sitemesh><mapping path="/*" decorator="/WEB-INF/layout/layout.jsp"/>
</sitemesh>
这就对了。 现在,我们已经配置了Sitemesh,以为我们的应用程序提供一致的外观。 让我们继续前进,了解如何实现将Asciidoc标记转换为HTML并呈现所创建HTML的视图类。
实施视图类
在开始实现将Asciidoc标记转换为HTML并呈现创建HTML的视图类之前,我们必须快速了解一下我们的需求。 与该步骤相关的要求是:
- 我们的解决方案必须支持从类路径中找到的Asciidoc文档。
- 我们的解决方案必须支持作为String对象给出的Asciidoc标记。
- 我们的解决方案必须将Asciidoc文档转换为HTML并呈现创建HTML。
这些要求建议我们应该创建三个视图类。 这些视图类如下所述:
- 我们应该创建一个抽象基类,该基类包含将Asciidoc标记转换为HTML并呈现创建HTML的逻辑。
- 我们应该创建一个视图类,该类可以从类路径中找到的文件中读取Asciidoc标记。
- 我们应该创建一个可以从String对象读取Asciidoc标记的视图类。
换句话说,我们必须创建以下类结构:
首先 ,我们必须实现AbstractAsciidoctorHtmlView类。 此类是一个抽象基类,可将Asciidoc标记转换为HTML并呈现创建HTML。 我们可以按照以下步骤实现此类:
- 创建AbstractAsciidoctorHtmlView类并扩展AbstractView类。
- 在创建的类中添加一个构造函数,并将视图的内容类型设置为“ text / html”。
- 将一个受保护的抽象方法getAsciidocMarkupReader()添加到创建的类,并将其返回类型设置为Reader 。 此抽象类的子类必须实现此方法,并且此方法的实现必须返回一个Reader对象,该对象可用于读取呈现的Asciidoc标记。
- 将私有的getAsciidoctorOptions()方法添加到创建的类中,并通过返回Asciidoctor的配置选项来实现它。
- 重写AbstractView类的renderMergedOutputModel()方法 ,并通过将Asciidoc文档转换为HTML并呈现创建HTML来实现它。
AbstractAsciidoctorHtmlView类的源代码如下所示:
import org.asciidoctor.Asciidoctor;
import org.asciidoctor.Options;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.view.AbstractView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Reader;
import java.io.Writer;
import java.util.Map;public abstract class AbstractAsciidoctorHtmlView extends AbstractView {public AbstractAsciidoctorHtmlView() {super.setContentType(MediaType.TEXT_HTML_VALUE);}protected abstract Reader getAsciidocMarkupReader();@Overrideprotected void renderMergedOutputModel(Map<String, Object> model,HttpServletRequest request,HttpServletResponse response) throws Exception {//Set the content type of the response to 'text/html'response.setContentType(super.getContentType());Asciidoctor asciidoctor = Asciidoctor.Factory.create();Options asciidoctorOptions = getAsciidoctorOptions();try (//Get the reader that reads the rendered Asciidoc document//and the writer that writes the HTML markup to the request bodyReader asciidoctorMarkupReader = getAsciidocMarkupReader();Writer responseWriter = response.getWriter();) {//Transform Asciidoc markup into HTML and write the created HTML //to the response bodyasciidoctor.render(asciidoctorMarkupReader, responseWriter, asciidoctorOptions);}}private Options getAsciidoctorOptions() {Options asciiDoctorOptions = new Options();//Ensure that Asciidoctor includes both the header and the footer of the Asciidoc //document when it is transformed into HTML.asciiDoctorOptions.setHeaderFooter(true);return asciiDoctorOptions;}
}
其次 ,我们必须实现ClasspathFileAsciidoctorHtmlView类。 此类可以从类路径中找到的文件中读取Asciidoc标记。 我们可以按照以下步骤实现此类:
- 创建ClasspathFileAsciidoctorHtmlView类,并扩展AbstractAsciidoctorHtmlView类。
- 在创建的类中添加一个名为asciidocFileLocation的私有String字段。 此字段包含转换为HTML的Asciidoc文件的位置。 必须以Class类的getResourceAsStream()方法可以理解的格式指定此位置。
- 创建一个构造器,该构造器将呈现的Asciidoc文件的位置作为构造器参数。 通过调用超类的构造函数并将呈现的Asciidoc文件的位置存储到asciidocFileLocation字段中来实现构造函数 。
- 重写getAsciidocMarkupReader()方法并通过返回一个新的InputStreamReader对象来实现它,该对象用于读取从类路径中找到的Asciidoc文件。
ClasspathFileAsciidoctorHtmlView类的源代码如下所示:
import java.io.InputStreamReader;
import java.io.Reader;public class ClasspathFileAsciidoctorHtmlView extends AbstractAsciidoctorHtmlView {private final String asciidocFileLocation;public ClasspathFileAsciidoctorHtmlView(String asciidocFileLocation) {super();this.asciidocFileLocation = asciidocFileLocation;}@Overrideprotected Reader getAsciidocMarkupReader() {return new InputStreamReader(this.getClass().getResourceAsStream(asciidocFileLocation));}
}
第三 ,我们必须实现StringAsciidoctorHtmlView类,该类可以从String对象读取Asciidoc标记。 我们可以按照以下步骤实现此类:
- 创建StringAsciidoctorHtmlView类,并扩展AbstractAsciidoctorHtmlView类。
- 在创建的类中添加一个名为asciidocMarkup的私有String字段。 此字段包含已转换为HTML的Asciidoc标记。
- 创建一个将呈现的Asciidoc标记用作构造函数参数的构造函数。 通过调用超类的构造函数并将呈现的Asciidoc标记设置为asciidocMarkup字段来实现此构造函数。
- 重写getAsciidocMarkupReader()方法,并通过返回一个新的StringReader对象来实现它,该对象用于读取存储在asciidocMarkup字段中的Asciidoc标记。
StringAsciidoctorHtmlView的源代码如下所示:
import java.io.Reader;
import java.io.StringReader;public class StringAsciidoctorHtmlView extends AbstractAsciidoctorHtmlView {private final String asciidocMarkup;public StringAsciidoctorHtmlView(String asciidocMarkup) {super();this.asciidocMarkup = asciidocMarkup;}@Overrideprotected Reader getAsciidocMarkupReader() {return new StringReader(asciidocMarkup);}
}
现在,我们已经创建了所需的视图类。 让我们继续研究如何在Spring MVC Web应用程序中使用这些类。
使用创建的视图类
我们的最后一步是创建使用创建的视图类的控制器方法。
我们必须实现以下描述的两种控制器方法:
- renderAsciidocDocument()方法处理发送到url'/ asciidoctor / document'的GET请求,然后将Asciidoc文档转换为HTML并呈现创建HTML。
- renderAsciidocString()方法处理发送到url'/ asciidoctor / string'的GET获取请求,然后将Asciidoc 字符串转换为HTML并呈现创建HTML。
AsciidoctorController类的源代码如下所示:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;@Controller
public class AsciidoctorController {private static final String ASCIIDOC_FILE_LOCATION = "/asciidoctor/document.adoc";private static final String ASCIIDOC_STRING = "= Hello, AsciiDoc (String)!\n" +"Doc Writer <doc@example.com>\n" +"\n" +"An introduction to http://asciidoc.org[AsciiDoc].\n" +"\n" +"== First Section\n" +"\n" +"* item 1\n" +"* item 2\n" +"\n" +"1\n" +"puts \"Hello, World!\"";@RequestMapping(value = "/asciidoctor/document", method = RequestMethod.GET)public ModelAndView renderAsciidocDocument() {//Create the view that transforms an Asciidoc document into HTML and//renders the created HTML.ClasspathFileAsciidoctorHtmlView docView = new ClasspathFileAsciidoctorHtmlView(ASCIIDOC_FILE_LOCATION);return new ModelAndView(docView);}@RequestMapping(value = "/asciidoctor/string", method = RequestMethod.GET)public ModelAndView renderAsciidocString() {//Create the view that transforms an Asciidoc String into HTML and//renders the created HTML.StringAsciidoctorHtmlView stringView = new StringAsciidoctorHtmlView(ASCIIDOC_STRING);return new ModelAndView(stringView);}
}
附加信息:
- @Controller批注的Javadoc
- @RequestMapping注释的Javadoc
- ModelAndView类的Javadoc
现在,我们已经创建了使用我们的视图类的控制器方法。 当我们的应用程序的用户调用url'/ asciidoctor / document'的GET请求时,呈现HTML页面的源代码如下所示(由Asciidoctor创建的部分被突出显示):
<!doctype html><html>
<head><title>Hello, AsciiDoc (File)!</title><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" type="text/css" href="/static/css/bootstrap.css"/><link rel="stylesheet" type="text/css" href="/static/css/bootstrap-theme.css"/><script type="text/javascript" src="/static/js/jquery-2.1.1.js"></script><script type="text/javascript" src="/static/js/bootstrap.js"></script><meta charset="UTF-8">
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 1.5.0">
<meta name="author" content="Doc Writer"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400">
<link rel="stylesheet" href="./asciidoctor.css"></head>
<body>
<nav class="navbar navbar-inverse" role="navigation"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle" data-toggle="collapse"data-target="#bs-example-navbar-collapse-1"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button></div><div class="collapse navbar-collapse"><ul class="nav navbar-nav"><li><a href="/">Document list</a></li></ul></div></div>
</nav>
<div class="container-fluid"><div id="header">
<h1>Hello, AsciiDoc (File)!</h1>
<div class="details">
<span id="author" class="author">Doc Writer</span><br>
<span id="email" class="email"><a href="mailto:doc@example.com">doc@example.com</a></span><br>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>An introduction to <a href="http://asciidoc.org">AsciiDoc</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_first_section">First Section</h2>
<div class="sectionbody">
<div class="ulist">
<ul>
<li>
<p>item 1</p>
</li>
<li>
<p>item 2</p>
</li>
</ul>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-ruby" data-lang="ruby">puts "Hello, World!"</code></pre>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2014-09-21 14:21:59 EEST
</div>
</div></div>
</body>
</html>
如我们所见,由Asciidoctor创建HTML嵌入到我们的布局中,这为我们的应用程序用户提供了一致的用户体验。
让我们继续并评估此解决方案的优缺点。
利弊
我们解决方案的优点是:
- 呈现HTML文档与我们应用程序的其他页面具有相同的外观。 这意味着我们可以为应用程序的用户提供一致的用户体验。
- 我们可以呈现静态文件和可以从数据库加载的字符串。
我们解决方案的缺点是:
- 我们的简单应用程序的war文件很大(51.9 MB)。 原因是,即使Asciidoctor具有Java API,它也是用Ruby编写的。 因此,我们的应用程序需要两个大的jar文件:
- asciidoctorj-1.5.0.jar文件的大小为27.5MB。
- 当用户请求时,我们的应用程序将Asciidoc文档转换为HTML。 这对我们的控制器方法的响应时间有负面影响,因为文档越大,处理该文档所花费的时间就越长。
- 将Asciidoc文档呈现为HTML的第一个请求比下一个请求慢4-5倍。 我没有剖析该应用程序,但我认为JRuby与此有关。
- 目前,如果我们要将Asciidoc文档转换为PDF文档,则无法使用此技术。
让我们继续并总结从这篇博客文章中学到的知识。
摘要
这篇博客文章教会了我们三件事:
- 我们了解了如何配置Sitemesh,以为我们的应用程序提供一致的外观。
- 我们学习了如何创建将Asciidoc文档转换为HTML并呈现所创建HTML的视图类。
- 即使我们的解决方案有效,它也有很多缺点,可能使其在现实生活中无法使用。
本教程的下一部分描述了如何解决此解决方案的性能问题。
PS:如果您想体验本博客文章的示例应用程序,可以从Github获得 。
翻译自: https://www.javacodegeeks.com/2014/10/using-asciidoctor-with-spring-rendering-asciidoc-documents-with-spring-mvc.html