关键词:view technology、template、template engine、markup。内容较多,按需查用即可。
- 介绍
- Thymeleaf
- Groovy Markup Templates
- Velocity & FreeMarker
- JSP & JSTL
- Script templates
- XML Marshalling View(暂空)
- Tiles(暂空)
- XSLT(暂空)
- Document views (PDF/Excel)(暂空)
- JasperReports(暂空)
- Feed Views(暂空)
- JSON Mapping View(暂空)
- XML Mapping View (暂空)
Spring 有很多优越的地方,其中一个就是将view技术与MVC框架的其他部分相隔离。例如,在JSP存在的情况下使用Groovy Markup Templates 还是使用Thymeleaf,仅仅是一个配置问题。
本章覆盖了主要的view技术,嗯嗯,可以与Spring结合的那些,并简明的说明了如何增加新的view技术。
本章假定你已经熟悉了Spring 4 官方文档学习(十一)Web MVC 框架之resolving views 解析视图 -- 它覆盖了views如何耦合到MVC框架的基础。
Thymeleaf是一个非常好的例子:view技术完美的嵌入MVC框架中。该集成的支持不是由Spring团队提供的,而是由Thymeleaf团队提供的。
为Spring配置Thymeleaf,只需要定义几个beans,例如一个ServletContextTemplateResolver、一个SpringTemplateEngine、一个ThymeleafViewResolver。 详见Thymeleaf+Spring。
Groovy Markup Template Engine 是Spring支持的另一个view技术。该模板引擎的主要目标是生成 类XML (XML, XHTML, HTML5,...)的标记,也可以被用于生成任意基于文本的内容。
嗯嗯,要求classpath中有 Groovy 2.3.1+。
配置Groovy Markup Template Engine 非常简单:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.groovy(); } @Bean public GroovyMarkupConfigurer groovyMarkupConfigurer() { GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer(); configurer.setResourceLoaderPath("/WEB-INF/"); return configurer; } }
使用MVC namespace的XML 也类似:
<mvc:annotation-driven/> <mvc:view-resolvers> <mvc:groovy/> </mvc:view-resolvers> <mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
不像传统的模板引擎,这个引擎(Groovy)依赖于DSL -- DSL使用了builder syntax。 下面是一个HTML页面的简单模板:
yieldUnescaped '<!DOCTYPE html>' html(lang:'en') { head { meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"') title('My page') } body { p('This is an example of HTML contents') } }
Velocity 和 FreeMarker是模板语言,可被用作Spring MVC application中的view技术。它们是非常相似的,并且服务于相似的需要,因此本部分将二者放在一起。关于二者的语法和语义的区别,见FreeMarker站点。
自Spring Framework 4.3起,对Velocity的支持已经是deprecated的了,原因是Apache Velocity project已经有6年没有活动的维护了!我们推荐Spring的FreeMarker支持,或者Thymeleaf--其自身带有Spring支持。
如果想使用Velocity或FreeMarker,你的web应用应该包含velocity-1.x.x.jar
or freemarker-2.x.jar
,另外,Velocity还需要commons-collections.jar
。通常它们会被放在 WEB-INF/lib 文件夹中 -- 该位置会由Java EE server自动发现并添加到应用的classpath中。当然,我们也假定你已经添加了spring-webmvc.jar
。另外,如果想在Velocity views中使用Spring的dateToolAttribute 或 numberToolAttribute,还需要添加velocity-tools-generic-1.x.jar
。
一个合适的配置是这样来初始化的--通过在你的 *-servlet.xml 中添加相关的configurer bean definition,如下:
<!-- 这个bean设置了Velocity环境--基于模板的一个root path。也可以在一个properties文件中指定更多的控制,但默认的已经很好了。
This bean sets up the Velocity environment for us based on a root path for templates. Optionally, a properties file can be specified for more control over the Velocity environment, but the defaults are pretty sane for file based template loading. --> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/velocity/"/> </bean> <!-- View resolvers can also be configured with ResourceBundles or XML files. If you need different view resolving based on Locale, you have to use the resource bundle resolver. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".vm"/> </bean>
<!-- freemarker config --> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> </bean> <!-- View resolvers can also be configured with ResourceBundles or XML files. If you need different view resolving based on Locale, you have to use the resource bundle resolver. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".ftl"/> </bean>
提示:对于非web的应用,添加一个 VelocityConfigurationFactoryBean
或 FreeMarkerConfigurationFactoryBean
即可。-- Java-based config >> @Configuration classes。
你的模板需要保存在一个目录中 -- 该目录由上面提到的 *Configurer bean指定!
本文档不涉及创建模板的细节 -- 有兴趣可以去看相关的站点。
如果你使用了上面提到的view resolvers,那么逻辑视图名会关联到模板文件名 -- 类似于InternalResourceViewResolver之于JSP。所以,当你的controller返回了一个包含welcome 视图名的ModelAndView对象时,该resolver会查找 /WEB-INF/freemarker/welcome.ftl
或 /WEB-INF/velocity/welcome.vm
模板。
上面提到的基本配置适用于大多数应用需求,除此之外,还有一些配置选项 -- 适用于不常见或者高级的需求。
velocity.properties
该文件完全是可选的,如果指定了,那其中的值会被传给Velocity runtime,从而配置velocity。只有高级配置时才需要,如果你需要,在VelocityConfigurer bean definition中指定其位置即可。如下:
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="configLocation" value="/WEB-INF/velocity.properties"/> </bean>
或者,也可以这样指定:
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="velocityProperties"> <props> <prop key="resource.loader">file</prop> <prop key="file.resource.loader.class"> org.apache.velocity.runtime.resource.loader.FileResourceLoader </prop> <prop key="file.resource.loader.path">${webapp.root}/WEB-INF/velocity</prop> <prop key="file.resource.loader.cache">false</prop> </props> </property> </bean>
FreeMarker
通过设置FreeMarkerConfigurer bean的properties,即可将FreeMarker的 Settings 和 SharedVariables 可以被直接传给FreeMarker的Configuration对象(由Spring管理)。freemarkerSettings property需要一个java.util.Properties对象;freemarkerVariables则需要一个java.util.Map!如下:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> <property name="freemarkerVariables"> <map> <entry key="xml_escape" value-ref="fmXmlEscape"/> </map> </property> </bean> <bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>
详见FreeMarker文档。
Spring提供了在JSP中使用的一个标签库,其中有一个<spring:bind/>标签。该标签主要是让form显示来自form backing objects的值、显示Validator校验失败的结果。从版本1.1开始,Spring在Velocity和FreeMarker中支持同样的功能 -- 带有额外的便捷的宏,用于生成form input元素。 -- 几个意思?
spring-webmvc.jar中维持了宏的一个标准集合,可以用于二者(Velocity和FreeMarker)。
Spring库中定义的一些宏被认为是内部的(私有的),但在宏定义中不存在该scope,所以,所有的宏都是可见的。
下面的部分将专注于在模板内直接调用的那些宏。如果你想看一下这些宏代码,它们位于 org.springframework.web.servlet.view.velocity
或 org.springframework.web.servlet.view.freemarker
包中,名字是spring.vm / spring.ftl。
在你的HTML forms (vm / ftl 模板)中,你可以使用类似下面的代码来为每一个input field绑定field values、显示错误信息 -- 类似于JSP。嗯嗯,此时这些HTML forms (vm / ftl 模板)是作为Spring MVC controller的一种form view。 -- 明明是模板形式的HTML表单!
下面的例子是配合上面提到的 personFormV/personFormF views :
<!-- velocity macros are automatically available --> <html> ... <form action="" method="POST"> Name: #springBind("myModelObject.name") <input type="text" name="${status.expression}" value="$!status.value"/><br> #foreach($error in $status.errorMessages) <b>$error</b> <br> #end <br> ... <input type="submit" value="submit"/> </form> ... </html>
<!-- freemarker macros have to be imported into a namespace. We strongly recommend sticking to 'spring' --> <#import "/spring.ftl" as spring/> <html> ... <form action="" method="POST"> Name: <@spring.bind "myModelObject.name"/> <input type="text" name="${spring.status.expression}" value="${spring.status.value?html}"/><br> <#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list> <br> ... <input type="submit" value="submit"/> </form> ... </html>
#springBind
/ <@spring.bind>
需要一个path argument,该argument由你的命令对象的名字(默认为command,可另行指定)、句点、还有你想绑定的field的名字组成。嵌套的field也可以使用,类似于"command.address.street"。bind 宏假定默认的HTML转义行为由web.xml中ServletContext的defaultHtmlEscape 参数指定。
该宏的可选form叫做#springBindEscaped
/ <@spring.bindEscaped>
,会接收第二个参数,并显式的指定HTML转义是否用在status error messages 或 values中。如果设为true或false。额外的form处理宏简化了HTML转义的使用,所以,只要合适就用吧。下一部分会有解释。
form input generation marcos -- 生成表单输入的宏
其他便捷的宏简化了二者绑定和表单的生成(包括校验错误显示)。
只是,使用这些宏来生成表单输入字段永远不是必要,可以混合使用它们,或者直接调用spring bind marcos。
下面的表格是可用宏的表格,列出了VTL和FTL定义,以及其相应的参数列表。
Table 23.1. Table of macro definitions
macro | VTL definition | FTL definition |
---|---|---|
message (output a string from a resource bundle based on the code parameter) |
#springMessage($code) |
<@spring.message code/> |
messageText (output a string from a resource bundle based on the code parameter, falling back to the value of the default parameter) |
#springMessageText($code $text) |
<@spring.messageText code, text/> |
url (prefix a relative URL with the application’s context root) |
#springUrl($relativeUrl) |
<@spring.url relativeUrl/> |
formInput (standard input field for gathering user input) |
#springFormInput($path $attributes) |
<@spring.formInput path, attributes, fieldType/> |
formHiddenInput * (hidden input field for submitting non-user input) |
#springFormHiddenInput($path $attributes) |
<@spring.formHiddenInput path, attributes/> |
formPasswordInput * (standard input field for gathering passwords. Note that no value will ever be populated in fields of this type) |
#springFormPasswordInput($path $attributes) |
<@spring.formPasswordInput path, attributes/> |
formTextarea (large text field for gathering long, freeform text input) |
#springFormTextarea($path $attributes) |
<@spring.formTextarea path, attributes/> |
formSingleSelect (drop down box of options allowing a single required value to be selected) |
#springFormSingleSelect( $path $options $attributes) |
<@spring.formSingleSelect path, options, attributes/> |
formMultiSelect (a list box of options allowing the user to select 0 or more values) |
#springFormMultiSelect($path $options $attributes) |
<@spring.formMultiSelect path, options, attributes/> |
formRadioButtons (a set of radio buttons allowing a single selection to be made from the available choices) |
#springFormRadioButtons($path $options $separator $attributes) |
<@spring.formRadioButtons path, options separator, attributes/> |
formCheckboxes (a set of checkboxes allowing 0 or more values to be selected) |
#springFormCheckboxes($path $options $separator $attributes) |
<@spring.formCheckboxes path, options, separator, attributes/> |
formCheckbox (a single checkbox) |
#springFormCheckbox($path $attributes) |
<@spring.formCheckbox path, attributes/> |
showErrors (simplify display of validation errors for the bound field) |
#springShowErrors($separator $classOrStyle) |
<@spring.showErrors separator, classOrStyle/> |
- 在FTL中,这两个宏实际上是不需要的,因为可以使用常规的formInput marco,指定 hidden或password作为fieldType parameter的值。
上面任意宏的parameters都有一致的含义:
- path:要绑定到的字段的名字 ("command.name")
- options:input field中可以选择的所有可用值组成的Map。--太长,懒得翻译。。。
- separator:多个选项可用时(radio buttons或者checkboxes),字符序列(就是字符串,如<br>)用来将选项隔离。
- attributes:一个额外的字符串 -- 任意标签或包含在HTML标签中的文本组成的字符串。该字符串由宏来显示。例如,在textarea 字段中,你可以提供attributes:rows=”5” cols=”60”,或者传递样式信息--诸如style=”border:1px solid silver”。
- classOrStyle:对showErrors 宏来说,CSS class的名字封装了它要使用的每个error。如果没有提供(或者值是空空),那errors会被封装在<b></b>标签中。
宏的用例:
<!-- the Name field example from above using form macros in VTL --> ... Name: #springFormInput("command.name" "")<br> #springShowErrors("<br>" "")<br>
宏formInput ,会接收path parameter (command.name)和一个额外的attributes parameter -- 上例中是空。该宏,配合其他所有生成表单的宏,共同实现了path parameter隐式地spring bind。该绑定一直持续到新的绑定发生,所以,showErrors宏不需要再次传递path parameter -- 它会直接操作上一次绑定的字段。
宏showErrors,会接收一个separator parameter,并会接收第二个参数,一个class name或style attribute。注意,FreeMarker能够为这些attributes parameter指定默认值,不像Velocity,这两个宏在FTL中这样表示:
<@spring.formInput "command.name"/> <@spring.showErrors "<br>"/>
上述生成的表单的片断如下所示(会生成name字段、会显示校验错误):
Name: <input type="text" name="name" value=""> <br> <b>required</b> <br> <br>
formTextarea宏,其工作方式与formInput宏一致,接收的parameter list也相同。共同地,第二个parameter (attributes) 会用来传递样式信息或该textarea的行列属性。
4个选择字段宏,可用来生成通用UI value selection inputs。
- formSingleSelect
- formMultiSelect
- formRadioButtons
- formCheckboxes
每一个都接收一个options Map,包含了form 字段的值、以及该值相对应的label。value和label可以相同。
下面是一个FTL的radio buttons例子。form backing object指定了该字段的默认值为London,所以,不必需要校验了。当form被渲染时,cities的整个列表会作为名字为cityMap的model中的引用。
... Town: <@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>
这里会渲染一行radio buttons,cityMap中的每个value都对应一个 -- separator是""。没有提供更多attributes (该宏的最后一个参数缺失)。该map的keys都是form使用POST提交的请求参数,map的values都是用户看到的labels。上面的例子中,有一个由三个城市组成的列表和一个默认值,那HTML会是这样的:
Town: <input type="radio" name="address.town" value="London">London</input> <input type="radio" name="address.town" value="Paris" checked="checked">Paris</input> <input type="radio" name="address.town" value="New York">New York</input>
如果你的应用想使用内部代码来处理cities,如下:
protected Map referenceData(HttpServletRequest request) throws Exception { Map cityMap = new LinkedHashMap(); cityMap.put("LDN", "London"); cityMap.put("PRS", "Paris"); cityMap.put("NYC", "New York"); Map m = new HashMap(); m.put("cityMap", cityMap); return m; }
该code会produce output:
Town: <input type="radio" name="address.town" value="LDN">London</input> <input type="radio" name="address.town" value="PRS" checked="checked">Paris</input> <input type="radio" name="address.town" value="NYC">New York</input>
HTML escaping and XHTML compliance -- HTML转义和XHTML兼容
上面提到的form macros的默认使用,其结果是HTML标签兼容HTML 4.01、使用默认值来HTML转义 -- 定义在web.xml中,被Spring的绑定支持所使用 (--什么鬼,狗屁不通)。
为了兼容XHTML或者想要覆盖默认的HTML转义值,可以在你的模板中(或model中)指定两个变量。在模板中指定它们的优点是它们能够被改成不同的值 -- 在模板处理过程中,从而为form中不同的字段提供不同的行为。
想要将你的标签切换至XHTML兼容的,指定名字为xhtmlCompliant的model/context变量的值为true即可:
# for Velocity.. #set($springXhtmlCompliant = true) <-- for FreeMarker --> <#assign xhtmlCompliant = true in spring>
现在,由Spring macros生成的标签都是XHTML兼容的了。
类似的,HTML转义:
<#-- until this point, default HTML escaping is used --> <#assign htmlEscape = true in spring> <#-- next field will use HTML escaping --> <@spring.formInput "command.name"/> <#assign htmlEscape = false in spring> <#-- all future fields will be bound with HTML escaping off -->
Spring为JSP和JSTL views提供了一对开箱即用的解决方案。通过使用定义在WebApplicationContext中的一个普通的view resolver就可以使用JSP或JSTL。当然,你还需要写一些JSPs -- 会自动渲染其view。
设置你的应用来使用JSTL是error的一个常见原因,主要是由不同的servlet spec.、JSP和JSTL版本的混淆导致的。文章 How to Reference and Use JSTL in your Web Application 提供了一个很有用的指导,诸如常见陷阱、如何避免它们。注意,自Spring 3.0 起,最小支持的servlet版本是 2.4 (JSP 2.0, JSTL 1.1),这样会降低混淆的范围。
就像其他所有你集成到Spring的view技术一样,JSPs也需要一个view resolver。开发JSPs时,最常用的view resolver是InternalResourceViewResolver 和 ResourceBundleViewResolver。二者都声明在WebApplicationContext中:
<!-- the ResourceBundleViewResolver --> <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean> # And a sample properties file is uses (views.properties in WEB-INF/classes): welcome.(class)=org.springframework.web.servlet.view.JstlView welcome.url=/WEB-INF/jsp/welcome.jsp productList.(class)=org.springframework.web.servlet.view.JstlView productList.url=/WEB-INF/jsp/productlist.jsp
如你所见,ResourceBundleViewResolver需要一个properties文件,该文件定义了映射到①一个class和②一个URL的视图名字。使用ResourceBundleViewResolver,你可以混合不同类型的视图 -- 只需要一个解析器!
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
InternalResourceViewResolver 可以使用如上方式来使用JSPs。最佳实践是,将JSP文件都放入WEB-INF目录下的一个文件夹内,这样客户端就不能直接访问了。
当使用JSTL时,你必须使用一个特定的view class -- JstlView,因为JSTL需要一些准备才能工作 -- 例如准备i18N功能。
如前所述,Spring提供了数据绑定,可以将请求参数绑定到命令对象。为了促进JSP页面的开发 -- 结合这些数据绑定功能,Spring提供了一些标签让事情变得更简单。所有的Spring标签都有HTML转义功能,以开启或禁用字符的转义。
该TLD(标签库描述符)包含在spring-webmvc.jar中。
As of version 2.0, Spring provides a comprehensive set of data binding-aware tags for handling form elements when using JSP and Spring Web MVC. Each tag provides support for the set of attributes of its corresponding HTML tag counterpart, making the tags familiar and intuitive to use. The tag-generated HTML is HTML 4.01/XHTML 1.0 compliant.
不像其他form/input标签库,Spring的form标签库是集成到Spring Web MVC中的,可以让这些标签访问controller处理的命令对象和引用数据。如下所示,你会发现form标签使得JSP更易于开发、解读和维护。
现在让我们来看一看form标签,以及在示例中每个标签是如何使用的。
form标签库来自 spring-webmvc.jar中。该库描述符被叫做 spring-form.tld。
想要使用该库中的标签,需要将下面的语句添加到JSP页面的顶部:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
在这里,form是标签名前缀。
该标签会渲染一个HTML form标签,并暴露一个绑定路径到内部标签中--用于绑定。它会将命令对象存入PageContext中,这样命令对象就可以通过内部标签来访问了。 注意:该库中其他所有的标签都是form标签的嵌套标签!
让我们假定一个domain object 叫做 User。 它是一个JavaBean,其properties可以是firstName、lastName等。我们会使用它作为我们form controller的form backing object,该controller会返回 form.jsp。 下面是一个form.jsp 的样例:
<form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName"/></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form:form>
这里,firstName和lastName会从命令对象中获取到值 -- 是由page controller放入PageContext中的。
生成的HTML是标准的form:
<form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value="Harry"/></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value="Potter"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form>
前面的JSP假定了form backing object的变量名是 command。 如果你想使用其他名字(绝对是个好主意),那可以这样做:
<form:form modelAttribute="user"> <table> <tr> <td>First Name:</td> <td><form:input path="firstName"/></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form:form>
该标签会渲染一个HTML input 标签,使用绑定的值,同时默认type=”text”。 见上面的例子。
从Spring 3.1 开始,你可以使用其他类型,例如HTML5专有的类型,如email、tel、date等等。
该标签渲染了一个HTML input 标签,类型为 checkbox。
假定我们的User有一些爱好的事物,例如新闻订阅、兴趣列表等。下面是Preferences类的一个样例:
public class Preferences { private boolean receiveNewsletter; private String[] interests; private String favouriteWord; public boolean isReceiveNewsletter() { return receiveNewsletter; } public void setReceiveNewsletter(boolean receiveNewsletter) { this.receiveNewsletter = receiveNewsletter; } public String[] getInterests() { return interests; } public void setInterests(String[] interests) { this.interests = interests; } public String getFavouriteWord() { return favouriteWord; } public void setFavouriteWord(String favouriteWord) { this.favouriteWord = favouriteWord; } }
然后,form.jsp应该是这样的:
<form:form> <table> <tr> <td>Subscribe to newsletter?:</td> <%-- Approach 1: Property is of type java.lang.Boolean --%> <td><form:checkbox path="preferences.receiveNewsletter"/></td> </tr> <tr> <td>Interests:</td> <%-- Approach 2: Property is of an array or of type java.util.Collection --%> <td> Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/> Herbology: <form:checkbox path="preferences.interests" value="Herbology"/> Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/> </td> </tr> <tr> <td>Favourite Word:</td> <%-- Approach 3: Property is of type java.lang.Object --%> <td> Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/> </td> </tr> </table> </form:form>
这里,checkbox标签有3个分支,应该会满足你所有的checkbox需求。
- 分支1 - 当绑定值是Boolean类型时,如果其值是true,那input(checkbox)会被标记为checked。
- 分支2 - 当绑定值是数组、集合时,如果setValue(Object)设置的值出现在绑定的集合中,那input(checkbox)会被标记为checked。
- 分支3 - 对于任何其他绑定类型 ,如果setValue(Object)设置的值与绑定值相等,那input(checkbox)会被标记为checked。
注意:无论哪个分支,都会生成同样的HTML结构。下面是一个HTML片段:
<tr> <td>Interests:</td> <td> Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/> <input type="hidden" value="1" name="_preferences.interests"/> Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/> <input type="hidden" value="1" name="_preferences.interests"/> Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/> <input type="hidden" value="1" name="_preferences.interests"/> </td> </tr>
上面,你可能不希望看到的是每个checkbox后面的那个额外的hidden字段。当HTML页面中的一个checkbox没有被checked时,它的值不会被发送至服务器,所以我们需要一个应急方案,以让Spring form data binding正常工作。checkbox标签遵从现有的Spring惯例 -- 为每个checkbox添加一个隐藏参数,前缀是下划线("_")。这样做,就是告诉Spring “该checkbox在form中是可见的,我希望我的对象(表单数据将要绑定到的对象)能够反映出checkbox的状态 -- 无论该状态是什么 ”。
这个标签会渲染多个HTML input标签 -- 类型是checkbox。
基于前面的例子,有时候你可能不希望在JSP页面中列出所有的hobbies。你可能更希望在运行时提供一个可用选项列表,并将其传至标签。这就是checkboxes标签的目的。你可以给该标签的items属性传入一个Array、List或者Map,包含可用的选项。通常,该绑定property是一个集合,所以它可用持有用户选择的多个值。 下面是一个例子:
<form:form> <table> <tr> <td>Interests:</td> <td> <%-- Property is of an array or of type java.util.Collection --%> <form:checkboxes path="preferences.interests" items="${interestList}"/> </td> </tr> </table> </form:form>
该例子假定了 interestList是一个List,包含有需要被选择的值的字符串。如果你使用了一个Map,那entry key会被用作值,而entry value则被用作要显示的label的值。你还可以使用一个自定义的对象,然后使用itemValue设置property的值,用itemLabel设置label。--最后这个没懂。
该标签会渲染一个HTML input标签,类型为radio。
一个典型的用法是有多个标签实例绑定到同一个property -- 当然要有不同的值。如下:
<tr> <td>Sex:</td> <td> Male: <form:radiobutton path="sex" value="M"/> <br/> Female: <form:radiobutton path="sex" value="F"/> </td> </tr>
该标签会渲染多个HTML input标签,类型为radio。
类似于checkboxes。
<tr> <td>Sex:</td> <td><form:radiobuttons path="sex" items="${sexOptions}"/></td> </tr>
该标签会渲染一个HTML input标签,类型为password。
<tr> <td>Password:</td> <td> <form:password path="password"/> </td> </tr>
注意,默认,password的值是不会显示的。如果你想显示,设置showPassword 属性为true即可:
<tr> <td>Password:</td> <td> <form:password path="password" value="^76525bvHGq" showPassword="true"/> </td> </tr>
该标签会渲染一个HTML select元素。 支持数据绑定到选定的选项,同时还支持使用内嵌的option和options标签。
假定User有一个技能列表:
<tr> <td>Skills:</td> <td><form:select path="skills" items="${skills}"/></td> </tr>
然后HTML样例如下:
<tr> <td>Skills:</td> <td> <select name="skills" multiple="true"> <option value="Potions">Potions</option> <option value="Herbology" selected="selected">Herbology</option> <option value="Quidditch">Quidditch</option> </select> </td> </tr>
该标签会渲染一个HTML option。 它会基于绑定值来设置selected。
<tr> <td>House:</td> <td> <form:select path="house"> <form:option value="Gryffindor"/> <form:option value="Hufflepuff"/> <form:option value="Ravenclaw"/> <form:option value="Slytherin"/> </form:select> </td> </tr>
然后HTML样例如下:
<tr> <td>House:</td> <td> <select name="house"> <option value="Gryffindor" selected="selected">Gryffindor</option> <option value="Hufflepuff">Hufflepuff</option> <option value="Ravenclaw">Ravenclaw</option> <option value="Slytherin">Slytherin</option> </select> </td> </tr>
该标签会渲染一个HTML option标签的列表。 并基于绑定值来设置selected属性。
<tr> <td>Country:</td> <td> <form:select path="country"> <form:option value="-" label="--Please Select"/> <form:options items="${countryList}" itemValue="code" itemLabel="name"/> </form:select> </td> </tr>
然后HTML样例如下:
<tr> <td>Country:</td> <td> <select name="country"> <option value="-">--Please Select</option> <option value="AT">Austria</option> <option value="UK" selected="selected">United Kingdom</option> <option value="US">United States</option> </select> </td> </tr>
如同上面例子所显示的,option和options标签的结合使用会生成同样的HTML,但会允许你指定一个仅用于显示的值,例如上面的默认字符串:"-- Please Select"。
该标签的items属性通常是item 对象的一个集合或者数组。itemValue和itemLabel 指向这些item objects的bean properties -- 如果指定的话;或者,这些item object本身会被字符串化。或者,也可以指定一个Map,然后keys被解释成option values,values相应于option labels。如果itemValue 和/或 itemLabel也被指定了,那item value property会被用到map key上,而item label property会被用到map value上面。-- 见鬼了,这么绕!
该标签会渲染一个HTML textarea。
<tr> <td>Notes:</td> <td><form:textarea path="notes" rows="3" cols="20"/></td> <td><form:errors path="notes"/></td> </tr>
该标签会渲染一个HTML input标签,类型是hidden,使用绑定值。想要提交一个未绑定的hidden值,需要使用hidden类型的HTML input标签。
<form:hidden path="house"/>
If we choose to submit the 'house' value as a hidden one, the HTML would look like:
<input name="house" type="hidden" value="Gryffindor"/>
该标签会在一个HTML span 标签内渲染字段的errors。它可以让你访问controller中创建的errors,或者,可以让你访问那些由validators(--controller使用的)创建的errors。
假定我们想在表单提交后显示出firstName 和 lastName字段的所有错误信息。我们有一个validator用于User实例,叫做UserValidator。
public class UserValidator implements Validator { public boolean supports(Class candidate) { return User.class.isAssignableFrom(candidate); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required."); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required."); } }
form.jsp 看起来是这样的:
<form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName"/></td> <%-- Show errors for firstName field --%> <td><form:errors path="firstName"/></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName"/></td> <%-- Show errors for lastName field --%> <td><form:errors path="lastName"/></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form:form>
如果我们提交的firstName和lastName是空白值 -- 就会报错,HTML是这样的:
<form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <%-- Associated errors to firstName field displayed --%> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <%-- Associated errors to lastName field displayed --%> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form>
如果我们想显示某个页面的全部错误列表,怎么做?下面的例子表明 errors标签也支持一些基本的通配符功能。
- path=”*” - 显示所有错误
- path=”lastName” - 显示lastName字段关联的所有错误
- 如果path被省略了 - 仅显示对象错误
下面的例子会在页面的顶部展示错误列表,字段后面跟有相应字段的错误:
<form:form> <form:errors path="*" cssClass="errorBox"/> <table> <tr> <td>First Name:</td> <td><form:input path="firstName"/></td> <td><form:errors path="firstName"/></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName"/></td> <td><form:errors path="lastName"/></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form:form>
然后HTML会是这样:
<form method="POST"> <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form>
REST的一个关键准则是使用统一的接口。这意味着所有的资源 (URLs) 都能使用四个HTTP methods来操作:GET/PUT/POST/DELETE。
对每个method来说,HTTP spec. 都定义了具体的语义。例如,GET应该是一个安全操作,就是说该操作不应有副作用(不会修改什么);PUT/DELETE应该是幂等的(idempotent),就是说你可以重复重复再重复这些操作,但其结果应该相同。
虽然HTTP定义了这4种methods,但HTML仅支持两种:GET/POST!
幸运的是,有两个权宜之计:你可以使用JavaScript来执行PUT/DELETE,或者也可以执行POST--然后将实际的method作为额外的参数。后者就是Spring的HiddenHttpMethodFilter所做的!!!该filter就是一个简单的Servlet filter,所以可以与任何web框架相结合(就是说不仅仅是Spring MVC)。只需要将其添加到你的web.xml,然后POST附加一个hidden _method参数,就会被自动转成相应的HTTP method request。
为了支持HTTP method conversion,升级了Spring MVC form tag 以支持设置HTTP method。例如:
<form:form method="delete"> <p class="submit"><input type="submit" value="Delete Pet"/></p> </form:form>
这里会自动执行一个HTTP POST请求,并将真实的DELETE method隐藏到一个请求参数中,然后会被HiddenHttpMethodFilter自动捡拾到。该filter这web.xml中的定义:
<filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <servlet-name>petclinic</servlet-name> </filter-mapping>
相应的@Controller method是这样的:
@RequestMapping(method = RequestMethod.DELETE) public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { this.clinic.deletePet(petId); return "redirect:/owners/" + ownerId; }
从Spring 3开始,Spring form 标签库允许输入动态attributes,就是说你可以输入任意的HTML5专有的attributes。
这Spring 3.1中,form input tag支持输入一个text之外的attribute。这被用于允许渲染新的HTML5专有的input 类型,例如email、date、range等等。
在使用Spring的web应用中,集成任意模板库(运行于JSR-223 script engine上面)是可能的。下面描述了如何做到。script engine必须实现ScriptEngine接口和Invocable接口!
已测试过的:
- Handlebars running on Nashorn
- Mustache running on Nashorn
- React running on Nashorn
- EJS running on Nashorn
- ERB running on JRuby
- String templates running on Jython
为了能够使用script templates 集成,你需要在你的classpath中有一个可用的script engine:
- Nashorn Javascript engine is provided builtin with Java 8+. Using the latest update release available is highly recommended.
- Rhino Javascript engine is provided builtin with Java 6 and Java 7. Please notice that using Rhino is not recommended since it does not support running most template engines.
- JRuby dependency should be added in order to get Ruby support.
- Jython dependency should be added in order to get Python support.
你还应该添加相关的依赖。例如,对JavaScript来说,你应该使用WebJars来添加Maven/Gradle依赖,从而让你的js库在classpath中可用。
为了使用脚本模板,必须配置一下以指定不同的参数,如使用的脚本引擎、要加载的脚本文件还有渲染模板需要的函数。这是由ScriptTemplateConfigurer bean 和 可选的脚本文件来完成的。
例如,为了渲染Mustache模板 -- 使用Java 8+ 提供的Nashorn JavaScript引擎,你应该这样:
@Configuration @EnableWebMvc public class MustacheConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.scriptTemplate(); } @Bean public ScriptTemplateConfigurer configurer() { ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer(); configurer.setEngineName("nashorn"); configurer.setScripts("mustache.js"); configurer.setRenderObject("Mustache"); configurer.setRenderFunction("render"); return configurer; } }
使用MVC namespace的XML也类似:
<mvc:annotation-driven/> <mvc:view-resolvers> <mvc:script-template/> </mvc:view-resolvers> <mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render"> <mvc:script location="mustache.js"/> </mvc:script-template-configurer>
Controller是这样的:
@Controller public class SampleController { @RequestMapping public ModelAndView test() { ModelAndView mav = new ModelAndView(); mav.addObject("title", "Sample title").addObject("body", "Sample body"); mav.setViewName("template.html"); return mav; } }
Mustache模板是这样:
<html> <head> <title>{{title}}</title> </head> <body> <p>{{body}}</p> </body> </html>
渲染函数是通过下列参数来调用的:
- String template:模板内容
- Map model:view model
- String url:模板 url (自 4.2.2起 )
Mustache.render() 兼容这些这种签名,可以直接调用。
如果你的模板技术需要一些定制,你可以提供一个实现了定制渲染函数的脚本。例如,Handlerbars需要在使用模板之前先编译,还需要一个polyfill 以便模拟某些浏览器设施在服务器侧脚本引擎不可用。-- fuck en!!!
@Configuration @EnableWebMvc public class MustacheConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.scriptTemplate(); } @Bean public ScriptTemplateConfigurer configurer() { ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer(); configurer.setEngineName("nashorn"); configurer.setScripts("polyfill.js", "handlebars.js", "render.js"); configurer.setRenderFunction("render"); configurer.setSharedEngine(false); return configurer; } }
当使用非线程安全的脚本引擎时,模板库不是设计用于并发的-- 如运行在Nashorn上面的Handlebars或React,需要将sharedEngine property设为false。这种情况下,必须使用Java 8u60+,原因见这里:this bug。
polyfill.js 只定义了Handlebars需要的window对象:
var window = {};
基本的 render.js 实现,会在使用模板之前先编译。一个生产就绪实现应该也能存储和复用缓存的模板/预编译的模板。这可以在脚本侧完成,同时进行任何需要的定制(如管理目标引擎配置)。
function render(template, model) { var compiledTemplate = Handlebars.compile(template); return compiledTemplate(model); }
更多配置样例,见Spring脚本模板单元测试 (java, resources)。
7、XML Marshalling View
略
8、Tiles
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/view.html#view-tiles
略
8.1、依赖
8.2、如何集成Tiles
UrlBasedViewResolver
ResourceBundleViewResolver (可以混合多种view技术)
SimpleSpringPreparerFactory 和 SpringBeanPreparerFactory
9、XSLT
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/view.html#view-xslt
略
10、Document views (PDF/Excel)
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/view.html#view-document
略
10.1、简介
10.2、Configuration and setup
Document view definitions
Controller code
Subclassing for Excel views
Subclassing for PDF views
11、JasperReports
JasperReports 这是一个强大的开源的报告引擎,支持使用简单易懂的XML文件格式来设计报告。JasperReports 支持四种不同的格式:CSV、Excel、HTML和PDF。
11.1、依赖
11.2、配置
配置ViewResolver
配置Views
关于Report文件
使用JasperReportsMultiFormatView
11.3、填充ModelAndView
11.4、使用Sub-Reports
配置Sub-Reports文件
配置Sub-Report数据源
11.5、配置Exporter Parameters
12、Feed Views
13、JSON Mapping View
14、XML Mapping View
官方文档链接:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/view.html