spring-framework/spring-framework-reference/src/web-integration.xml

1132 lines
54 KiB
XML
Raw Normal View History

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="web-integration">
<title>Integrating with other web frameworks</title>
<section id="intro">
<title>Introduction</title>
<para>This chapter details Spring's integration with third party web
frameworks such as <ulink
url="http://java.sun.com/javaee/javaserverfaces/">JSF</ulink>, <ulink
url="http://struts.apache.org/">Struts</ulink>, <ulink
url="http://www.opensymphony.com/webwork/">WebWork</ulink>, and <ulink
url="http://tapestry.apache.org/">Tapestry</ulink>.</para>
<!-- insert some content about Spring Web Flow here -->
<xi:include href="swf-sidebar.xml"
xmlns:xi="http://www.w3.org/2001/XInclude" />
<para>One of the core value propositions of the Spring Framework is that
of enabling <emphasis>choice</emphasis>. In a general sense, Spring does
not force one to use or buy into any particular architecture, technology,
or methodology (although it certainly recommends some over others). This
freedom to pick and choose the architecture, technology, or methodology
that is most relevant to a developer and his or her development team is
arguably most evident in the web area, where Spring provides its own web
framework (<link linkend="mvc">Spring MVC</link>), while at the same time
providing integration with a number of popular third party web frameworks.
This allows one to continue to leverage any and all of the skills one may
have acquired in a particular web framework such as Struts, while at the
same time being able to enjoy the benefits afforded by Spring in other
areas such as data access, declarative transaction management, and
flexible configuration and application assembly.</para>
<para>Having dispensed with the woolly sales patter (c.f. the previous
paragraph), the remainder of this chapter will concentrate upon the meaty
details of integrating your favorite web framework with Spring. One thing
that is often commented upon by developers coming to Java from other
languages is the seeming super-abundance of web frameworks available in
Java. There are indeed a great number of web frameworks in the Java
space; in fact there are far too many to cover with any semblance of
detail in a single chapter. This chapter thus picks four of the more
popular web frameworks in Java, starting with the Spring configuration
that is common to all of the supported web frameworks, and then detailing
the specific integration options for each supported web framework.</para>
<note>
<para>Please note that this chapter does not attempt to explain
how to use any of the supported web frameworks. For example, if you want
to use Struts for the presentation layer of your web application, the
assumption is that you are already familiar with Struts. If you need
further details about any of the supported web frameworks themselves,
please do consult <xref linkend="web-integration-resources" /> at the end
of this chapter.
</para>
</note>
</section>
<section id="web-integration-common">
<title>Common configuration</title>
<para>Before diving into the integration specifics of each supported web
framework, let us first take a look at the Spring configuration that is
<emphasis>not</emphasis> specific to any one web framework. (This section
is equally applicable to Spring's own web framework, Spring MVC.)</para>
<para>One of the concepts (for want of a better word) espoused by
(Spring's) lightweight application model is that of a layered
architecture. Remember that in a 'classic' layered architecture, the web
layer is but one of many layers; it serves as one of the entry points
into a server side application and it delegates to service objects
(facades) defined in a service layer to satisfy business specific (and
presentation-technology agnostic) use cases. In Spring, these service
objects, any other business-specific objects, data access objects, etc.
exist in a distinct 'business context', which contains
<emphasis>no</emphasis> web or presentation layer objects (presentation
objects such as Spring MVC controllers are typically configured in a
distinct 'presentation context'). This section details how one configures
a Spring container (a <classname>WebApplicationContext</classname>) that
contains all of the 'business beans' in one's application.</para>
<para>On to specifics: all that one need do is to declare a <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/context/ContextLoaderListener.html"><classname>ContextLoaderListener</classname></ulink>
2009-07-31 02:32:05 +08:00
in the standard Java EE servlet <literal>web.xml</literal> file of one's web
application, and add a <literal>contextConfigLocation</literal>
&lt;context-param/&gt; section (in the same file) that defines which set
of Spring XML configuration files to load.</para>
<para>Find below the &lt;listener/&gt; configuration:</para>
<programlisting language="xml">&lt;listener&gt;
&lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
&lt;/listener&gt;</programlisting>
<para>Find below the &lt;context-param/&gt; configuration:</para>
<programlisting language="xml">&lt;context-param&gt;
&lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
&lt;param-value&gt;/WEB-INF/applicationContext*.xml&lt;/param-value&gt;
&lt;/context-param&gt;</programlisting>
<para>If you don't specify the <literal>contextConfigLocation</literal>
context parameter, the <classname>ContextLoaderListener</classname> will
look for a file called <literal>/WEB-INF/applicationContext.xml</literal>
to load. Once the context files are loaded, Spring creates a <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/context/WebApplicationContext.html"><classname>WebApplicationContext</classname></ulink>
object based on the bean definitions and stores it in the
<interface>ServletContext</interface> of the web application.</para>
<para>All Java web frameworks are built on top of the Servlet API, and so
one can use the following code snippet to get access to this 'business
context' <interface>ApplicationContext</interface> created by the
<classname>ContextLoaderListener</classname>.</para>
<programlisting language="java">WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);</programlisting>
<para>The <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/context/support/WebApplicationContextUtils.html"><classname>WebApplicationContextUtils</classname></ulink>
class is for convenience, so you don't have to remember the name of the
<interface>ServletContext</interface> attribute. Its
<emphasis>getWebApplicationContext()</emphasis> method will return
<literal>null</literal> if an object doesn't exist under the
<literal>WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE</literal>
key. Rather than risk getting <classname>NullPointerExceptions</classname>
in your application, it's better to use the
<literal>getRequiredWebApplicationContext()</literal> method. This method
throws an exception when the <interface>ApplicationContext</interface> is
missing.</para>
<para>Once you have a reference to the
<classname>WebApplicationContext</classname>, you can retrieve beans by
their name or type. Most developers retrieve beans by name and then cast them
to one of their implemented interfaces.</para>
<para>Fortunately, most of the frameworks in this section have simpler
ways of looking up beans. Not only do they make it easy to get beans from
a Spring container, but they also allow you to use dependency injection on
their controllers. Each web framework section has more detail on its
specific integration strategies.</para>
</section>
<section id="jsf">
<title>JavaServer Faces 1.1 and 1.2</title>
<para>JavaServer Faces (JSF) is the JCP's standard component-based,
event-driven web user interface framework. As of Java EE 5, it is an
official part of the Java EE umbrella.</para>
<para>For a popular JSF runtime as well as for popular JSF component
libraries, check out the <ulink url="http://myfaces.apache.org/">Apache
MyFaces project</ulink>. The MyFaces project also provides common JSF
extensions such as <ulink
url="http://myfaces.apache.org/orchestra/">MyFaces Orchestra</ulink>: a
Spring-based JSF extension that provides rich conversation scope
support.</para>
<note>
<para>Spring Web Flow 2.0 provides rich JSF support through its newly
established Spring Faces module, both for JSF-centric usage (as
described in this section) and for Spring-centric usage (using JSF views
within a Spring MVC dispatcher). Check out the <ulink
url="http://www.springframework.org/webflow">Spring Web Flow
website</ulink> for details!</para>
</note>
<para>The key element in Spring's JSF integration is the JSF 1.1
<classname>VariableResolver</classname> mechanism. On JSF 1.2, Spring
supports the <classname>ELResolver</classname> mechanism as a
next-generation version of JSF EL integration.</para>
<section id="jsf-delegatingvariableresolver">
<title>DelegatingVariableResolver (JSF 1.1/1.2)</title>
<para>The easiest way to integrate one's Spring middle-tier with one's
JSF web layer is to use the <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/jsf/DelegatingVariableResolver.html">
<classname>DelegatingVariableResolver</classname></ulink> class. To
configure this variable resolver in one's application, one will need to
edit one's <emphasis>faces-context.xml</emphasis> file. After the
opening <literal>&lt;faces-config/&gt;</literal> element, add an
<literal>&lt;application/&gt;</literal> element and a
<literal>&lt;variable-resolver/&gt;</literal> element within it. The
value of the variable resolver should reference Spring's
<classname>DelegatingVariableResolver</classname>; for example:</para>
<programlisting language="xml">&lt;faces-config&gt;
&lt;application&gt;
&lt;variable-resolver&gt;org.springframework.web.jsf.DelegatingVariableResolver&lt;/variable-resolver&gt;
&lt;locale-config&gt;
&lt;default-locale&gt;en&lt;/default-locale&gt;
&lt;supported-locale&gt;en&lt;/supported-locale&gt;
&lt;supported-locale&gt;es&lt;/supported-locale&gt;
&lt;/locale-config&gt;
&lt;message-bundle&gt;messages&lt;/message-bundle&gt;
&lt;/application&gt;
&lt;/faces-config&gt;</programlisting>
<para>The <classname>DelegatingVariableResolver</classname> will first
delegate value lookups to the default resolver of the underlying JSF
implementation and then to Spring's 'business context'
<classname>WebApplicationContext</classname>. This allows one to easily
inject dependencies into one's JSF-managed beans.</para>
<para>Managed beans are defined in one's
<literal>faces-config.xml</literal> file. Find below an example where
<literal>#{userManager}</literal> is a bean that is retrieved from the
Spring 'business context'.</para>
<programlisting language="xml">&lt;managed-bean&gt;
&lt;managed-bean-name&gt;userList&lt;/managed-bean-name&gt;
&lt;managed-bean-class&gt;com.whatever.jsf.UserList&lt;/managed-bean-class&gt;
&lt;managed-bean-scope&gt;request&lt;/managed-bean-scope&gt;
&lt;managed-property&gt;
&lt;property-name&gt;userManager&lt;/property-name&gt;
&lt;value&gt;#{userManager}&lt;/value&gt;
&lt;/managed-property&gt;
&lt;/managed-bean&gt;</programlisting>
</section>
<section id="jsf-springbeanvariableresolver">
<title>SpringBeanVariableResolver (JSF 1.1/1.2)</title>
<para><classname>SpringBeanVariableResolver</classname> is a variant of
<classname>DelegatingVariableResolver</classname>. It delegates to the
Spring's 'business context' <classname>WebApplicationContext</classname>
<emphasis>first</emphasis> and then to the default resolver of the
underlying JSF implementation. This is useful in particular when using
request/session-scoped beans with special Spring resolution rules, e.g.
Spring <interfacename>FactoryBean</interfacename>
implementations.</para>
<para>Configuration-wise, simply define
<classname>SpringBeanVariableResolver</classname> in your
<emphasis>faces-context.xml</emphasis> file:</para>
<programlisting language="xml">&lt;faces-config&gt;
&lt;application&gt;
&lt;variable-resolver&gt;org.springframework.web.jsf.SpringBeanVariableResolver&lt;/variable-resolver&gt;
...
&lt;/application&gt;
&lt;/faces-config&gt;</programlisting>
</section>
<section id="jsf-springbeanfaceselresolver">
<title>SpringBeanFacesELResolver (JSF 1.2+)</title>
<para><classname>SpringBeanFacesELResolver</classname> is a JSF 1.2
compliant <classname>ELResolver</classname> implementation, integrating
with the standard Unified EL as used by JSF 1.2 and JSP 2.1. Like
<classname>SpringBeanVariableResolver</classname>, it delegates to the
Spring's 'business context' <classname>WebApplicationContext</classname>
<emphasis>first</emphasis>, then to the default resolver of the
underlying JSF implementation.</para>
<para>Configuration-wise, simply define
<classname>SpringBeanFacesELResolver</classname> in your JSF 1.2
<emphasis>faces-context.xml</emphasis> file:</para>
<programlisting language="xml">&lt;faces-config&gt;
&lt;application&gt;
&lt;el-resolver&gt;org.springframework.web.jsf.el.SpringBeanFacesELResolver&lt;/el-resolver&gt;
...
&lt;/application&gt;
&lt;/faces-config&gt;</programlisting>
</section>
<section id="jsf-facescontextutils">
<title>FacesContextUtils</title>
<para>A custom <interfacename>VariableResolver</interfacename> works
well when mapping one's properties to beans in
<emphasis>faces-config.xml</emphasis>, but at times one may need to grab
a bean explicitly. The <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/jsf/FacesContextUtils.html">
<classname>FacesContextUtils</classname></ulink> class makes this easy.
It is similar to <classname>WebApplicationContextUtils</classname>,
except that it takes a <classname>FacesContext</classname> parameter
rather than a <interface>ServletContext</interface> parameter.</para>
<programlisting language="java">ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());</programlisting>
</section>
</section>
<section id="struts">
<title>Apache Struts 1.x and 2.x</title>
<para><ulink url="http://struts.apache.org">Struts</ulink> used to be the
<emphasis>de facto</emphasis> web framework for Java applications, mainly
because it was one of the first to be released (June 2001). It has now been renamed to <emphasis>Struts 1</emphasis>
(as opposed to Struts 2). Many applications still use it.
Invented by Craig McClanahan, Struts is an open source project hosted by the Apache
Software Foundation. At the time, it greatly simplified the JSP/Servlet
programming paradigm and won over many developers who were using
proprietary frameworks. It simplified the programming model, it was open
source (and thus free as in beer), and it had a large community, which
allowed the project to grow and become popular among Java web
developers.</para>
<note>
<para><emphasis>The following section discusses Struts 1 a.k.a. "Struts
Classic".</emphasis></para>
<para>Struts 2 is effectively a different product - a successor of
WebWork 2.2 (as discussed in <xref linkend="webwork" />), carrying the
Struts brand now. Check out the Struts 2 <ulink
url="http://struts.apache.org/2.x/docs/spring-plugin.html">Spring
Plugin</ulink> for the built-in Spring integration shipped with Struts
2. In general, Struts 2 is closer to WebWork 2.2 than to Struts 1 in
terms of its Spring integration implications.</para>
</note>
<para>To integrate your Struts 1.x application with Spring, you have two
options:</para>
<itemizedlist>
<listitem>
<para>Configure Spring to manage your Actions as beans, using the
<classname>ContextLoaderPlugin</classname>, and set their dependencies
in a Spring context file.</para>
</listitem>
<listitem>
<para>Subclass Spring's <classname>ActionSupport</classname> classes
and grab your Spring-managed beans explicitly using a
<emphasis>getWebApplicationContext()</emphasis> method.</para>
</listitem>
</itemizedlist>
<section id="struts-contextloaderplugin">
<title>ContextLoaderPlugin</title>
<para>The <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/ContextLoaderPlugIn.html"><classname>ContextLoaderPlugin</classname></ulink>
is a Struts 1.1+ plug-in that loads a Spring context file for the Struts
<classname>ActionServlet</classname>. This context refers to the root
<classname>WebApplicationContext</classname> (loaded by the
<classname>ContextLoaderListener</classname>) as its parent. The default
name of the context file is the name of the mapped servlet, plus
<emphasis>-servlet.xml</emphasis>. If
<classname>ActionServlet</classname> is defined in web.xml as
<literal>&lt;servlet-name&gt;action&lt;/servlet-name&gt;</literal>, the
default is <emphasis>/WEB-INF/action-servlet.xml</emphasis>.</para>
<para>To configure this plug-in, add the following XML to the plug-ins
section near the bottom of your <emphasis>struts-config.xml</emphasis>
file:</para>
<programlisting language="xml">&lt;plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/&gt;</programlisting>
<para>The location of the context configuration files can be customized
using the '<literal>contextConfigLocation</literal>' property.</para>
<programlisting language="xml">&lt;plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"&gt;
&lt;set-property property="contextConfigLocation"
value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.xml"/&gt;
&lt;/plug-in&gt;</programlisting>
<para>It is possible to use this plugin to load all your context files,
which can be useful when using testing tools like StrutsTestCase.
StrutsTestCase's <classname>MockStrutsTestCase</classname> won't
initialize Listeners on startup so putting all your context files in the
plugin is a workaround. (A <ulink
url="http://sourceforge.net/tracker/index.php?func=detail&amp;aid=1088866&amp;group_id=39190&amp;atid=424562">
bug has been filed</ulink> for this issue, but has been closed as 'Wont
Fix').</para>
<para>After configuring this plug-in in
<emphasis>struts-config.xml</emphasis>, you can configure your
<classname>Action</classname> to be managed by Spring. Spring (1.1.3+)
provides two ways to do this:</para>
<itemizedlist>
<listitem>
<para>Override Struts' default
<classname>RequestProcessor</classname> with Spring's
<classname>DelegatingRequestProcessor</classname>.</para>
</listitem>
<listitem>
<para>Use the <classname>DelegatingActionProxy</classname> class in
the <literal>type</literal> attribute of your
<literal>&lt;action-mapping&gt;</literal>.</para>
</listitem>
</itemizedlist>
<para>Both of these methods allow you to manage your Actions and their
dependencies in the <emphasis>action-servlet.xml</emphasis> file. The
bridge between the Action in <emphasis>struts-config.xml</emphasis> and
<emphasis>action-servlet.xml</emphasis> is built with the
action-mapping's "path" and the bean's "name". If you have the following
in your <emphasis>struts-config.xml</emphasis> file:</para>
<programlisting language="xml">&lt;action path="/users" .../&gt;</programlisting>
<para>You must define that Action's bean with the "/users" name in
<emphasis>action-servlet.xml</emphasis>:</para>
<programlisting language="xml">&lt;bean name="/users" .../&gt;</programlisting>
<section id="struts-delegatingrequestprocessor">
<title>DelegatingRequestProcessor</title>
<para>To configure the <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/DelegatingRequestProcessor.html">
<literal>DelegatingRequestProcessor</literal></ulink> in your
<emphasis>struts-config.xml</emphasis> file, override the
"processorClass" property in the &lt;controller&gt; element. These
lines follow the &lt;action-mapping&gt; element.</para>
<programlisting language="xml">&lt;controller&gt;
&lt;set-property property="processorClass"
value="org.springframework.web.struts.DelegatingRequestProcessor"/&gt;
&lt;/controller&gt;</programlisting>
<para>After adding this setting, your Action will automatically be
looked up in Spring's context file, no matter what the type. In fact,
you don't even need to specify a type. Both of the following snippets
will work:</para>
<programlisting language="xml">&lt;action path="/user" type="com.whatever.struts.UserAction"/&gt;
&lt;action path="/user"/&gt;</programlisting>
<para>If you're using Struts' <emphasis>modules</emphasis> feature,
your bean names must contain the module prefix. For example, an action
defined as <literal>&lt;action path="/user"/&gt;</literal> with module
prefix "admin" requires a bean name with <literal>&lt;bean
name="/admin/user"/&gt;</literal>.</para>
<note>
<para>If you are using Tiles in your Struts application, you must
configure your &lt;controller&gt; with the <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/DelegatingTilesRequestProcessor.html"><classname>DelegatingTilesRequestProcessor</classname></ulink>
instead.</para>
</note>
</section>
<section id="struts-delegatingactionproxy">
<title>DelegatingActionProxy</title>
<para>If you have a custom <classname>RequestProcessor</classname> and
can't use the <classname>DelegatingRequestProcessor</classname> or
<classname>DelegatingTilesRequestProcessor</classname> approaches, you
can use the <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/DelegatingActionProxy.html">
<classname>DelegatingActionProxy</classname></ulink> as the type in
your action-mapping.</para>
<programlisting language="xml">&lt;action path="/user" type="org.springframework.web.struts.DelegatingActionProxy"
name="userForm" scope="request" validate="false" parameter="method"&gt;
&lt;forward name="list" path="/userList.jsp"/&gt;
&lt;forward name="edit" path="/userForm.jsp"/&gt;
&lt;/action&gt;</programlisting>
<para>The bean definition in <emphasis>action-servlet.xml</emphasis>
remains the same, whether you use a custom
<literal>RequestProcessor</literal> or the
<classname>DelegatingActionProxy</classname>.</para>
<para>If you define your <classname>Action</classname> in a context
file, the full feature set of Spring's bean container will be
available for it: dependency injection as well as the option to
instantiate a new <classname>Action</classname> instance for each
request. To activate the latter, add
<emphasis>scope="prototype"</emphasis> to your Action's bean
definition.</para>
<programlisting language="xml">&lt;bean name="/user" scope="prototype" autowire="byName"
class="org.example.web.UserAction"/&gt;</programlisting>
</section>
</section>
<section id="struts-actionsupport">
<title>ActionSupport Classes</title>
<para>As previously mentioned, you can retrieve the
<classname>WebApplicationContext</classname> from the
<interface>ServletContext</interface> using the
<classname>WebApplicationContextUtils</classname> class. An easier way
is to extend Spring's <classname>Action</classname> classes for Struts.
For example, instead of subclassing Struts'
<classname>Action</classname> class, you can subclass Spring's <ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/ActionSupport.html">
<classname>ActionSupport</classname></ulink> class.</para>
<para>The <classname>ActionSupport</classname> class provides additional
convenience methods, like
<emphasis>getWebApplicationContext()</emphasis>. Below is an example of
how you might use this in an Action:</para>
<programlisting language="java">public class UserAction extends DispatchActionSupport {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (log.isDebugEnabled()) {
log.debug("entering 'delete' method...");
}
WebApplicationContext ctx = getWebApplicationContext();
UserManager mgr = (UserManager) ctx.getBean("userManager");
// talk to manager for business logic
return mapping.findForward("success");
}
}</programlisting>
<para>Spring includes subclasses for all of the standard Struts Actions
- the Spring versions merely have <emphasis>Support</emphasis> appended
to the name: <itemizedlist spacing="compact">
<listitem>
<para><ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/ActionSupport.html"><classname>ActionSupport</classname></ulink>,</para>
</listitem>
<listitem>
<para><ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/DispatchActionSupport.html"><literal>DispatchActionSupport</literal></ulink>,</para>
</listitem>
<listitem>
<para><ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/LookupDispatchActionSupport.html"><literal>LookupDispatchActionSupport</literal></ulink>
and</para>
</listitem>
<listitem>
<para><ulink
url="http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/struts/MappingDispatchActionSupport.html"><literal>MappingDispatchActionSupport</literal></ulink>.</para>
</listitem>
</itemizedlist></para>
<para>The recommended strategy is to use the approach that best suits
your project. Subclassing makes your code more readable, and you know
exactly how your dependencies are resolved. In contrast, using the
<classname>ContextLoaderPlugin</classname> allows you to easily add new
dependencies in your context XML file. Either way, Spring provides some
nice options for integrating with Struts.</para>
</section>
</section>
<section id="webwork">
<title>WebWork 2.x</title>
<para>From the <ulink url="http://www.opensymphony.com/webwork/">WebWork
homepage</ulink>:</para>
<para>
<quote>
<emphasis>WebWork is a Java web-application development framework. It is
built specifically with developer productivity and code simplicity in
mind, providing robust support for building reusable UI templates, such
as form controls, UI themes, internationalization, dynamic form
parameter mapping to JavaBeans, robust client and server side
validation, and much more.</emphasis>
</quote>
</para>
2010-02-10 23:38:54 +08:00
<para>Web work's architecture and concepts are easy to
understand, and the framework also has an extensive tag library as well as
nicely decoupled validation.</para>
<para>One of the key enablers in WebWork's technology stack is <ulink
url="http://www.opensymphony.com/webwork/wikidocs/IoC%20Overview.html">an
IoC container</ulink> to manage Webwork Actions, handle the "wiring" of
business objects, etc. Prior to WebWork version 2.2, WebWork used its own
proprietary IoC container (and provided integration points so that one
could integrate an IoC container such as Spring's into the mix). However,
as of WebWork version 2.2, the default IoC container that is used within
WebWork <emphasis>is</emphasis> Spring. This is obviously great news if
one is a Spring developer, because it means that one is immediately
familiar with the basics of IoC configuration, idioms, and suchlike within
WebWork.</para>
<para>Now in the interests of adhering to the DRY (Don't Repeat Yourself)
principle, it would be foolish to document the Spring-WebWork integration
in light of the fact that the WebWork team have already written such a
writeup. Please consult the <ulink
url="http://www.opensymphony.com/webwork/wikidocs/Spring.html">Spring-WebWork
integration page</ulink> on the <ulink
url="http://wiki.opensymphony.com/display/WW/WebWork">WebWork wiki</ulink>
for the full lowdown.</para>
<para>Note that the Spring-WebWork integration code was developed (and
continues to be maintained and improved) by the WebWork developers
themselves. So please refer first to the WebWork site and forums if you are
having issues with the integration. But feel free to
post comments and queries regarding the Spring-WebWork integration on the
<ulink url="http://forum.springframework.org/forumdisplay.php?f=25">Spring
support forums</ulink>, too.</para>
</section>
<section id="tapestry">
<title>Tapestry 3.x and 4.x</title>
<para>From the <ulink url="http://tapestry.apache.org/">Tapestry
homepage</ulink>:</para>
<para>
<quote>
<emphasis>Tapestry is an open-source framework for creating dynamic,
robust, highly scalable web applications in Java. Tapestry complements
and builds upon the standard Java Servlet API, and so it works in any
servlet container or application server.</emphasis>
</quote>
</para>
<para>While Spring has its own <link linkend="mvc">powerful web
layer</link>, there are a number of unique advantages to building an
enterprise Java application using a combination of Tapestry for the web
user interface and the Spring container for the lower layers. This section
of the web integration chapter attempts to detail a few best practices for
combining these two frameworks.</para>
<para>A <emphasis>typical</emphasis> layered enterprise Java application
built with Tapestry and Spring will consist of a top user interface (UI)
layer built with Tapestry, and a number of lower layers, all wired together
by one or more Spring containers. Tapestry's own reference documentation
contains the following snippet of best practice advice. (Text that the
author of this Spring section has added is contained within
<literal>[]</literal> brackets.)</para>
<para>
<quote>
<emphasis>A very succesful design pattern in Tapestry is to keep pages
and components very simple, and <emphasis
role="bold">delegate</emphasis> as much logic as possible out to
HiveMind [or Spring, or whatever] services. Listener methods should
2010-08-07 21:57:23 +08:00
ideally do little more than marshal together the correct information
and pass it over to a service.</emphasis>
</quote>
</para>
<para>The key question then is: how does one supply Tapestry pages with
collaborating services? The answer, ideally, is that one would want to
dependency inject those services directly into one's Tapestry pages. In
Tapestry, one can effect this dependency injection by a variety of
means. This section is only going to enumerate the dependency injection
means afforded by Spring. The real beauty of the rest of this
Spring-Tapestry integration is that the elegant and flexible design of
Tapestry itself makes doing this dependency injection of Spring-managed
beans a cinch. (Another nice thing is that this Spring-Tapestry
integration code was written - and continues to be maintained - by the
Tapestry creator <ulink url="http://howardlewisship.com/blog/">Howard M.
Lewis Ship</ulink>, so hats off to him for what is really some silky
smooth integration).</para>
<section id="tapestry-di">
<title>Injecting Spring-managed beans</title>
<para>Assume we have the following simple Spring container definition
(in the ubiquitous XML format):</para>
<programlisting language="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<emphasis role="bold">xmlns:jee="http://www.springframework.org/schema/jee"</emphasis>
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
2009-11-21 04:40:55 +08:00
<emphasis role="bold">http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd"</emphasis>&gt;
&lt;beans&gt;
&lt;!-- the DataSource --&gt;
&lt;jee:jndi-lookup id="dataSource" jndi-name="java:DefaultDS"/&gt;
&lt;bean id="hibSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt;
&lt;property name="dataSource" ref="dataSource"/&gt;
&lt;/bean&gt;
&lt;bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"/&gt;
&lt;bean id="mapper"
class="com.whatever.dataaccess.mapper.hibernate.MapperImpl"&gt;
&lt;property name="sessionFactory" ref="hibSessionFactory"/&gt;
&lt;/bean&gt;
&lt;!-- (transactional) AuthenticationService --&gt;
&lt;bean id="authenticationService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;
&lt;property name="transactionManager" ref="transactionManager"/&gt;
&lt;property name="target"&gt;
&lt;bean class="com.whatever.services.service.user.AuthenticationServiceImpl"&gt;
&lt;property name="mapper" ref="mapper"/&gt;
&lt;/bean&gt;
&lt;/property&gt;
&lt;property name="proxyInterfacesOnly" value="true"/&gt;
&lt;property name="transactionAttributes"&gt;
&lt;value&gt;
*=PROPAGATION_REQUIRED
&lt;/value&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;!-- (transactional) UserService --&gt;
&lt;bean id="userService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;
&lt;property name="transactionManager" ref="transactionManager"/&gt;
&lt;property name="target"&gt;
&lt;bean class="com.whatever.services.service.user.UserServiceImpl"&gt;
&lt;property name="mapper" ref="mapper"/&gt;
&lt;/bean&gt;
&lt;/property&gt;
&lt;property name="proxyInterfacesOnly" value="true"/&gt;
&lt;property name="transactionAttributes"&gt;
&lt;value&gt;
*=PROPAGATION_REQUIRED
&lt;/value&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;/beans&gt;</programlisting>
<para>Inside the Tapestry application, the above bean definitions need
to be <link linkend="web-integration-common">loaded into a Spring
container</link>, and any relevant Tapestry pages need to be supplied
(injected) with the <literal>authenticationService</literal> and
<literal>userService</literal> beans, which implement the
<interfacename>AuthenticationService</interfacename> and
<interfacename>UserService</interfacename> interfaces,
respectively.</para>
<para>At this point, the application context is available to a web
application by calling Spring's static utility function
<literal>WebApplicationContextUtils.getApplicationContext(servletContext)</literal>,
where servletContext is the standard
2009-07-31 02:32:05 +08:00
<interface>ServletContext</interface> from the Java EE Servlet
specification. As such, one simple mechanism for a page to get an
instance of the <interfacename>UserService</interfacename>, for example,
would be with code such as:</para>
<programlisting language="java">WebApplicationContext appContext = WebApplicationContextUtils.getApplicationContext(
getRequestCycle().getRequestContext().getServlet().getServletContext());
UserService userService = (UserService) appContext.getBean("userService");
<lineannotation>// ... some code which uses UserService</lineannotation></programlisting>
<para>This mechanism does work. Having said that, it can be made a lot
less verbose by encapsulating most of the functionality in a method in
the base class for the page or component. However, in some respects it
goes against the IoC principle; ideally you would like the page to not
have to ask the context for a specific bean by name, and in fact, the
page would ideally not know about the context at all.</para>
<para>Luckily, there is a mechanism to allow this. We rely upon the fact
that Tapestry already has a mechanism to declaratively add properties to
a page, and it is in fact the preferred approach to manage all
properties on a page in this declarative fashion, so that Tapestry can
properly manage their lifecycle as part of the page and component
lifecycle.</para>
<note>
<para>This next section is applicable to Tapestry 3.x. If you are
using Tapestry version 4.x, please consult the section entitled <xref
linkend="tapestry-4-style-di" />.</para>
</note>
<section id="tapestry-pre4-style-di">
<title>Dependency Injecting Spring Beans into Tapestry pages</title>
<para>First we need to make the
<interface>ApplicationContext</interface> available to the Tapestry
page or Component without having to have the
<interface>ServletContext</interface>; this is because at the stage in
the page's/component's lifecycle when we need to access the
<interface>ApplicationContext</interface>, the
<interface>ServletContext</interface> won't be easily available to the
page, so we can't use
<literal>WebApplicationContextUtils.getApplicationContext(servletContext)</literal>
directly. One way is by defining a custom version of the Tapestry
<interfacename>IEngine</interfacename> which exposes this for
us:</para>
<programlisting language="java">package com.whatever.web.xportal;
// import ...
public class MyEngine extends org.apache.tapestry.engine.BaseEngine {
public static final String APPLICATION_CONTEXT_KEY = "appContext";
/**
* @see org.apache.tapestry.engine.AbstractEngine#setupForRequest(org.apache.tapestry.request.RequestContext)
*/
protected void setupForRequest(RequestContext context) {
super.setupForRequest(context);
// insert ApplicationContext in global, if not there
Map global = (Map) getGlobal();
ApplicationContext ac = (ApplicationContext) global.get(APPLICATION_CONTEXT_KEY);
if (ac == null) {
ac = WebApplicationContextUtils.getWebApplicationContext(
context.getServlet().getServletContext()
);
global.put(APPLICATION_CONTEXT_KEY, ac);
}
}
}</programlisting>
<para>This engine class places the Spring Application Context as an
attribute called "appContext" in this Tapestry app's 'Global' object.
Make sure to register the fact that this special IEngine instance
should be used for this Tapestry application, with an entry in the
Tapestry application definition file. For example:</para>
<programlisting language="xml"><lineannotation>file: xportal.application:</lineannotation>
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE application PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"&gt;
&lt;application
name="Whatever xPortal"
engine-class="com.whatever.web.xportal.MyEngine"&gt;
&lt;/application&gt;</programlisting>
</section>
<section id="tapestry-componentdefs">
<title>Component definition files</title>
<para>Now in our page or component definition file (*.page or *.jwc),
we simply add property-specification elements to grab the beans we
need out of the <interfacename>ApplicationContext</interfacename>, and
create page or component properties for them. For example:</para>
<programlisting language="xml"> &lt;property-specification name="userService"
type="com.whatever.services.service.user.UserService"&gt;
global.appContext.getBean("userService")
&lt;/property-specification&gt;
&lt;property-specification name="authenticationService"
type="com.whatever.services.service.user.AuthenticationService"&gt;
global.appContext.getBean("authenticationService")
&lt;/property-specification&gt;</programlisting>
<para>The OGNL expression inside the property-specification specifies
the initial value for the property, as a bean obtained from the
context. The entire page definition might look like this:</para>
<programlisting language="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"&gt;
&lt;page-specification class="com.whatever.web.xportal.pages.Login"&gt;
&lt;property-specification name="username" type="java.lang.String"/&gt;
&lt;property-specification name="password" type="java.lang.String"/&gt;
&lt;property-specification name="error" type="java.lang.String"/&gt;
&lt;property-specification name="callback" type="org.apache.tapestry.callback.ICallback" persistent="yes"/&gt;
&lt;property-specification name="userService"
type="com.whatever.services.service.user.UserService"&gt;
global.appContext.getBean("userService")
&lt;/property-specification&gt;
&lt;property-specification name="authenticationService"
type="com.whatever.services.service.user.AuthenticationService"&gt;
global.appContext.getBean("authenticationService")
&lt;/property-specification&gt;
&lt;bean name="delegate" class="com.whatever.web.xportal.PortalValidationDelegate"/&gt;
&lt;bean name="validator" class="org.apache.tapestry.valid.StringValidator" lifecycle="page"&gt;
&lt;set-property name="required" expression="true"/&gt;
&lt;set-property name="clientScriptingEnabled" expression="true"/&gt;
&lt;/bean&gt;
&lt;component id="inputUsername" type="ValidField"&gt;
&lt;static-binding name="displayName" value="Username"/&gt;
&lt;binding name="value" expression="username"/&gt;
&lt;binding name="validator" expression="beans.validator"/&gt;
&lt;/component&gt;
&lt;component id="inputPassword" type="ValidField"&gt;
&lt;binding name="value" expression="password"/&gt;
&lt;binding name="validator" expression="beans.validator"/&gt;
&lt;static-binding name="displayName" value="Password"/&gt;
&lt;binding name="hidden" expression="true"/&gt;
&lt;/component&gt;
&lt;/page-specification&gt;</programlisting>
</section>
<section id="tapestry-getters">
<title>Adding abstract accessors</title>
<para>Now in the Java class definition for the page or component
itself, all we need to do is add an abstract getter method for the
properties we have defined (in order to be able to access the
properties).</para>
<programlisting language="java">// our UserService implementation; will come from page definition
public abstract UserService getUserService();
// our AuthenticationService implementation; will come from page definition
public abstract AuthenticationService getAuthenticationService();</programlisting>
<para>For the sake of completeness, the entire Java class, for a login
page in this example, might look like this:</para>
<programlisting language="java">package com.whatever.web.xportal.pages;
/**
* Allows the user to login, by providing username and password.
* After successfully logging in, a cookie is placed on the client browser
* that provides the default username for future logins (the cookie
* persists for a week).
*/
public abstract class Login extends BasePage implements ErrorProperty, PageRenderListener {
/** the key under which the authenticated user object is stored in the visit as */
public static final String USER_KEY = "user";
/** The name of the cookie that identifies a user **/
private static final String COOKIE_NAME = Login.class.getName() + ".username";
private final static int ONE_WEEK = 7 * 24 * 60 * 60;
public abstract String getUsername();
public abstract void setUsername(String username);
public abstract String getPassword();
public abstract void setPassword(String password);
public abstract ICallback getCallback();
public abstract void setCallback(ICallback value);
public abstract UserService getUserService();
public abstract AuthenticationService getAuthenticationService();
protected IValidationDelegate getValidationDelegate() {
return (IValidationDelegate) getBeans().getBean("delegate");
}
protected void setErrorField(String componentId, String message) {
IFormComponent field = (IFormComponent) getComponent(componentId);
IValidationDelegate delegate = getValidationDelegate();
delegate.setFormComponent(field);
delegate.record(new ValidatorException(message));
}
/**
* Attempts to login.
* &lt;p&gt;
* If the user name is not known, or the password is invalid, then an error
* message is displayed.
**/
public void attemptLogin(IRequestCycle cycle) {
String password = getPassword();
// Do a little extra work to clear out the password.
setPassword(null);
IValidationDelegate delegate = getValidationDelegate();
delegate.setFormComponent((IFormComponent) getComponent("inputPassword"));
delegate.recordFieldInputValue(null);
// An error, from a validation field, may already have occurred.
if (delegate.getHasErrors()) {
return;
}
try {
User user = getAuthenticationService().login(getUsername(), getPassword());
loginUser(user, cycle);
}
catch (FailedLoginException ex) {
this.setError("Login failed: " + ex.getMessage());
return;
}
}
/**
* Sets up the {@link User} as the logged in user, creates
* a cookie for their username (for subsequent logins),
* and redirects to the appropriate page, or
* a specified page).
**/
public void loginUser(User user, IRequestCycle cycle) {
String username = user.getUsername();
// Get the visit object; this will likely force the
// creation of the visit object and an HttpSession
Map visit = (Map) getVisit();
visit.put(USER_KEY, user);
// After logging in, go to the MyLibrary page, unless otherwise specified
ICallback callback = getCallback();
if (callback == null) {
cycle.activate("Home");
}
else {
callback.performCallback(cycle);
}
IEngine engine = getEngine();
Cookie cookie = new Cookie(COOKIE_NAME, username);
cookie.setPath(engine.getServletPath());
cookie.setMaxAge(ONE_WEEK);
// Record the user's username in a cookie
cycle.getRequestContext().addCookie(cookie);
engine.forgetPage(getPageName());
}
public void pageBeginRender(PageEvent event) {
if (getUsername() == null) {
setUsername(getRequestCycle().getRequestContext().getCookieValue(COOKIE_NAME));
}
}
}</programlisting>
</section>
<section id="tapestry-4-style-di">
<title>Dependency Injecting Spring Beans into Tapestry pages -
Tapestry 4.x style</title>
<para>Effecting the dependency injection of Spring-managed beans into
Tapestry pages in Tapestry version 4.x is <emphasis>so</emphasis> much
simpler. All that is needed is a single <ulink
url="http://howardlewisship.com/tapestry-javaforge/tapestry-spring/">add-on
library</ulink>, and some (small) amount of (essentially boilerplate)
configuration. Simply package and deploy this library with the (any of
the) other libraries required by your web application (typically in
<literal>WEB-INF/lib</literal>).</para>
<para>You will then need to create and expose the Spring container
using the <link linkend="web-integration-common">method detailed
previously</link>. You can then inject Spring-managed beans into
Tapestry very easily; if we are using Java 5, consider the
<classname>Login</classname> page from above: we simply need to
annotate the appropriate getter methods in order to dependency inject
the Spring-managed <literal>userService</literal> and
<literal>authenticationService</literal> objects (lots of the class
definition has been elided for clarity).</para>
<programlisting language="java">package com.whatever.web.xportal.pages;
public abstract class Login extends BasePage implements ErrorProperty, PageRenderListener {
@InjectObject("spring:userService")
public abstract UserService getUserService();
@InjectObject("spring:authenticationService")
public abstract AuthenticationService getAuthenticationService();
}</programlisting>
<para>We are almost done. All that remains is the HiveMind
configuration that exposes the Spring container stored in the
<interfacename>ServletContext</interfacename> as a HiveMind service;
for example:</para>
<programlisting language="xml">&lt;?xml version="1.0"?&gt;
&lt;module id="com.javaforge.tapestry.spring" version="0.1.1"&gt;
&lt;service-point id="SpringApplicationInitializer"
interface="org.apache.tapestry.services.ApplicationInitializer"
visibility="private"&gt;
&lt;invoke-factory&gt;
&lt;construct class="com.javaforge.tapestry.spring.SpringApplicationInitializer"&gt;
&lt;set-object property="beanFactoryHolder"
value="service:hivemind.lib.DefaultSpringBeanFactoryHolder" /&gt;
&lt;/construct&gt;
&lt;/invoke-factory&gt;
&lt;/service-point&gt;
&lt;!-- Hook the Spring setup into the overall application initialization. --&gt;
&lt;contribution
configuration-id="tapestry.init.ApplicationInitializers"&gt;
&lt;command id="spring-context"
object="service:SpringApplicationInitializer" /&gt;
&lt;/contribution&gt;
&lt;/module&gt;</programlisting>
<para>If you are using Java 5 (and thus have access to annotations),
then that really is it.</para>
<para>If you are not using Java 5, then one obviously doesn't annotate
one's Tapestry page classes with annotations; instead, one simply uses
good old fashioned XML to declare the dependency injection; for
example, inside the <literal>.page</literal> or
<literal>.jwc</literal> file for the <classname>Login</classname> page
(or component):</para>
<programlisting language="xml">&lt;inject property="userService" object="spring:userService"/&gt;
&lt;inject property="authenticationService" object="spring:authenticationService"/&gt;</programlisting>
<para>In this example, we've managed to allow service beans defined in a
Spring container to be provided to the Tapestry page in a declarative
fashion. The page class does not know where the service implementations
are coming from, and in fact it is easy to slip in another implementation,
for example, during testing. This inversion of control is one of the prime
goals and benefits of the Spring Framework, and we have managed to extend
it throughout the stack in this Tapestry application.</para>
</section>
</section>
</section>
<section id="web-integration-resources">
<title>Further Resources</title>
<para>Find below links to further resources about the various web
frameworks described in this chapter.</para>
<itemizedlist>
<listitem>
<para>The <ulink
url="http://java.sun.com/javaee/javaserverfaces/">JSF</ulink>
homepage</para>
</listitem>
<listitem>
<para>The <ulink url="http://struts.apache.org/">Struts</ulink>
homepage</para>
</listitem>
<listitem>
<para>The <ulink
url="http://www.opensymphony.com/webwork/">WebWork</ulink>
homepage</para>
</listitem>
<listitem>
<para>The <ulink url="http://tapestry.apache.org/">Tapestry</ulink>
homepage</para>
</listitem>
</itemizedlist>
</section>
</chapter>