Spring的一个擅长领域在于,把视图层技术和MVC框架的其余部分分离开来。 例如,选择使用Velocity或者XSLT来代替已有的JSP方式只需要在配置上做改动就可以了。 本章涵盖了和Spring协同工作的主流view层技术并简要介绍了如何增加新的方式。 这里假设你已经熟悉Section 16.5, “视图解析”章节的知识,那里讲述了view层与MVC框架协作的基础。
Spring为JSP和JSTL这些view层技术提供了几个开箱即用的解决方案。 使用JSP和JSTL的,采用WebApplicationContext中定义的普通视图解析器即可; 此外,你当然还得自己写一些实际做渲染的JSP页面。
Note | |
---|---|
设置应用程序使用JSTL是一种常见的错误,主要在不同的Servlet规范,JSP和JSTL版本上会引起混乱, 这意味着如何正确的声明这些标签库。这篇文章http://www.mularien.com/blog/2008/04/24/how-to-reference-and-use-jstl-in-your-web-application/[How to Reference and Use JSTL in your Web Application]会提供给我们一些有用的指导来避免一些常见的陷阱。 需要注意的是,Spring 3.0支持的Servlet最低版本是2.4(JSP 2.0,JSTL 1.1),这样会在一定范围内减少一些混乱。 |
与在Spring中采用的任何其他视图技术一样,使用JSP需要一个视图解析器来解析你的视图,
常用的是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
需要一个属性文件来定义view名称对应映射到到一个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>
如上例所示, 用JSP时可以配置一个InternalResourceBundleViewResolver
。作为一个最佳实践,我们强烈推荐你用 WEB-INF下的一个目录来存放JSP文件,从而避免被客户端直接访问。
前面的章节中提到过,Spring提供了从请求参数到命令对象的数据绑定。为了简化与数据绑定特性配合使用的JSP页面的开发,Spring提供了一些标签让事情变得更简单。
这些标签都提供了 HTML escaping的特性,能够打开或关闭字符转码的功能。
标签库描述符(TLD)包含在spring-webmvc.jar
中。关于每个tag的更多详细信息请参阅附录Chapter 35, spring.tld。
在2.0的版本中,当使用JSP和Spring Web MVS的时候,Srping提供了一组全面的用于处理表单元素进行数据绑定的标签。 每个标签都提供了支持设置对应HTML标签的属性,使得标签熟悉且直观的使用。标签生成的HTML兼容HTML 4.01/XHTML 1.0文档标准。
不像其他的form/input标签库,Spring的表单表单标签库集成在Spring Web MVC中,能够让标签访问命令对象以及引用数据在你的处理控制器中。 正如接下来看到的例子,这些form标签使得使用JSPs开发更容易、更易于阅读和维护。
让我们通过一个例子了解每个标签是如何使用的。我们已经包含了生成好的html片段,某些标签需要更进一步的探讨。
这些表单标签库捆绑在spring-webmvc.jar
中。这些标签库的描述符被称为spring-form.tld
。
使用这个标签库,需要把下面这段代码加到你的JSP页面的最顶端:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
这里的form
就是你要使用的这些标签名称的前缀。
这些标签渲染了一个HTML form标签并且公开了一个绑定路径内标签用于绑定。
它把命令对象放置到PageContext
中,这样命令对象对能被这些内部标签访问。
库中所有其他标签都是这个form
标签的嵌套标签。
假设我们有一个领域对象User
。是一个包含属性的JavaBean,例如firstName
和lastName
。
我们把它作为表单控制器的表单回传对象返回到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
的值从放置在PageContext
中的命令对象中检索出来。
继续阅读更复杂的例子,了解如何使用form
标签的内部标签。
生成的HTML就是一个标准的表单:
<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中假定回传对象的命名变量就是'command'
。如果你以另外一个名称把表单回传对象放到model里
(绝对是一个最佳的例子),那么你可以将表单绑定到命名变量,就像下面这样:
<form:form commandName="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>
这个标签默认通过绑定的值和 type=text 渲染 HTML input 标签。关于例子可以参考 the section called “表单标签”。从 Spring 3.1 开始,你可以使用其它类型,例如HTML5中定义的 email, tel, date 等等。
这个标签通过 checkbox 类型来渲染 HTML input 标签。
假设我们的 用户(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>是否订阅时事资讯?:</td> <%-- 方式 1: 属性是 java.lang.Boolean 类型的 --%> <td><form:checkbox path="preferences.receiveNewsletter"/></td> </tr> <tr> <td>兴趣:</td> <%-- 方式 2: 属性是数组或者 java.util.Collection 类型的 --%> <td> 魁地奇球赛: <form:checkbox path="preferences.interests" value="Quidditch"/> 草药学: <form:checkbox path="preferences.interests" value="Herbology"/> 黑魔法防御术: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/> </td> </tr> <tr> <td>最喜欢的单词:</td> <%-- 方式 3: 属性是 java.lang.Object 类型的 --%> <td> 魔法: <form:checkbox path="preferences.favouriteWord" value="Magic"/> </td> </tr> </table> </form:form>
checkbox
标签有三种方式来满足你对 checkbox 的几乎所有的需要。
java.lang.Boolean
,如果值是 true
,那么 input(checkbox)
会被标记为 checked。value
属性的值对应 setValue(Object)
的解析值。
array
或者 java.util.Collection
, 如果绑定的 Collection
中存在通过 setValue(Object)
配置的值,那么 input(checkbox)
会被标记为 checked。
setValue(Object)
配置的值相等,那么 input(checkbox)
会被标记为 checked。
需要注意的是,不管用哪种方式,都会生成同样的 HTML 结构。下面是一个关于 checkbox 的 HTML 片段:
<tr> <td>兴趣:</td> <td> 魁地奇球赛: <input name="preferences.interests" type="checkbox" value="Quidditch"/> <input type="hidden" value="1" name="_preferences.interests"/> 草药学: <input name="preferences.interests" type="checkbox" value="Herbology"/> <input type="hidden" value="1" name="_preferences.interests"/> 黑魔法防御术: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/> <input type="hidden" value="1" name="_preferences.interests"/> </td> </tr>
你可能不希望看到附加在每个 checkbox 后面的隐藏域。但是当 HTML 页面中的 checkbox 不(not)被选中,
在提交表单时,它就不会做为 HTTP 请求参数的一部分被发送到服务端。所以,为了保证 Spring 表单数据绑定能正常工作,
我们必须需要一个工作区来解决这个怪异的问题(quirk)。checkbox
标签遵循现有的 Spring 惯例,
每个 checkbox 相对应的隐藏参数都加上一个下划线("_")前缀。通过这样做,可以有效的告诉 Spring,
“这个 checkbox 在表单中是可见的,并且无论它的值是什么我都希望被绑定表单数据中(the checkbox was visible in the form and I want my object to
which the form data will be bound to reflect the state of the checkbox no matter what)”。
This tag renders multiple HTML input tags with type checkbox.
Building on the example from the previous checkbox
tag section. Sometimes you prefer
not to have to list all the possible hobbies in your JSP page. You would rather provide
a list at runtime of the available options and pass that in to the tag. That is the
purpose of the checkboxes
tag. You pass in an Array
, a List
or a Map
containing
the available options in the "items" property. Typically the bound property is a
collection so it can hold multiple values selected by the user. Below is an example of
the JSP using this tag:
<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>
This example assumes that the "interestList" is a List
available as a model attribute
containing strings of the values to be selected from. In the case where you use a Map,
the map entry key will be used as the value and the map entry’s value will be used as
the label to be displayed. You can also use a custom object where you can provide the
property names for the value using "itemValue" and the label using "itemLabel".
This tag renders an HTML input tag with type radio.
A typical usage pattern will involve multiple tag instances bound to the same property but with different values.
<tr> <td>Sex:</td> <td> Male: <form:radiobutton path="sex" value="M"/> <br/> Female: <form:radiobutton path="sex" value="F"/> </td> </tr>
This tag renders multiple HTML input tags with type radio.
Just like the checkboxes
tag above, you might want to pass in the available options as
a runtime variable. For this usage you would use the radiobuttons
tag. You pass in an
Array
, a List
or a Map
containing the available options in the "items" property.
In the case where you use a Map, the map entry key will be used as the value and the map
entry’s value will be used as the label to be displayed. You can also use a custom
object where you can provide the property names for the value using "itemValue" and the
label using "itemLabel".
<tr> <td>Sex:</td> <td><form:radiobuttons path="sex" items="${sexOptions}"/></td> </tr>
This tag renders an HTML input tag with type password using the bound value.
<tr> <td>Password:</td> <td> <form:password path="password" /> </td> </tr>
Please note that by default, the password value is not shown. If you do want the
password value to be shown, then set the value of the 'showPassword'
attribute to
true, like so.
<tr> <td>Password:</td> <td> <form:password path="password" value="^76525bvHGq" showPassword="true" /> </td> </tr>
This tag renders an HTML select element. It supports data binding to the selected
option as well as the use of nested option
and options
tags.
Let’s assume a User
has a list of skills.
<tr> <td>Skills:</td> <td><form:select path="skills" items="${skills}"/></td> </tr>
If the User's
skill were in Herbology, the HTML source of the Skills row would look
like:
<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>
This tag renders an HTML option. It sets selected as appropriate based on the bound value.
<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>
If the User's
house was in Gryffindor, the HTML source of the House row would look
like:
<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>
This tag renders a list of HTML option tags. It sets the selected attribute as appropriate based on the bound value.
<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>
If the User
lived in the UK, the HTML source of the Country row would look like:
<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>
As the example shows, the combined usage of an option
tag with the options
tag
generates the same standard HTML, but allows you to explicitly specify a value in the
JSP that is for display only (where it belongs) such as the default string in the
example: "-- Please Select".
The items
attribute is typically populated with a collection or array of item objects.
itemValue
and itemLabel
simply refer to bean properties of those item objects, if
specified; otherwise, the item objects themselves will be stringified. Alternatively,
you may specify a Map
of items, in which case the map keys are interpreted as option
values and the map values correspond to option labels. If itemValue
and/or itemLabel
happen to be specified as well, the item value property will apply to the map key and
the item label property will apply to the map value.
This tag renders an HTML textarea.
<tr> <td>Notes:</td> <td><form:textarea path="notes" rows="3" cols="20" /></td> <td><form:errors path="notes" /></td> </tr>
This tag renders an HTML input tag with type hidden using the bound value. To submit
an unbound hidden value, use the HTML input
tag with type hidden.
<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"/>
This tag renders field errors in an HTML span tag. It provides access to the errors created in your controller or those that were created by any validators associated with your controller.
Let’s assume we want to display all error messages for the firstName
and lastName
fields once we submit the form. We have a validator for instances of the User
class
called 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."); } }
The form.jsp
would look like:
<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>
If we submit a form with empty values in the firstName
and lastName
fields, this is
what the HTML would look like:
<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>
What if we want to display the entire list of errors for a given page? The example below
shows that the errors
tag also supports some basic wildcarding functionality.
path="*"
- displays all errors
path="lastName"
- displays all errors associated with the lastName
field
path
is omitted - object errors only are displayed
The example below will display a list of errors at the top of the page, followed by field-specific errors next to the fields:
<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>
The HTML would look like:
<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> </form>
A key principle of REST is the use of the Uniform Interface. This means that all
resources (URLs) can be manipulated using the same four HTTP methods: GET, PUT, POST,
and DELETE. For each method, the HTTP specification defines the exact semantics. For
instance, a GET should always be a safe operation, meaning that is has no side effects,
and a PUT or DELETE should be idempotent, meaning that you can repeat these operations
over and over again, but the end result should be the same. While HTTP defines these
four methods, HTML only supports two: GET and POST. Fortunately, there are two possible
workarounds: you can either use JavaScript to do your PUT or DELETE, or simply do a POST
with the real method as an additional parameter (modeled as a hidden input field in an
HTML form). This latter trick is what Spring’s HiddenHttpMethodFilter
does. This
filter is a plain Servlet Filter and therefore it can be used in combination with any
web framework (not just Spring MVC). Simply add this filter to your web.xml, and a POST
with a hidden _method parameter will be converted into the corresponding HTTP method
request.
To support HTTP method conversion the Spring MVC form tag was updated to support setting the HTTP method. For example, the following snippet taken from the updated Petclinic sample
<form:form method="delete"> <p class="submit"><input type="submit" value="Delete Pet"/></p> </form:form>
This will actually perform an HTTP POST, with the real DELETE method hidden behind a
request parameter, to be picked up by the HiddenHttpMethodFilter
, as defined in
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>
The corresponding @Controller
method is shown below:
@RequestMapping(method = RequestMethod.DELETE) public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { this.clinic.deletePet(petId); return "redirect:/owners/" + ownerId; }
Starting with Spring 3, the Spring form tag library allows entering dynamic attributes, which means you can enter any HTML5 specific attributes.
In Spring 3.1, the form input tag supports entering a type attribute other than text. This is intended to allow rendering new HTML5 specific input types such as email, date, range, and others. Note that entering type=text is not required since text is the default type.
It is possible to integrate Tiles - just as any other view technology - in web applications using Spring. The following describes in a broad way how to do this.
Note | |
---|---|
This section focuses on Spring’s support for Tiles v3 in the
|
To be able to use Tiles, you have to add a dependency on Tiles version 3.0.1 or higher and its transitive dependencies to your project.
To be able to use Tiles, you have to configure it using files containing definitions
(for basic information on definitions and other Tiles concepts, please have a look at
http://tiles.apache.org). In Spring this is done using the TilesConfigurer
. Have a
look at the following piece of example ApplicationContext configuration:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/general.xml</value> <value>/WEB-INF/defs/widgets.xml</value> <value>/WEB-INF/defs/administrator.xml</value> <value>/WEB-INF/defs/customer.xml</value> <value>/WEB-INF/defs/templates.xml</value> </list> </property> </bean>
As you can see, there are five files containing definitions, which are all located in
the 'WEB-INF/defs'
directory. At initialization of the WebApplicationContext
, the
files will be loaded and the definitions factory will be initialized. After that has
been done, the Tiles includes in the definition files can be used as views within your
Spring web application. To be able to use the views you have to have a ViewResolver
just as with any other view technology used with Spring. Below you can find two
possibilities, the UrlBasedViewResolver
and the ResourceBundleViewResolver
.
You can specify locale specific Tiles definitions by adding an underscore and then the locale. For example:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/tiles.xml</value> <value>/WEB-INF/defs/tiles_fr_FR.xml</value> </list> </property> </bean>
With this configuration, tiles_fr_FR.xml
will be used for requests with the fr_FR
locale,
and tiles.xml
will be used by default.
Note | |
---|---|
Since underscores are used to indicate locales, it is recommended to avoid using them otherwise in the file names for Tiles definitions. |
The UrlBasedViewResolver
instantiates the given viewClass
for each view it has to
resolve.
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/> </bean>
The ResourceBundleViewResolver
has to be provided with a property file containing
viewnames and viewclasses the resolver can use:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean>
... welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView welcomeView.url=welcome (this is the name of a Tiles definition) vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView vetsView.url=vetsView (again, this is the name of a Tiles definition) findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp ...
As you can see, when using the ResourceBundleViewResolver
, you can easily mix
different view technologies.
Note that the TilesView
class supports JSTL (the JSP Standard Tag Library) out of the
box.
As an advanced feature, Spring also supports two special Tiles PreparerFactory
implementations. Check out the Tiles documentation for details on how to use
ViewPreparer
references in your Tiles definition files.
Specify SimpleSpringPreparerFactory
to autowire ViewPreparer instances based on
specified preparer classes, applying Spring’s container callbacks as well as applying
configured Spring BeanPostProcessors. If Spring’s context-wide annotation-config has
been activated, annotations in ViewPreparer classes will be automatically detected and
applied. Note that this expects preparer classes in the Tiles definition files, just
like the default PreparerFactory
does.
Specify SpringBeanPreparerFactory
to operate on specified preparer names instead
of classes, obtaining the corresponding Spring bean from the DispatcherServlet’s
application context. The full bean creation process will be in the control of the Spring
application context in this case, allowing for the use of explicit dependency injection
configuration, scoped beans etc. Note that you need to define one Spring bean definition
per preparer name (as used in your Tiles definitions).
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/general.xml</value> <value>/WEB-INF/defs/widgets.xml</value> <value>/WEB-INF/defs/administrator.xml</value> <value>/WEB-INF/defs/customer.xml</value> <value>/WEB-INF/defs/templates.xml</value> </list> </property> <!-- resolving preparer names as Spring bean definition names --> <property name="preparerFactoryClass" value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/> </bean>
Velocity 和 FreeMarker 是两种可以用于 Spring MVC应用中的视图层技术的模板语言. 他们很相似并且使用起来也有类似的需求, 所以在这里一并讨论. 关于两个语言在语义与句法上的区别, 请查看 FreeMarker.
你的web应用需要分别包含velocity-1.x.x.jar
或者freemarker-2.x.jar
来使用Velocity或者FreeMarker,
并且对于Velocity你还需要commons-collections.jar
. 通常他们都会被放在文件夹WEB-INF/lib
中, 这样
他们就会被Java EE服务器加载并放进应用的classpath中. 很显然是假定你也已经将spring-webmvc.jar
放到了
你应用的'WEB-INF/lib'
文件夹的! 如果你打算在Velocity的视图中使用Spring的dateToolAttribute或者
numberToolAttribute, 你还需要包含进velocity-tools-generic-1.x.jar
.
一个合适的配置是通过添加相关配置bean初始化到你的'*-servlet.xml'
, 就像下面这样:
<!-- 这个bean在一个模板根目录基础上为我们设置一个Velocity环境. 可选的, 一个properties文件可以指定来 对Velocity环境进行更多的控制, 但是默认的用来做基于文件的模板加载已经够了. --> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/velocity/"/> </bean> <!-- 视图处理也可以通过ResourceBundles或者XML文件. 如果你需要根据Locale来使用不同的视图处理, 你需要使用资源绑定处理器. --> <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 配置 --> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> </bean> <!-- 视图处理也可以通过ResourceBundles或者XML文件. 如果你需要根据Locale来使用不同的视图处理, 你需要使用资源绑定处理器. --> <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>
Note | |
---|---|
对于非web应用, 添加 |
你所有的模板需要放置在前面的*Configurer
bean所指明的目录中. 本文档没有涵盖创建那两种语言的模板的细
节--请到他们的相关网站查看相关信息. 如果你像推荐地那样使用视图处理器, 那么视图的逻辑名称和模板文件名称
的对应关系就像JSP文件的InternalResourceViewResolver
相似. 所以如果你的控制器返回一个ModelAndView
的对象, 包含一个名为"welcome"的视图名称, 那么处理器就会适当地去寻找/WEB-INF/freemarker/welcome.ftl
或者/WEB-INF/velocity/welcome.vm
.
上面指明的基础配置对于大多数的应用需求是满足的, 但是额外的配置选项对于不寻常或高级的需求是可用的.
这个文件完全是可选的, 但是如果指定了, 所包含的值是直接传递给Velocity的运行时来配置velocity的. 只有在
高级配置中才会需要, 如果指定了这个文件, 请在上面配置的VelocityConfigurer
bean中指定位置.
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="configLocation" value="/WEB-INF/velocity.properties"/> </bean>
备选地, 你可以直接将velocity属性配置在Velocity配置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>
请参看
API文档了解
Spring对于Velocity的配置, 或者Velocity文档中的例子或者它自己关于'velocity.properties'
文件的描述.
FreeMarker的Settings和SharedVariables都可以直接通过在Spring里面配置FreeMarkerConfigurer
bean
的合适的properties来传递给Spring管理的FreeMarker的Configuration
对象. freemarkerSettings
属性
需要一个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的文档了解Configuration
对象详细的设置和变量.
Spring对于JSP的使用提供了一个标签库包含(通常还有其他)一个<spring:bind/>
标签. 这个标签主要是用来
开启表单中显示后台对象的值以及在web中或者业务层的Validator
失败的时候显示结果. 从版本1.1开始, Spring
已经开始在Velocity和FreeMarker开启同样的方法支持, 通过添加方便的宏来生成表单的输入域.
A standard set of macros are maintained within the spring-webmvc.jar
file for both
languages, so they are always available to a suitably configured application.
Some of the macros defined in the Spring libraries are considered internal (private) but
no such scoping exists in the macro definitions making all macros visible to calling
code and user templates. The following sections concentrate only on the macros you need
to be directly calling from within your templates. If you wish to view the macro code
directly, the files are called spring.vm / spring.ftl and are in the packages
org.springframework.web.servlet.view.velocity
or
org.springframework.web.servlet.view.freemarker
respectively.
In your html forms (vm / ftl templates) that act as the formView for a Spring form
controller, you can use code similar to the following to bind to field values and
display error messages for each input field in similar fashion to the JSP equivalent.
Note that the name of the command object is "command" by default, but can be overridden
in your MVC configuration by setting the commandName bean property on your form
controller. Example code is shown below for the personFormV
and personFormF
views
configured earlier;
<!-- velocity macros are automatically available --> <html> ... <form action="" method="POST"> Name: #springBind( "command.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 "command.name" /> <input type="text" name="${spring.status.expression}" value="${spring.status.value?default("")}" /><br> <#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list> <br> ... <input type="submit" value="submit"/> </form> ... </html>
#springBind
/ <@spring.bind>
requires a path argument which consists of the name
of your command object (it will be command unless you changed it in your
FormController properties) followed by a period and the name of the field on the command
object you wish to bind to. Nested fields can be used too such as
"command.address.street". The bind
macro assumes the default HTML escaping behavior
specified by the ServletContext parameter defaultHtmlEscape
in web.xml
The optional form of the macro called #springBindEscaped
/ <@spring.bindEscaped>
takes a second argument and explicitly specifies whether HTML escaping should be used in
the status error messages or values. Set to true or false as required. Additional form
handling macros simplify the use of HTML escaping and these macros should be used
wherever possible. They are explained in the next section.
Additional convenience macros for both languages simplify both binding and form generation (including validation error display). It is never necessary to use these macros to generate form input fields, and they can be mixed and matched with simple HTML or calls direct to the spring bind macros highlighted previously.
The following table of available macros show the VTL and FTL definitions and the parameter list that each takes.
Table 17.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/> |
formInput
macro, specifying ' hidden
' or ' password
' as the value for the
fieldType
parameter.
The parameters to any of the above macros have consistent meanings:
SortedMap
such as a TreeMap
with a suitable Comparator may
be used and for arbitrary Maps that should return values in insertion order, use a
LinkedHashMap
or a LinkedMap
from commons-collections.
Examples of the macros are outlined below some in FTL and some in VTL. Where usage differences exist between the two languages, they are explained in the notes.
<!-- the Name field example from above using form macros in VTL --> ... Name: #springFormInput("command.name" "")<br> #springShowErrors("<br>" "")<br>
The formInput macro takes the path parameter (command.name) and an additional attributes parameter which is empty in the example above. The macro, along with all other form generation macros, performs an implicit spring bind on the path parameter. The binding remains valid until a new bind occurs so the showErrors macro doesn’t need to pass the path parameter again - it simply operates on whichever field a bind was last created for.
The showErrors macro takes a separator parameter (the characters that will be used to separate multiple errors on a given field) and also accepts a second parameter, this time a class name or style attribute. Note that FreeMarker is able to specify default values for the attributes parameter, unlike Velocity, and the two macro calls above could be expressed as follows in FTL:
<@spring.formInput "command.name"/> <@spring.showErrors "<br>"/>
Output is shown below of the form fragment generating the name field, and displaying a validation error after the form was submitted with no value in the field. Validation occurs through Spring’s Validation framework.
The generated HTML looks like this:
Name: <input type="text" name="name" value=""> <br> <b>required</b> <br> <br>
The formTextarea macro works the same way as the formInput macro and accepts the same parameter list. Commonly, the second parameter (attributes) will be used to pass style information or rows and cols attributes for the textarea.
Four selection field macros can be used to generate common UI value selection inputs in your HTML forms.
Each of the four macros accepts a Map of options containing the value for the form field, and the label corresponding to that value. The value and the label can be the same.
An example of radio buttons in FTL is below. The form backing object specifies a default value of London for this field and so no validation is necessary. When the form is rendered, the entire list of cities to choose from is supplied as reference data in the model under the name cityMap.
... Town: <@spring.formRadioButtons "command.address.town", cityMap, "" /><br><br>
This renders a line of radio buttons, one for each value in cityMap
using the
separator "". No additional attributes are supplied (the last parameter to the macro is
missing). The cityMap uses the same String for each key-value pair in the map. The map’s
keys are what the form actually submits as POSTed request parameters, map values are the
labels that the user sees. In the example above, given a list of three well known cities
and a default value in the form backing object, the HTML would be
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>
If your application expects to handle cities by internal codes for example, the map of codes would be created with suitable keys like the example below.
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; }
The code would now produce output where the radio values are the relevant codes but the user still sees the more user friendly city names.
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>
Default usage of the form macros above will result in HTML tags that are HTML 4.01 compliant and that use the default value for HTML escaping defined in your web.xml as used by Spring’s bind support. In order to make the tags XHTML compliant or to override the default HTML escaping value, you can specify two variables in your template (or in your model where they will be visible to your templates). The advantage of specifying them in the templates is that they can be changed to different values later in the template processing to provide different behavior for different fields in your form.
To switch to XHTML compliance for your tags, specify a value of true for a model/context variable named xhtmlCompliant:
# for Velocity.. #set($springXhtmlCompliant = true) <-- for FreeMarker --> <#assign xhtmlCompliant = true in spring>
Any tags generated by the Spring macros will now be XHTML compliant after processing this directive.
In similar fashion, HTML escaping can be specified per field:
<#-- 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 -->
XSLT is a transformation language for XML and is popular as a view technology within web applications. XSLT can be a good choice as a view technology if your application naturally deals with XML, or if your model can easily be converted to XML. The following section shows how to produce an XML document as model data and have it transformed with XSLT in a Spring Web MVC application.
This example is a trivial Spring application that creates a list of words in the
Controller
and adds them to the model map. The map is returned along with the view
name of our XSLT view. See Section 16.3, “Implementing Controllers” for details of Spring Web MVC’s
Controller
interface. The XSLT view will turn the list of words into a simple XML
document ready for transformation.
Configuration is standard for a simple Spring application. The dispatcher servlet config
file contains a reference to a ViewResolver
, URL mappings and a single controller
bean…
<bean id="homeController"class="xslt.HomeController"/>
The controller logic is encapsulated in a subclass of AbstractController
, with the
handler method being defined like so…
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { Map map = new HashMap(); List wordList = new ArrayList(); wordList.add("hello"); wordList.add("world"); map.put("wordList", wordList); return new ModelAndView("home", map); }
So far we’ve done nothing that’s XSLT specific. The model data has been created in the
same way as you would for any other Spring MVC application. Depending on the
configuration of the application now, that list of words could be rendered by JSP/JSTL
by having them added as request attributes, or they could be handled by Velocity by
adding the object to the VelocityContext
. In order to have XSLT render them, they of
course have to be converted into an XML document somehow. There are software packages
available that will automatically domify an object graph, but within Spring, you have
complete flexibility to create the DOM from your model in any way you choose. This
prevents the transformation of XML playing too great a part in the structure of your
model data which is a danger when using tools to manage the domification process.
In order to create a DOM document from our list of words or any other model data, we
must subclass the (provided)
org.springframework.web.servlet.view.xslt.AbstractXsltView
class. In doing so, we must
also typically implement the abstract method createXsltSource(..)
method. The first
parameter passed to this method is our model map. Here’s the complete listing of the
HomePage
class in our trivial word application:
package xslt; // imports omitted for brevity public class HomePage extends AbstractXsltView { protected Source createXsltSource(Map model, String rootName, HttpServletRequest request, HttpServletResponse response) throws Exception { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); Element root = document.createElement(rootName); List words = (List) model.get("wordList"); for (Iterator it = words.iterator(); it.hasNext();) { String nextWord = (String) it.next(); Element wordNode = document.createElement("word"); Text textNode = document.createTextNode(nextWord); wordNode.appendChild(textNode); root.appendChild(wordNode); } return new DOMSource(root); } }
A series of parameter name/value pairs can optionally be defined by your subclass which
will be added to the transformation object. The parameter names must match those defined
in your XSLT template declared with <xsl:param
name="myParam">defaultValue</xsl:param>
. To specify the parameters, override the
getParameters()
method of the AbstractXsltView
class and return a Map
of the
name/value pairs. If your parameters need to derive information from the current
request, you can override the getParameters(HttpServletRequest request)
method instead.
The views.properties file (or equivalent xml definition if you’re using an XML based view resolver as we did in the Velocity examples above) looks like this for the one-view application that is My First Words:
home.(class)=xslt.HomePage home.stylesheetLocation=/WEB-INF/xsl/home.xslt home.root=words
Here, you can see how the view is tied in with the HomePage
class just written which
handles the model domification in the first property '.(class)'
. The
'stylesheetLocation'
property points to the XSLT file which will handle the XML
transformation into HTML for us and the final property '.root'
is the name that will
be used as the root of the XML document. This gets passed to the HomePage
class above
in the second parameter to the createXsltSource(..)
method(s).
Finally, we have the XSLT code used for transforming the above document. As shown in the
above 'views.properties'
file, the stylesheet is called 'home.xslt'
and it lives in
the war file in the 'WEB-INF/xsl'
directory.
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" omit-xml-declaration="yes"/> <xsl:template match="/"> <html> <head><title>Hello!</title></head> <body> <h1>My First Words</h1> <xsl:apply-templates/> </body> </html> </xsl:template> <xsl:template match="word"> <xsl:value-of select="."/><br/> </xsl:template> </xsl:stylesheet>
A summary of the files discussed and their location in the WAR file is shown in the simplified WAR structure below.
ProjectRoot | +- WebContent | +- WEB-INF | +- classes | | | +- xslt | | | | | +- HomePageController.class | | +- HomePage.class | | | +- views.properties | +- lib | | | +- spring-*.jar | +- xsl | | | +- home.xslt | +- frontcontroller-servlet.xml
You will also need to ensure that an XML parser and an XSLT engine are available on the classpath. JDK 1.4 provides them by default, and most Java EE containers will also make them available by default, but it’s a possible source of errors to be aware of.
Returning an HTML page isn’t always the best way for the user to view the model output, and Spring makes it simple to generate a PDF document or an Excel spreadsheet dynamically from the model data. The document is the view and will be streamed from the server with the correct content type to (hopefully) enable the client PC to run their spreadsheet or PDF viewer application in response.
In order to use Excel views, you need to add the poi library to your classpath, and for PDF generation, the iText library.
Document based views are handled in an almost identical fashion to XSLT views, and the following sections build upon the previous one by demonstrating how the same controller used in the XSLT example is invoked to render the same model as both a PDF document and an Excel spreadsheet (which can also be viewed or manipulated in Open Office).
First, let’s amend the views.properties file (or xml equivalent) and add a simple view definition for both document types. The entire file now looks like this with the XSLT view shown from earlier:
home.(class)=xslt.HomePage home.stylesheetLocation=/WEB-INF/xsl/home.xslt home.root=words xl.(class)=excel.HomePage pdf.(class)=pdf.HomePage
If you want to start with a template spreadsheet or a fillable PDF form to add your model data to, specify the location as the url property in the view definition
The controller code we’ll use remains exactly the same from the XSLT example earlier other than to change the name of the view to use. Of course, you could be clever and have this selected based on a URL parameter or some other logic - proof that Spring really is very good at decoupling the views from the controllers!
Exactly as we did for the XSLT example, we’ll subclass suitable abstract classes in
order to implement custom behavior in generating our output documents. For Excel, this
involves writing a subclass of
org.springframework.web.servlet.view.document.AbstractExcelView
(for Excel files
generated by POI) or org.springframework.web.servlet.view.document.AbstractJExcelView
(for JExcelApi-generated Excel files) and implementing the buildExcelDocument()
method.
Here’s the complete listing for our POI Excel view which displays the word list from the model map in consecutive rows of the first column of a new spreadsheet:
package excel; // imports omitted for brevity public class HomePage extends AbstractExcelView { protected void buildExcelDocument(Map model, HSSFWorkbook wb, HttpServletRequest req, HttpServletResponse resp) throws Exception { HSSFSheet sheet; HSSFRow sheetRow; HSSFCell cell; // Go to the first sheet // getSheetAt: only if wb is created from an existing document // sheet = wb.getSheetAt(0); sheet = wb.createSheet("Spring"); sheet.setDefaultColumnWidth((short) 12); // write a text at A1 cell = getCell(sheet, 0, 0); setText(cell, "Spring-Excel test"); List words = (List) model.get("wordList"); for (int i=0; i < words.size(); i++) { cell = getCell(sheet, 2+i, 0); setText(cell, (String) words.get(i)); } } }
And the following is a view generating the same Excel file, now using JExcelApi:
package excel; // imports omitted for brevity public class HomePage extends AbstractJExcelView { protected void buildExcelDocument(Map model, WritableWorkbook wb, HttpServletRequest request, HttpServletResponse response) throws Exception { WritableSheet sheet = wb.createSheet("Spring", 0); sheet.addCell(new Label(0, 0, "Spring-Excel test")); List words = (List) model.get("wordList"); for (int i = 0; i < words.size(); i++) { sheet.addCell(new Label(2+i, 0, (String) words.get(i))); } } }
Note the differences between the APIs. We’ve found that the JExcelApi is somewhat more intuitive, and furthermore, JExcelApi has slightly better image-handling capabilities. There have been memory problems with large Excel files when using JExcelApi however.
If you now amend the controller such that it returns xl
as the name of the view (
return new ModelAndView("xl", map);
) and run your application again, you should find
that the Excel spreadsheet is created and downloaded automatically when you request the
same page as before.
The PDF version of the word list is even simpler. This time, the class extends
org.springframework.web.servlet.view.document.AbstractPdfView
and implements the
buildPdfDocument()
method as follows:
package pdf; // imports omitted for brevity public class PDFPage extends AbstractPdfView { protected void buildPdfDocument(Map model, Document doc, PdfWriter writer, HttpServletRequest req, HttpServletResponse resp) throws Exception { List words = (List) model.get("wordList"); for (int i=0; i<words.size(); i++) { doc.add( new Paragraph((String) words.get(i))); } } }
Once again, amend the controller to return the pdf
view with return new
ModelAndView("pdf", map);
, and reload the URL in your application. This time a PDF
document should appear listing each of the words in the model map.
JasperReports ( http://jasperreports.sourceforge.net) is a powerful open-source reporting engine that supports the creation of report designs using an easily understood XML file format. JasperReports is capable of rendering reports in four different formats: CSV, Excel, HTML and PDF.
Your application will need to include the latest release of JasperReports, which at the time of writing was 0.6.1. JasperReports itself depends on the following projects:
JasperReports also requires a JAXP compliant XML parser.
To configure JasperReports views in your Spring container configuration you need to
define a ViewResolver
to map view names to the appropriate view class depending on
which format you want your report rendered in.
Typically, you will use the ResourceBundleViewResolver
to map view names to view
classes and files in a properties file.
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean>
Here we’ve configured an instance of the ResourceBundleViewResolver
class that will
look for view mappings in the resource bundle with base name views
. (The content of
this file is described in the next section.)
The Spring Framework contains five different View
implementations for JasperReports,
four of which correspond to one of the four output formats supported by JasperReports,
and one that allows for the format to be determined at runtime:
Table 17.2. JasperReports View classes
Class Name | Render Format |
---|---|
| CSV |
| HTML |
| |
| Microsoft Excel |
| The view is decided upon at runtime |
Mapping one of these classes to a view name and a report file is a matter of adding the appropriate entries in the resource bundle configured in the previous section as shown here:
simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper
Here you can see that the view with name simpleReport
is mapped to the
JasperReportsPdfView
class, causing the output of this report to be rendered in PDF
format. The url
property of the view is set to the location of the underlying report
file.
JasperReports has two distinct types of report file: the design file, which has a
.jrxml
extension, and the compiled report file, which has a .jasper
extension.
Typically, you use the JasperReports Ant task to compile your .jrxml
design file into
a .jasper
file before deploying it into your application. With the Spring Framework
you can map either of these files to your report file and the framework will take care
of compiling the .jrxml
file on the fly for you. You should note that after a .jrxml
file is compiled by the Spring Framework, the compiled report is cached for the lifetime
of the application. Thus, to make changes to the file you will need to restart your
application.
The JasperReportsMultiFormatView
allows for the report format to be specified at
runtime. The actual rendering of the report is delegated to one of the other
JasperReports view classes - the JasperReportsMultiFormatView
class simply adds a
wrapper layer that allows for the exact implementation to be specified at runtime.
The JasperReportsMultiFormatView
class introduces two concepts: the format key and the
discriminator key. The JasperReportsMultiFormatView
class uses the mapping key to look
up the actual view implementation class, and it uses the format key to lookup up the
mapping key. From a coding perspective you add an entry to your model with the format
key as the key and the mapping key as the value, for example:
public ModelAndView handleSimpleReportMulti(HttpServletRequest request, HttpServletResponse response) throws Exception { String uri = request.getRequestURI(); String format = uri.substring(uri.lastIndexOf(".") + 1); Map model = getModel(); model.put("format", format); return new ModelAndView("simpleReportMulti", model); }
In this example, the mapping key is determined from the extension of the request URI and
is added to the model under the default format key: format
. If you wish to use a
different format key then you can configure this using the formatKey
property of the
JasperReportsMultiFormatView
class.
By default the following mapping key mappings are configured in
JasperReportsMultiFormatView
:
Table 17.3. JasperReportsMultiFormatView Default Mapping Key Mappings
Mapping Key | View Class |
---|---|
csv |
|
html |
|
| |
xls |
|
So in the example above a request to URI /foo/myReport.pdf would be mapped to the
JasperReportsPdfView
class. You can override the mapping key to view class mappings
using the formatMappings
property of JasperReportsMultiFormatView
.
In order to render your report correctly in the format you have chosen, you must supply
Spring with all of the data needed to populate your report. For JasperReports this means
you must pass in all report parameters along with the report datasource. Report
parameters are simple name/value pairs and can be added to the Map
for your model as
you would add any name/value pair.
When adding the datasource to the model you have two approaches to choose from. The
first approach is to add an instance of JRDataSource
or a Collection
type to the
model Map
under any arbitrary key. Spring will then locate this object in the model
and treat it as the report datasource. For example, you may populate your model like so:
private Map getModel() { Map model = new HashMap(); Collection beanData = getBeanData(); model.put("myBeanData", beanData); return model; }
The second approach is to add the instance of JRDataSource
or Collection
under a
specific key and then configure this key using the reportDataKey
property of the view
class. In both cases Spring will wrap instances of Collection
in a
JRBeanCollectionDataSource
instance. For example:
private Map getModel() { Map model = new HashMap(); Collection beanData = getBeanData(); Collection someData = getSomeData(); model.put("myBeanData", beanData); model.put("someData", someData); return model; }
Here you can see that two Collection
instances are being added to the model. To ensure
that the correct one is used, we simply modify our view configuration as appropriate:
simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper simpleReport.reportDataKey=myBeanData
Be aware that when using the first approach, Spring will use the first instance of
JRDataSource
or Collection
that it encounters. If you need to place multiple
instances of JRDataSource
or Collection
into the model you need to use the second
approach.
JasperReports provides support for embedded sub-reports within your master report files. There are a wide variety of mechanisms for including sub-reports in your report files. The easiest way is to hard code the report path and the SQL query for the sub report into your design files. The drawback of this approach is obvious: the values are hard-coded into your report files reducing reusability and making it harder to modify and update report designs. To overcome this you can configure sub-reports declaratively, and you can include additional data for these sub-reports directly from your controllers.
To control which sub-report files are included in a master report using Spring, your report file must be configured to accept sub-reports from an external source. To do this you declare a parameter in your report file like so:
<parameter name="ProductsSubReport" class="net.sf.jasperreports.engine.JasperReport"/>
Then, you define your sub-report to use this sub-report parameter:
<subreport> <reportElement isPrintRepeatedValues="false" x="5" y="25" width="325" height="20" isRemoveLineWhenBlank="true" backcolor="#ffcc99"/> <subreportParameter name="City"> <subreportParameterExpression><![CDATA[$F{city}]]></subreportParameterExpression> </subreportParameter> <dataSourceExpression><![CDATA[$P{SubReportData}]]></dataSourceExpression> <subreportExpression class="net.sf.jasperreports.engine.JasperReport"> <![CDATA[$P{ProductsSubReport}]]></subreportExpression> </subreport>
This defines a master report file that expects the sub-report to be passed in as an
instance of net.sf.jasperreports.engine.JasperReports
under the parameter
ProductsSubReport
. When configuring your Jasper view class, you can instruct Spring to
load a report file and pass it into the JasperReports engine as a sub-report using the
subReportUrls
property:
<property name="subReportUrls"> <map> <entry key="ProductsSubReport" value="/WEB-INF/reports/subReportChild.jrxml"/> </map> </property>
Here, the key of the Map
corresponds to the name of the sub-report parameter in the
report design file, and the entry is the URL of the report file. Spring will load this
report file, compiling it if necessary, and pass it into the JasperReports engine under
the given key.
This step is entirely optional when using Spring to configure your sub-reports. If you
wish, you can still configure the data source for your sub-reports using static queries.
However, if you want Spring to convert data returned in your ModelAndView
into
instances of JRDataSource
then you need to specify which of the parameters in your
ModelAndView
Spring should convert. To do this, configure the list of parameter names
using the subReportDataKeys
property of your chosen view class:
<property name="subReportDataKeys" value="SubReportData"/>
Here, the key you supply must correspond to both the key used in your ModelAndView
and the key used in your report design file.
If you have special requirements for exporter configuration — perhaps you want a
specific page size for your PDF report — you can configure these exporter parameters
declaratively in your Spring configuration file using the exporterParameters
property
of the view class. The exporterParameters
property is typed as a Map
. In your
configuration the key of an entry should be the fully-qualified name of a static field
that contains the exporter parameter definition, and the value of an entry should be the
value you want to assign to the parameter. An example of this is shown below:
<bean id="htmlReport" class="org.springframework.web.servlet.view.jasperreports.JasperReportsHtmlView"> <property name="url" value="/WEB-INF/reports/simpleReport.jrxml"/> <property name="exporterParameters"> <map> <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER"> <value>Footer by Spring! </td><td width="50%">&nbsp; </td></tr> </table></body></html> </value> </entry> </map> </property> </bean>
Here you can see that the JasperReportsHtmlView
is configured with an exporter
parameter for net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER
which will output a footer in the resulting HTML.
Both AbstractAtomFeedView
and AbstractRssFeedView
inherit from the base class
AbstractFeedView
and are used to provide Atom and RSS Feed views respectfully. They
are based on java.net’s ROME project and are located in the
package org.springframework.web.servlet.view.feed
.
AbstractAtomFeedView
requires you to implement the buildFeedEntries()
method and
optionally override the buildFeedMetadata()
method (the default implementation is
empty), as shown below.
public class SampleContentAtomView extends AbstractAtomFeedView { @Override protected void buildFeedMetadata(Map<String, Object> model, Feed feed, HttpServletRequest request) { // implementation omitted } @Override protected List<Entry> buildFeedEntries(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // implementation omitted } }
Similar requirements apply for implementing AbstractRssFeedView
, as shown below.
public class SampleContentAtomView extends AbstractRssFeedView { @Override protected void buildFeedMetadata(Map<String, Object> model, Channel feed, HttpServletRequest request) { // implementation omitted } @Override protected List<Item> buildFeedItems(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // implementation omitted } }
The buildFeedItems()
and buildFeedEntires()
methods pass in the HTTP request in case
you need to access the Locale. The HTTP response is passed in only for the setting of
cookies or other HTTP headers. The feed will automatically be written to the response
object after the method returns.
For an example of creating an Atom view please refer to Alef Arendsen’s Spring Team Blog entry.
The MarshallingView
uses an XML Marshaller
defined in the org.springframework.oxm
package to render the response content as XML. The object to be marshalled can be set
explicitly using MarhsallingView
's modelKey
bean property. Alternatively, the view
will iterate over all model properties and marshal the first type that is supported
by the Marshaller
. For more information on the functionality in the
org.springframework.oxm
package refer to the chapter Marshalling XML using O/X
Mappers.
The MappingJackson2JsonView
uses the Jackson library’s ObjectMapper
to render the response
content as JSON. By default, the entire contents of the model map (with the exception of
framework-specific classes) will be encoded as JSON. For cases where the contents of the
map need to be filtered, users may specify a specific set of model attributes to encode
via the RenderedAttributes
property. The extractValueFromSingleKeyModel
property may
also be used to have the value in single-key models extracted and serialized directly
rather than as a map of model attributes.
JSON mapping can be customized as needed through the use of Jackson’s provided
annotations. When further control is needed, a custom ObjectMapper
can be injected
through the ObjectMapper
property for cases where custom JSON
serializers/deserializers need to be provided for specific types.
JSONP is supported and automatically enabled when
the request has a query parameter named jsonp
or callback
. The JSONP query parameter
name(s) could be customized through the jsonpParameterNames
property.
The MappingJackson2XmlView
uses the
Jackson XML extension's XmlMapper
to render the response content as XML. If the model contains multiples entries, the
object to be serialized should be set explicitly using MappingJackson2XmlView
's
modelKey
bean property. If the model contains a single entry, it will be serialized
automatically.
XML mapping can be customized as needed through the use of JAXB or Jackson’s provided
annotations. When further control is needed, a custom XmlMapper
can be injected
through the ObjectMapper
property for cases where custom XML
serializers/deserializers need to be provided for specific types.