This article comes with a sample application that should be
easy to set up for other developers. This application is not meant
to illustrate any aspects introduced in the article, but covers
what the author thought to be common use cases in a - not so -
real-world application.
As this is the author's first Java Enterprise
application, and his first IT-related article, too, likely there
are major flaws, i.e., regarding best practices or
technical understandings. Furthermore, the author's mother tongue
is not English.
Reading the article and studying the sample application is
recommend for software developers that don't want to read the -
even more comprehensive - Spring Reference (although the latter
certainly provides much more accuracy), want to refresh their
knowledge on Spring, look at it from an additional perspective, or
have working working samples that partially extend what's covered
by Spring's own sample applications.
Readers are supposed to have a basic knowledge of the Java
language, Database access, and Servlet / JSP technologies.
This article deals with version 2.5 of the Spring framework.
The upcoming 3.0 version will add new features, but, basically, not
revoke existing ones. Thus, the article will remain valid to a
large degree.
The Spring Framework is an open source framework for the Java
platform (there is a port for Microsoft .NET, too, as well as some
other platforms). It facilitates software development for Java, and
Java EE, and advocates best practices in software
development. It has been introduced in October 2002 and, since
then, evolved constantly.
To run Spring applications, an application server is not
necessary. Java applications of any type can host a Spring
container.
Spring modules can be used independently from another, i.e.,
Spring's JDBC-related APIs can be used without programming the AOP
way.
The Spring framework is comprehensively and sensibly
documented. While there is qualified community support, commercial
support is available, too. Spring's architecture appears to be
thoroughly planned, recent versions of the Spring framework have
been backwards compatible to a very large degree.
In Eclipse for instance, - the m2eclipse plugin being
installed - the project can be imported via File —> Import
—> Maven Projects.
Maven-related tasks like testing and
packaging can be conducted using the aforementioned Eclipse Maven
plugin or, alternatively, by using Maven from the command
line.
On Debian-based Linux distributions, Maven can be installed by
running ...
... as root.
For other operating systems, please consult the installation
instructions at the Maven web site.
The sample application has been successfully tested on the
following application servers (which could also
be referred to as web containers in this
case):
On other application servers, including Apache
Geronimo, IBM WebSphere Community Edition, JBoss AS GA, and Sun
Glassfish, deployment fails - mostly due to errors related to
ActiveMQ's and OpenEJB default configuration.
In order to use OpenEJB on Tomcat (as well as the
Tomcat-based Springsource tc Server),
$M2_REPO/repository/org/apache/openejb/javaee-api/5.0-2/javaee-api-5.0-2.jar
needs to be copied to $TOMCAT_HOME/lib. Theoretically,
this should not be necessary, however, it was in the author's
testing.
- HSQLDB 1.8
- MySQL 5.0
- Oracle Express 10g
- PostgreSQL 8.3
Edit jdbc.properties to choose your database
implementation, and edit user name and password.
Additional, non-Hibernate-based, DDL indexes,
checks and constraints (see chapter
Additional DDL Commands) have
been implemented for
PostgreSQL, only.
It is therefor recommended to use PostgreSQL as database
backend.
The
Oracle JDBC driver
(
ojdbc14.jar) is
not included in the Maven
pom.xml file (as, at the time of writing, it was
incomplete in the Maven repository). Please download it from
Oracle if planning to use the sample
application with an
Oracle database.
Hibernate's property
hibernate.hbm2ddl.auto by default is set up to
create-drop the database tables on application startup
and shutdown. On other DBMSs than HSQLDB, this does not affect the
creation of the database itself.
In this case, create a database named "article" before running
the application.
Running the Sample Application
- Download and
unzip the project.
- Edit any *.properties files in the
src/main/resources and src/test/resources source
folders.
- If using another DBMS than HSQLDB, create an empty database
named "article".
- As for running the unit tests, see chapter Running the Tests.
- As for packaging and manually deploying the application, create
a WAR file as described in chapter Running the Tests, then deploy the
WAR file to an application server (i.e., copy it into
$TOMCAT_HOME/webapps).
- As for starting the application from within Eclipse,
right-click on the project root and select the Run as —> Run
on Server menu item.
When running in an application server, the web application is
accessible under the context URL "/springarticle" (i.e.,
http://localhost:8080/springarticle. The first
page is a login form. Any user name / password combination from the
src/main/resources/logins.properties file can be
used.
User Roles and Use Cases
In general, the application consists of a number of
layers:
- Security: Authentication and Authorization
- Business operations: An administrator creating vendors and
customers, as well as products, a vendor selecting her products
assortment from the list of available products, a customer
selecting products into a newly created order, submitting the
order, etc.
- A fictitious, remote ERP system: There are a number of remoting
interfaces to vendors' (fictitious) ERP systems
User Roles
The following user roles exist (BTW, these are Java types in
the com.acme.sample.domain package):
A
User has a name and a password, by which she
can login to the application. Additionally, she has been assigned
one or more
authorities (which could be also named
roles) that are used by the application to determine which
parts of the web GUI she may view, and which Java classes and
methods she is allowed to call. See chapter
Spring Security for further
information.
To each
User that does not have the
authority Administrator, a
BusinessUser should be assigned (-
really).
BusinessUsers may have associated
Orders,
Vendors associated
Products,
additionally. See chapter
Domain
Model for a complete diagram.
Use Cases
Use cases are based on a User's role, which can
be ROLE_ADMINISTRATOR, ROLE_CUSTOMER or
ROLE_VENDOR.
All Users
- Login
- Logout
- Change the language of the user interface
Administrators
- Products:
-
- Customers:
-
- Vendors:
-
- Logins:
-
Customers
- Own orders:
-
- List
- Add:
-
- Select vendor
- Re-select vendor
- Select product(s) from a list by editing the desired
quantity
- De-select product(s)
- Finish the order
- On finish, several vendors (not being too realistic) are
notified via several remoting technologies (which vendors are
notified is configurable)
- Export an order as Microsoft Excel document
The following diagram illustrates the "Place Order" use
case:
Vendors
- Assortment:
-
- List
- Select products
- De-select products
- Own orders:
-
- List
- Export an order as Microsoft Excel document
The Spring Container
Generally spoken, the Spring container manages beans.
Beans, in the Spring context, are POJOs (Plain Old Java
Objects), classes of virtually any type and shape, that are
managed by the Spring container. I.e., they do not have to
implement special interfaces, or throw special exceptions.
Managing beans to the container means, controlling beans'
lifecycles and configuring them, particularly, by assigning (plain
old, in a way) dependencies and (
Spring
AOP) intercepting functionality.
Beans can be configured via XML schema-based configuration
files or inline Java 5 annotations (any other format
would be possible, however, these are commonly used).
BeanFactory and ApplicationContext
The root interface for accessing a Spring container is a
BeanFactory, which contains the (overloaded)
Object getBean(..) method.
The ApplicationContext interface (indirectly)
extends BeanFactory, plus other interfaces, which
functionality it thus provides, including:
- The
MessageSource interface provides support for
internationalized, parametrized messages (see chapter Internationalization) by its
overloaded String getMessage(..) methods.
- The
ResourceLoader interface supports loading
configuration files (in practice, XML schema-based configuration
files).
- There is the ApplicationEventPublisher interface, with its
publishEvent(ApplicationEvent) method, that notifys
listeners of events regarding application, HTTP request and
session, and security state changes. Beans can also publish custom
ApplicationEvents.
- A number of interfaces that provide extended access to an
application context's beans.
There may be several ApplicationContexts per classloader, i.e., in a web
application, one for the complete application, and others for
single servlets. Application contexts may inherit from parent
contexts, servlets' contexts do inherit from the 'global'
context.
Container Instantiation
Other than, i.e., Java EE, a Spring container is not tied to a
parent container. It can be run in a web or JEE application, Java
applet, Swing application, or testing application.
ApplicationContext implementations can be instantiated
directly, or indirectly (i.e., in a web application or JUnit
test).
Spring provides a number of ApplicationContext implementations (in the
org.apache.xbean.spring.context package), including
the commonly used ClassPathXmlApplicationContext.
Manual Instantiation
Instantiating a ClassPathXmlApplicationContext manually is
straightforward:
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "daos.xml"});
MyBean myBean = (MyBean) context.getBean("myBean");
To the constructor, one or more XML beans configuration files
are passed. In the code snippet above, the files are relative to
the current directory. Alternatively, the prefix
classpath: causes them to be searched on the
classpath, and classpath*: also considers linked
JARs.
Implicit Instantiation within the Spring TestContext
Framework
Test classes within the Spring TestContext framework may
specify XML configuration files by setting the @ContextConfiguration annotation:
@ContextConfiguration(locations = {
"classpath:applicationContext-remoting.xml",
"classpath:applicationContext-web.xml"})
public abstract class AbstractTests extends AbstractJUnit4SpringContextTests {}
For further information see chapter
Testing.
Implicit Instantiation within a Web Application
This section refers to Servlet 2.4 containers or
newer. In legacy containers, a slightly modified configuration is
needed.
In
Spring MVC, an
ApplicationContext - by default, an
ClassPathXmlApplicationContext - is implicitly set up
by configuring a
ContextLoaderListener
and/or one or more
DispatcherServlets.
ContextLoaderListener
The ContextLoaderListener is meant to manage
Spring beans that belong to the middle tier and data
access layers. It is configured as follows (in
web.xml):
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext-middleTier.xml
classpath:applicationContext-dataAccess.xml
</param-value>
</context-param>
DispatcherServlet
Next, one or more DispatcherServlets
can be set up. Typically, they will have their own ApplicationContext, each, however, that context
inherits from the ContextLoaderListener's
context (where inherited bean definitions can be overridden), so
the 'global' bean definitions can be used there.
A DispatcherServlet is configured as follows (in
web.xml):
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext-client-frontend.xml
</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
The contextConfigLocation property is optional.
By default, the servlet would load a bean configuration file named
<servlet-name>-servlet.xml (in this sample,
springDispatcher-servlet.xml) in the web application's
WEB-INF folder.
This configuration file may contain bean definitions of
Controllers,
ViewResolvers,
LocaleResolvers,
handler
mappings, etc. (many of which discussed in chapter
Spring MVC), but should not - in terms of
separation of concerns - contain definitions of beans from
the middle tier or data access layer (as mentioned, the latter ones
will be accessible, though).
WebApplicationContext
The default application context is a XmlWebApplicationContext, which supports
theming (Theme getTheme(String) method) and
is aware of the ServletContext it is running in
(ServletContext getServletContext() method).
Dependency Injection
Objects in the Java programming world are instances
of types (classes). Objects may be dependant on
other objects to be functional, i.e., a car should have four tires
(has-a relationship). The most straightforward way to
construct these dependencies is to use the new
statement:
class Car {
Tire[] tires = new Tire[4];
Car() {
tires[0] = new SnowTire();
tires[1] = new SnowTire();
tires[2] = new SnowTire();
tires[3] = new SnowTire();
}
}
However, this leads to tight coupling: Whenever the
type of the Tire is to be changed (i.e., spring comes,
normal tires are to be tested, snow tires are sold out), the
Car's code needs to be changed, and recompiled. This
is impractical.
There are a number of
design patterns to work around
tight coupling, including the
Factory
Pattern, the
Service Locator Pattern, and, to
some extend, the
Decorator
Pattern, loosening the coupling. One can, however, argue
that wherever there is still a
new statement in code,
coupling is still too tight.
Dependency Injection (DI), a form of
Inversion of Control (IoC), comes to the rescue:
The container - that runs the application - injects the
dependencies (from outside), based on the application's
(declarative) configuration.
In Spring, basically, two forms of dependency injection
declaration exist, using Java 5 annotations or external
XML configuration markup (see chapter
Bean Configuration).
Injection Types
Constructor Arguments Injection
In the following code snippet there is a constructor requiring
two arguments to be passed:
class Car {
Tire[] tires;
CigaretteLighter lighter;
Car(Tire[] tires, CigaretteLighter lighter) {
this.tires = tires;
this.lighter = lighter;
}
}
In XML schema-based configuration, injection could be set up
as follows:
<bean id="tire" class="example.SnowTire" />
<util:set id="tires">
<idref local="tire" />
<idref local="tire" />
<idref local="tire" />
<idref local="tire" />
</util:set>
<bean id="lighter" class="example.CigaretteLighter" />
<bean id="car" class="example.Car">
<constructor-arg ref="tires" />
<constructor-arg ref="lighter" />
</bean>
Most details in the above code snippet are not
relevant for this subsection. There is more information in the
chapters
XML
Schema-Based Configuration and
The util Schema. The
util:set declaration will cause Spring to create a
java.util.HashSet instance, which will be
auto-converted to the
Tire[] array.
The object instances passed via the constructor-arg XML elements will be assigned by
their Java type, automatically. Alternatively, the type
can be explicitly set:
<constructor-arg type="int" value="42" />
<constructor-arg type="java.lang.String" value="777" />
Additionally, a (0-based) argument index can be set:
<constructor-arg index="0" value="42" />
<constructor-arg index="1" value="777" />
Instance Field Injection
Setter Injection
Considering the following Java class, ...
public class Car {
CigaretteLighter lighter;
public void setCigaretteLighter(CigaretteLighter lighter) {
this.lighter = lighter;
}
}
... a corresponding XML bean configuration could be:
<bean class="example.Car">
<property name="cigaretteLighter">
<bean class="example.CigaretteLighter" />
</property>
</bean>
In the above sample, an anonymous inner bean is
created. Alternatively, there could be a reference to a bean
declared elsewhere.
Method Arguments Injection
Specific annotations can be used to declare dependencies to be
injected on methods
not being
setter methods, but
having an arbitrary method name as well as possibly multiple
arguments (see chapter
Configuration
Related to Dependency Injection). Method arguments
injection cannot be declared using XML schema-based
configuration.
Autowiring
Autowiring means that the Spring container will try to locate
exactly one matching bean to dependency-inject into applicable
properties (setter methods), each, or a constructor.
In XML schema-based configuration, there is the autowire attribute, i.e.:
<bean class="example.Car" autowire="byType" />
It can take the following values:
no: This is the default value. Autowiring will not
be applied.
byName: Based on the property name of the target
bean (i.e., cigaretteLighter in the code
snippets above) Spring tries to locate a corresponding bean with
the same id or name.
byType: Spring tries to find a corresponding bean
by looking at the type of the target property.
constructor: Spring tries to find a corresponding
bean by looking at the type of the (one) argument to the target
constructor. If no matching bean is found, an exception will be
thrown.
autodetect: Selects byType if a
default constructor is found, else constructor.
In the
byName and
byType
modes, no exception will be thrown if no matching bean is found.
This can be changed by setting
dependency-check="objects".
There is a corresponding default-autowire attribute on the bean's
beans parent element.
The bean's attribute autowire-candidate (with its possible values
default|true|false) specifies whether or not a bean
may be considered when looking for matches. By default, it may.
This setting only applies to autowiring by type, and not
by name.
At the beans parent level, the
default-autowire-candidates attribute can be used to
set one or more bean name patterns for identifying autowire
candidates. The patterns may contain wildcards (*), multiple
patterns are delimited by commas (,).
Bean Scopes
Spring beans are singleton scoped by default.
Singleton beans' lifecycles start when being initialized (by
default, at container startup time) and end when the container
prepares to shutdown. Each call to BeanFactory.getBean(..) and each dependency-injected
bean refers to the same instance of a singleton scoped bean.
Singleton beans are not meant to hold application state beyond
their initial configuration (particularly, dependencies).
Alternatively, a bean's scope can be set to
prototype. In this case a new instance will be created
with every BeanFactory.getBean(..) call
or dependency injection.
In WebApplication contexts the request
and session scopes exist, such a bean instance will be
created for each HTTP request or session.
There is also the globalSession scope. A
globalSession scoped bean instance is shared amongst each
portlet within a HTTP session. If the web application is
not a portlet application, the scope will implicitly
fallback to the session scope.
It is possible to create a custom scope by implementing the
Scope interface and registering the bean with
ConfigurableBeanFactory.registerScope(String,
Scope).
In XML schema-based configuration, scope is an attribute of the bean element. As an annotation, @Scope can be used.
Bean Configuration
Spring Beans typically are either configured by Java 5
annotations, or by XML schema-based configuration (typically,
configuration files).
XML Schema-Based Configuration
The XML schemas, on which configuration is based,
are documented inline, and can be read while editing a
configuration file in commonly used IDEs, including Eclipse and
Spring Tools Suite.
The beans Schema
An empty configuration file starts with the beans element and typically includes
bean elements:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
p:order="0"
p:interceptors-ref="localeChangeInterceptor">
<property name="mappings">
<value>
/adminAddCustomer.htm=adminAddCustomerController
</value>
</property>
</bean>
<bean id="localeChangeInterceptor" class="..." ... />
</beans>
Bean Properties
As with the property attribute in the above
snippet, a bean may have properties
set. The property "mappings" would refer to the bean class'
setMappings(..) setter method.
An analogous shortcut to <bean><property name="mappings"> ...
would be <bean p:mappings="...". This requires the
p namespace to be specified (see xmlns:p in the above snippet).
p:interceptors-ref in the above snippet refers to
another Spring bean.
The "mappings" property in this case is of the Java type
java.util.Properties. Spring will convert the value by
using a matching PropertyEditor, based on
the type of the target property.
In case of the Java type Properties,
the XML element value ist used (which
takes a list of values, separated by whitespace). XML elements that
map to other Java types include list, map, props (similar to
map), and set.
In the following snippet, not a property, but a
constructor argument, is set to a map:
<bean id="supportedLocales" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="en" value="general.languages.english" />
<entry key="de" value="general.languages.german" />
</constructor-arg>
</bean>
Other possible property XML child elements
include bean (for creating an anonymous inner bean,
like an anonymous inner class in Java), ref
(referencing a bean defined elsewhere) and idref
(similar to ref, but an XML parser will validate the
existence of the referenced bean within the same file).
bean Attributes
This subsection refers to the XML attributes an
XML bean element can take.
None of the possible bean attributes is
mandantory.
Firstly, there is the class attribute, which
takes the fully qualified class name. With the parent
attribute, a "superclass" bean can be specified, from which the
bean then will inherit most settings. The parent bean could be
abstract="true" in order to not be instantiated,
directly.
By a bean's id, other
beans can reference it. The name attribute can be used
to add one or more aliases.
As Spring manages the lifecycle of the bean, it can call an
init-method or
destroy-method. Optionally, the bean can be
instantiated by using the given
factory-method of a given
factory-class (see chapter
Factory Pattern).
Autowiring can be
configured by the
autowired,
autowire-candidate, and
primary
attributes.
The depends-on attribute is used when the
currently configured bean needs another bean to be fully configured
before it will be configured itself.
By default, Spring loads all beans on startup. This can be
changed by setting the lazy-init attribute.
By setting the
scope attribute, another scope
than the default scope
singleton can be set (see chapter
Bean Scopes).
The aop Schema
To use the aop namespace, its declaration needs
to be associated with the corresponding XML schema:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
The aop:config, aop:pointcut, aop:aspect,
and aop:advisor can be used to create and
advise AOP aspects.
The context Schema
To use the context namespace, its declaration
needs to be associated with the corresponding XML schema:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
The
context:property-placeholder element
configures a Spring
PropertyPlaceholderConfigurer, which replaces
properties in the shape
${...} by their actual values. See
chapter
DataSource Setup
for an example.
Multiple
PropertyPlaceholderConfigurers (allowing for using
multiple .properties files) within an
ApplicationContext will conflict by default. This can
be resolved by setting a different placeholderPrefix
property for each instance. The default
placeholderPrefix is "${", and could, for instance, be
changed to "$jdbcStuff{".
The context:annotation-config
element causes Spring detect various annotations, including
Spring's @Required and @Autowired, JSR 250's @PostConstruct, @PreDestroy
and @Resource (if available), and JPA's
@PersistenceContext and @PersistenceUnit
(if available).
Detection of Spring's @Transactional annotation, in contrast, is activated
by the tx:annotation-config
element.
The context:component-scan element makes Spring
scan the classpath for annotated components that will be
auto-registered as Spring beans. By default, the Spring-provided
@Component, @Repository, @Service, and @Controller
stereotypes will be detected. By default,
context:component-scan implicitly activates
tx:annotation-config.
The context:component-scan element's
base-package attribute allows for limiting the scan to
the given package contents. The sub-elements context:exclude-filter and context:include-filter allow for further
filtering.
Additional possible elements are omitted in this
subsection.
The util Schema
To use the util namespace, its declaration needs
to be associated with the corresponding XML schema:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
</beans>
The util schema contains convenience utility
shortcuts for defining collection beans, setting constant values,
etc.
In example, the following code snippet ...
<bean id="supportedLocales" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="en" value="general.languages.english"></entry>
<entry key="de" value="general.languages.german"></entry>
</map>
</constructor-arg>
</bean>
... can also be expressed as:
<util:map id="supportedLocales">
<entry key="en" value="general.languages.english"></entry>
<entry key="de" value="general.languages.german"></entry>
</util:map>
There are corresponding util:list,
util:set and util:properties
elements.
The tx Schema
To use the tx namespace, its declaration needs to
be associated with the corresponding XML schema:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
The existence of an tx:annotation-driven element instructs Spring to
create transactional proxies for beans annoted with Spring's
@Transactional and EJB3's
@TransactionAttribute. By default, this requires a
TransactionManager with an id "txManager"
to be set up, this can be changed by setting the transaction-manager attribute.
Using the tx:advice element, classes containing
matching methods can be advised by transactional advices (similar
to annotating them).
<tx:advice id="readOnlyTransactionsAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceLayerGettersPointcut"
expression="execution(* com.acme.sample.businessfacade.impl.*.*(..))" />
<aop:advisor id="readOnlyTransactionsInServiceLayerAdvisor"
advice-ref="readOnlyTransactionsAdvice" pointcut-ref="serviceLayerGettersPointcut" />
</aop:config>
Annotation-based Configuration
Configuration Related to Dependency Injection
In order to cause Spring detect the annotations
mentioned below, one or more
BeanPostProcessors need to be activated by using the
context:component-scan element in XML schema-based
configuration (see chapter
The
context Schema).
The @Required annotation can be set on methods -
reasonably, on setter methods. If the annotated property
has not been populated initially, an exception will be
thrown.
The @Autowired annotation may
be placed on constructors, fields, and methods (not only
setter methods, but also methods of other signatures and
more than one argument). It has an optional property
required, which defaults to true. It also
works on typed collections, like List<Jacket>, which then are populated with all
matching beans.
Autowiring means that matching beans (by
default, determined by type) are automatically injected. If there
is more or less than exactly one autowiring candidate at
initialization time, an exception will be thrown.
Autowiring by qualifier can be accomplished by adding
a @Qualifier annotation, for example:
@Autowired
public void prepareForRide(Saddle saddle, @Qualifier("rainJacket") Jacket jacket) {
...
}
There should be a correspondig XML configuration (see chapter
The beans Schema),
i.e.:
<bean class="example.jackets.RainJacket">
<qualifier value="rainJacket">
</bean>
Custom qualifier annotation interfaces can be
easily written. Additionally, a CustomAutoWireConfigurer may be set up.
The JSR-250 @Resource annotation, which Spring
detects on fields and setter methods, causes Spring to look up a
named bean if the name attribute is
given, otherwise, to look up a matching bean by type.
The ApplicationContext and its
sub-interfaces are globally accessible within a Spring application.
There is not need to create explicit bean definitions for
them.
Configuration Related to Lifecycle Events
In order to cause Spring detect the annotations
mentioned below, one or more
BeanPostProcessors need to be activated by using the
context:component-scan element in XML schema-based
configuration (see chapter
The
context Schema).
No-arg methods (other restrictions do apply) annotated with
@PostConstruct or @PreDestroy will be executed after the bean has been
initially configured (i.e., dependencies have been injected) or
just before the bean will get out of the container's scope.
Configuration Related to Bean Management
In order to cause Spring detect the annotations
mentioned below, one or more
BeanPostProcessors need to be activated by using the
context:component-scan element in XML schema-based
configuration (see chapter
The
context Schema).
As an alternative to
XML Schema-Based
Configuration, where beans to manage are detected after
they are defined using an XML
bean
element, Spring can also detect beans to manage by annotations on
them.
Beans being annotated by
@Component at the type
level will be auto-detected. Specializations of
@Component include
@Service
(i.e., a business facade),
@Repository (a Data Access
Object, DAO), and
@Controller (a controller - this
annotation is usually used in combination with a
@RequestMapping annotation, see chapter
Mapping Controllers to
URLs).
These specializations currently have a limited
meaning, however, this may change in future Spring versions (i.e.,
to be used with exception translation). These annotations
may also serve as pointcut targets.
The annotations mentioned above also have an optional
name attribute.
By using the
@Scope annotation at the type level,
i.e.,
@Scope("prototype"), another scope
than the default
singleton scope can be specified (see
chapter
Bean Scopes).
Configuration Related to Spring AOP
Annotations related to
aspect-orientated programming
include
@Aspect,
@Pointcut,
@Before, and others. See chapter
Spring AOP for further information.
Configuration Related to Transactional Behavior
Configuration Related to the Spring TestContext Framework
Spring Security
This article and its sample application focus on
the
Spring Core Framework rather than
Spring Security, and the author has not
dealed too much with the latter yet, so essential information may
be missing in this chapter. Nonetheless, the sample application
basically needs any form of authentication and authorization to
represent workflows in which users of different roles
participate.
When starting the sample application's web GUI, the user needs
to login. The database has been pre-populated with three user
logins. See login.properties for user name / password
combinations.
Authentication (and Some Bits of Authorization)
Authentication and
authorization can declaratively be applied as simple as
that: First, specify a AuthenticationProvider
implementation and assign a UserDetailsManager implementation to it.
The following code snippet shows a sample for an DaoAuthenticationProvider with a InMemoryDaoImpl:
<authentication-provider>
<user-service>
<user
name="admin1"
password="l33nuxRules"
authorities="ROLE_ADMINISTRATOR" />
<user
name="vendor1"
password="l33nuxRules"
authorities="ROLE_VENDOR" />
</user-service>
</authentication-provider>
Alternatively, a JdbcDaoImpl instance can be
assigned to a DaoAuthenticationProvider:
<authentication-provider>
<jdbc-user-service
data-source-ref="dataSource"
authorities-by-username-query="SELECT username,authority
FROM authorities WHERE username = ?"
users-by-username-query="SELECT username,password,enabled
FROM users WHERE username = ?" />
</authentication-provider>
It could be configured as follows:
<http auto-config="true">
<intercept-url
pattern="/**"
access="ROLE_CUSTOMER,ROLE_VENDOR,ROLE_ADMINISTRATOR" />
<intercept-url
pattern="/admin/**"
access="ROLE_ADMINISTRATOR" />
</http>
In the above code snippet, auto-config="true" "automatically registers a login
form, BASIC authentication, anonymous authentication, logout
services, remember-me and servlet-api-integration" (quoted from the
tag attribute's inline documentation). This means, you don't even
have to write a login form!
In this article's sample application a custom login form is
used, though (/login.jsp). - A minimal JSP login form
could be of the shape:
<c:if test="${not empty param.login_error}">
<c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
<br />
</c:if>
<form action="j_spring_security_check">
User name: <input type='text' name='j_username'
value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>' />
<br />
Password: <input type='password' name='j_password' />
<%--
<br />
"Remember me" is optional
<input type="checkbox" name="_spring_security_remember_me" />
Remember password
--%>
<input value="Login" type="submit">
</form>
The form POSTs to the "URL" j_spring_security_check, and the user name and
password fields are named j_username and
j_password, respectively.
Authorization
In the sample application, authorization is applied both at
the JSP and Java source level.
In /WEB-INF/views/includes/navi.jsp, the Spring
Security tag library is used:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authorize ifAllGranted="ROLE_ADMINISTRATOR">
<a href="<c:url value='/app/adminListProducts.htm'/>">Products</a>
</sec:authorize>
In the Java source, the controllers
as well as the business facade interfaces are secured by
annotating types or methods by either @javax.annotation.security.RolesAllowed(String[]) or
@org.springframework.security.annotation.Secured(String[])
(where the role names or authorities are passed as
arguments) can be used.
For this to work, Spring needs to be configured to interprete
these annotations:
<global-method-security jsr250-annotations="enabled"
secured-annotations="enabled" />
Spring AOP
Aspect Orientated Programming (AOP)
addresses cross-cutting concerns, which are pieces of code
(or logic) that typically are not directly part of the business
logic and may occur over and over again, at several places within
the business logic code.
Such cross-cutting concerns can be logging,
performance monitoring, tracing, transaction management, custom
security checks, exception handling, caching, and others.
Similar to applying the
Decorator Pattern, with AOP,
additional behavior can be added to an existing class, where a
proxy wraps that class and delegates calls to it. On the other
hand, cross-cutting code is isolated from the application
code.
AOP Terminology
An aspect is a concern that typically
cross-cuts over multiple objects, like logging or transaction
management.
A join point is a point of code execution in
the target object. In Spring AOP, this is always the execution of a
method.
An advice is a chunk of code that gets
executed at a specific join point - before, before and
after (around), or after execution of the target
object's code.
A pointcut is a combination of a class filter
and method matcher, matching one or more join points.
Target objects, or advised,
are those objects to which one or more aspects are applied. These
objects will be proxied in Spring AOP.
An AOP proxy wraps a target object, delegates
to it, and executes applicable advices.
An introduction adds code - an interface and
its implementation - to a target object; the object will implement
additional methods.
Advices
An aspect is a concern that typically cross-cuts over multiple
objects, like logging or transaction management.
Declaring or Implementing an Advice
There are three ways to implement an advice in a Spring
application:
- Using the Spring
aop namespace in an XML
configuration file to be passed to a Spring ApplicationContext
- Using AspectJ annotations in Java code
- Implementing specific Spring (and, exceptionally, AOP Alliance)
interfaces
Declaring an Advice in XML configuration
In a configuration file that is passed to Spring's
ApplicationContext, i.e., an around advice
might be declared as follows:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
...
<aop:config>
<aop:aspect ref="someBean">
<aop:around method="doProfiling" pointcut="execution(* *(..))" />
</aop:aspect>
</aop:config>
...
</beans>
Declaring an Advice Using AspectJ Annotations
In Java code, in an aspect, AspectJ annotations - in
package org.aspectj.lang.annotation - can be used to
declare an advice:
@Aspect
public class ProfilingAdvice {
@Around("execution(* *(..))")
public void doProfiling() {
...
}
}
For AspectJ annotations to be picked up by Spring, an
application configuration file must contain:
<aop:aspectj-autoproxy />
Implementing an Advice by Implementing a Specific
Interface
In package org.springframework.aop, Spring
provides several interfaces, i.e., MethodBeforeAdvice, which all extend
org.aopalliance.aop.Advice. Somewhat surprisingly, no
interface for around advices exists in the org.springframework.aop namespace. For around
advices the interface org.aopalliance.intercept.MethodInterceptor would be
implemented, i.e.:
public class ProfilingAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation method) throws Throwable {
...
}
}
While this approach is valid, implementing any of
both alternatives is simpler. Thus, like the Spring Reference
Documentation, this article will focus on the latter ones. Anyways,
there is more information available in the article
Spring: A Quick Journey Through Spring
AOP.
Types of Advices
Before Advice
A before advice gets executed before a join point. It
may interrupt the execution flow by throwing a
Throwable. It can access the target object itself, the
target object's method currently being called, and the method
arguments:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
The equivalent AspectJ annotation is @Before, the equivalent XML tag aop:before.
After Returning Advice
An after returning advice gets executed after a
method successfully returned.
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
The equivalent AspectJ annotation is @AfterReturning, the equivalent XML tag
aop:after-returning.
After Throwing Advice
An after throwing advice gets executed after a method
has thrown an exception.
public interface ThrowsAdvice extends AfterAdvice {
}
Implementing classes must implements methods of the
shape:
void afterThrowing([Method, args, target], ThrowableSubclass);
If the advice throws an exception itself, that will override
the target object's original exception. If the advice's exception
is not a RuntimeException but a checked
Exception, it mandantorily needs to be compatible with
the exception the target method declares in its throws clause.
The equivalent AspectJ annotation is @AfterThrowing, the equivalent XML tag
aop:after-throwing.
After Advice
An after advice gets executed after a method
returned, whether an exception had been thrown or not.
public interface AfterAdvice extends Advice {
}
From the reference documentation, it's not quite
clear how to implement valid methods here.
The equivalent AspectJ annotation is @After, the equivalent XML tag aop:after.
Around Advice
An around advice gets executed before and /
or after the target method is executed.
public interface org.aopalliance.intercept.MethodInterceptor extends org.aopalliance.intercept.Interceptor {
Object invoke(org.aopalliance.intercept.MethodInvocation arg0) throws Throwable;
}
The equivalent AspectJ annotation is @Around, the equivalent XML tag aop:around.
When annotating with @Around or using the
aop:around XML tag, the depicted method can take a
ProceedingJoinPoint as a method argument,
which can be used to invoke the target method. Its Object proceed(..) method may take custom arguments
to be passed to the target method. That way further execution can
be manipulated. Also, the target method's return value - if any -
can be modified or replaced.
In this article's sample application's I18nAdvice, an around advice is used to add commonly
used data to each ModelAndView:
@Aspect
public class I18nAdvice {
...
@Around("execution(org.springframework.web.servlet.ModelAndView *.*(..))")
public ModelAndView addSupportedLocales(ProceedingJoinPoint joinPoint)
throws Throwable {
ModelAndView mav = (ModelAndView) joinPoint.proceed();
mav.addObject("supportedLocales", supportedLocales);
return mav;
}
}
Pointcuts
A pointcut is a combination of a class filter and method
matcher, matching one or more join points.
Pointcut Expressions
Pointcut expressions consist of a designator and
arguments passed to the former.
The designator most often used is execution, which matches method execution
join points.
Other designators do filter for types, arguments, and
annotations.
Pointcut expressions can be combined by using the &&,
|| and ! operators.
The format of an execution expression is:
execution(
modifiers-pattern?
ret-type-pattern
declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?
)
In example, the pattern ...
execution(* *(..))
... matches any method.
execution(org.springframework.web.servlet.ModelAndView *.*(..))
... matches any method that returns a ModelAndView instance.
Declaring or Implementing a Pointcut
There are three ways to implement a pointcut in a Spring
application:
- Using the Spring aop namespace in an XML configuration file
passed to Spring's ApplicationContext
- Using AspectJ annotations in Java code
- Using Spring's
Pointcut implementations
Declaring a Pointcut in XML Configuration
In a configuration file that is passed to Spring's
ApplicationContext, i.e., a pointcut might be declared
as follows:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
...
<aop:config>
<aop:aspect ref="someBean">
<aop:around method="doProfiling" pointcut="execution(* *(..))" />
</aop:aspect>
</aop:config>
...
</beans>
Alternatively, the pointcut can be defined in its own XML
element:
<aop:config>
<aop:aspect ref="someBean">
<aop:pointcut expression="execution(* *(..))" id="myPointcut"/>
<aop:around method="doProfiling" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
Declaring a Pointcut Using AspectJ Annotations
AspectJ
pointcut annotations (see chapter
Types of Advices) typically take a
pointcut expression as an argument, so there is an implicit
pointcut declaration.
Additionally, there is the @Pointcut
annotation, which is - obviously - not an advice, but can
be referred by one, i.e.,
@Pointcut("execution(* *(..))")
public void foo() {}
@Before("foo()")
public void bar() {
...
}
Pointcut Implementations
In subpackages of org.springframework.aop, Spring provides a number of
Pointcut implementations:
The AspectJExpressionPointcut evaluates AspectJ
expressions - mainly execution
expressions, which, however, can be combined with others.
There are two contrasting, abstract, *MethodMatcherPointcuts: StaticMethodMatcherPointcut (with a
JdkRegexpMethodPointcut implementation, that makes use
of regular expressions) and DynamicMethodMatcherPointcut. Implementations of the
latter will finally execute a method of the following signature
(from the MethodMatcher interface inherited):
boolean matches(Method method, Class targetClass, Object[] args);
This allows for evaluating method argument values at
runtime.
An AnnotationMatchingPointcut inspects Java 5
annotations on the type or method level.
A ComposablePointcut allows for combining
Pointcuts, or their ClassFilters or
MethodMatchers, respectively. Applying
the union(..) method means, both pointcuts need to
match, applying the intersection(..)
method, one of both must match. In practice, it might be easier to
use AspectJ expressions.
A Pointcut, basically, is made up of
a ClassFilter and a MethodMatcher.
Finally, there is a TruePointcut that is always
matching (it is used to control "temporary" - like abstract -
program flow in other Pointcut
implementations).
Advisors
An advisor (Advisor) is an aspect that refers to
a single advice. A pointcut advisor (PointcutAdvisor)
- with its implementation DefaultPointcutAdvisor - extends the advisor by
additionally referring to a pointcut, linking advice and pointcut.
There are some other types of advisors, not discussed at this
place.
In XML configuration, an advisor can be set up as
follows:
<!--
Add read-only transactions to all get* methods in the service layer.
—>
<tx:advice id="readOnlyTransactionsAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceLayerGettersPointcut"
expression="execution(* com.acme.sample.businessfacade.impl.*.*(..))" />
<aop:advisor id="readOnlyTransactionsInServiceLayerAdvisor"
advice-ref="readOnlyTransactionsAdvice" pointcut-ref="serviceLayerGettersPointcut" />
</aop:config>
AOP and Dependency Injection
@Aspect
public class I18nAdvice {
...
@Around("execution(org.springframework.web.servlet.ModelAndView *.*(..))")
public ModelAndView addSupportedLocales(ProceedingJoinPoint joinPoint)
throws Throwable {
...
}
}
As a result, actually, an AOP proxy
(a JdkDynamicAopProxy) is used whenever
the advised class is needed. This AOP proxy
cannot be cast to the advised class, AdminAddCustomerController. (A ClassCastException will be thrown.)
The reason is, the proxy does not extend the advised
class, but delegates to it.
In contrast, if an interface (AdminAddCustomer) can be extracted from the advised
class (AdminAddCustomerController), the
JdkDynamicProxy can be cast to
that interface (down-cast from Controller
which the proxy implements):
public class AdminAddCustomerControllerTests extends AbstractTests {
@Autowired
@Resource(name="adminAddCustomerController")
private AdminAddCustomer controller;
...
In web applications, alternatively to using an
AOP advice, a Spring servlet HandlerInterceptor could
be used to postHandle(..) a
ModelAndView.
AOP Use Cases in the Sample Application
Add Transactional Behavior to Certain Methods
Chapter
tx:advice XML
Configuration describes how to combine an advice, a
pointcut, and an advisor, in order to add transactional behavior to
certain method executions.
Under the hood, Spring uses AOP with all of its
transaction management, i.e., when methods are marked @Transactional.
Add a List of Available Languages To Each MVC View
For customized GUI internationalization, the end user needs to
choose a language from a list. This list will be displayed in any
MVC view, and thus, passed to each ModelAndView instance.
The advice I18nAdvice is defined as:
@Aspect
public class I18nAdvice {
private Map<String, String> supportedLocales;
@Resource(name="supportedLocales")
public void setSupportedLocales(Map<String, String> locales) {
this.supportedLocales = locales;
}
@Around("execution(org.springframework.web.servlet.ModelAndView *.*(..))")
public ModelAndView addSupportedLocales(ProceedingJoinPoint joinPoint)
throws Throwable {
ModelAndView mav = (ModelAndView) joinPoint.proceed();
mav.addObject("supportedLocales", supportedLocales);
return mav;
}
}
Alternatively to using an AOP advice, a Spring
servlet HandlerInterceptor could be used to
postHandle(..) a ModelAndView.
Notify Vendor on Order Placement
Several vendors in the sample application wish to be notified
whenever a customer places an order that concerns them. Other
vendors would wish, too, however, they don't have any notification
gateway (but snail mail). ;-) Thus, notifications are only
transmitted to a - configurable - list of vendors.
This use case is implemented by using Spring's AOP API,
particularly, DefaultPointcutAdvisor,
Pointcut and MethodMatcher, and AfterReturningAdvice.
The following image shows the related bean configuration in
appContext-businessFacadeInterceptors.xml:
Starting at the image's right bottom, the configuration
defines a list of vendors (by their names) that need to be
notified:
<util:list id="vendorsToNotifyList">
<value>vendor1</value>
<value>John Doe</value>
<value>Big Bill Broonzey</value>
</util:list>
Based on that list that is passsed to the pointcut ...
<bean id="vendorNotifierPointcut"
class="com.acme.sample.businessfacade.aop.VendorNotifierPointcutImpl"
p:vendorsToNotifyNames-ref="vendorsToNotifyList"></bean>
... the sample application's self-implemented DynamicMethodMatcherPointcut, VendorNotifierPointcutImpl, decides, based on runtime
arguments, whether boolean matches(.., Object[] args)
returns true:
public boolean matches(Method method, Class targetClass, Object[] args) {
boolean matches = matches(method, targetClass);
if (matches) {
matches = false;
Order order = (Order) args[0];
String vendorName = order.getVendor().getName();
for (String vendorToNotifyName : vendorsToNotifyNames) {
if (vendorName.equals(vendorToNotifyName)) {
matches = true;
break;
}
}
}
return matches;
}
If true gets returned, an
AfterReturning advice, VendorNotifierAdviceImpl will be called, that
notifies the vendor via several remoting services, including
RMI:
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
Order order = (Order) args[0];
vendorRmiConnector.notifyAboutNewOrder(order);
}
This use case is tested by InsertOrderTest's insertOrder() method.
Data Persistence
Spring supports a number of data access technologies,
including JDBC (Java Database Connectitiy) and
several object-relational mapping (ORM) tools:
Hibernate, JDO (Java Data Objects),
Oracle TopLink, iBATIS SQL Maps and JPA
(Java Persistence API).
In the sample application, JDBC and (mostly)
Hibernate is used, and only these are covered by this
article.
DataSource Setup
A javax.sql.DataSource implementation can be set
up in the application context's XML configuration files. Here, a
BasicDataSource, provided by the
Apache Commons project, is used:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" />
</beans>
At the end of the data source's lifecycle, Spring will call
its method specified by destroy-method to
close the database connection properly.
It is good practice to externalize the database connection
properties into a separate file to support an
administrator or deployer focus on the settings
she actually needs to edit: There are placeholders like
${jdbc.username}, which are to be found in a file
classpath:jdbc.properties. This file
might have the following content:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:file:testdb
jdbc.username=sa
jdbc.password=
For the placeholder replacement to work, a PropertyPlaceholderBeanDefinitionParser needs to be
set up to find the placeholder definitions file:
<context:property-placeholder
location="classpath:jdbc.properties" />.
Alternatively to using a BasicDataSource, one could obtain a data source from
JNDI as follows:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/DefaultDS"/>
</bean>
The Sample Application's Data Access Layer
Domain Model
The sample application's domain objects -
entities - can be located in the com.acme.sample.domain package.
Users need to login into the
application. Upon successful authentication they are associated
with one or more Authority, which
has a role ROLE_ADMINISTRATOR, ROLE_CUSTOMER or ROLE_VENDOR.
While
Users that have the
Role ROLE_ADMINISTRATOR are
able to edit the shop's
meta data (add, edit, delete
Users,
Customers,
Vendors and
Products), the
application's
business use cases are tailored to the
BusinessUsers, which are
specified as
Customers and
Vendors. See chapter
User
Roles for more information.
Both Customers and Vendors - as they are BusinessUsers - may have Orders associated. Furthermore, a Vendor may have an assortment of Products.
An Order has a Vendor and a
Customer, and it has one or more
LineItems, which consists of a number
(count) of a Product.
The following table shows the
entities's relations:
| Entity |
Relation Target Entity |
Relation Type |
User |
Authority |
one-to-many |
BusinessUser |
one-to-one (target may be null) |
BusinessUser (Customer
or Vendor) |
Order |
one-to-many |
Vendor |
Product |
one-to-many |
Order |
Customer |
one-to-one |
Vendor |
one-to-one |
LineItem |
one-to-many |
LineItem |
Product |
one-to-one |
These are has-a relations, refering to bean
properties.
There are also a number of is-a relations, refering
to inheritance: All entities have an id, so they inherit from the AbstractIdentifiable class, which defines a public
getter and setter method for that field. Most entities also have a
name, so they extend the class
AbstractNameable. Both Customers and
Vendors can have associated Orders, so they inherit from the BusinessUser class.
The following image shows the corresponding database
diagram:
Data Access Layer
The sample application's data access objects
(DAOs) are defined via Java interfaces (in the
com.acme.sample.dao package). The business
objects that are using them are unaware of the actual
data access implementations, which may be replaced without
affecting the business objects' functioning at all.
For each domain object (entity), one
data access object exists. All of them extend the
interface GenericDAO<T extends
AbstractIdentifyable> that defines common data access
operations:
public interface GenericDAO<T extends AbstractIdentifyable> {
T get(int id);
List<T> get(String filterCriteria);
List get(String query, Object[] params);
List<T> get();
void insert(T object);
void update(T object);
void remove(T object);
}
Data access operations that cannot be shared with other
entities, are defined separately:
public interface OrderDAO extends GenericDAO<Order> {
/**
* Return a list of orders for the given customer.
*/
List<Order> get(Customer customer);
/**
* Return a list of orders for the given vendor.
*/
List<Order> get(Vendor vendor);
}
ORM with Hibernate
Object-Relational Mapping (ORM,
O/RM or O/R mapping) means converting database
entities to objects that can be accessed programmatically.
SessionFactory Setup
The Hibernate SessionFactory is set up as
follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
p:dataSource-ref="dataSource"
p:mappingResources="article.hbm.xml">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}
</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}
</prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="merge">
<bean
class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener" />
</entry>
</map>
</property>
</bean>
</beans>
The LocalSessionFactoryBean
that creates an org.hibernate.SessionFactory basically is configured
by setting a reference to a DataSource
and specifying one or more Hibernate mappingResources.
There are numerous other settings which could be configured,
however, the aforementioned ones might be most common.
It is also mandantory to specify the
hibernate.dialect (i.e., to a
org.hibernate.dialect.PostgreSQLDialect)
to enable Hibernate build matching SQL statements for the database
used.
In this case, the placeholders - already discussed in chapter
DataSource Setup - again
are read from the
jdbc.properties file.
The IdTransferringMergeEventListener
is needed in order to transfer the ID of entities newly saved by
Hibernate - typically, the ID will be auto-generated by the
database on INSERT - back to the entity object. This
is not standard behavior with Hibernate Session's Object merge(Object) ..
method.
Hibernate Mappings
ORM mappings - as with Hibernate - associate
object data (persistent entities) to relational data (a
relational database).
This article covers XML mappings only, and only
those needed to construct the sample application. For more
information please consult the
Hibernate Reference Documentation.
Basically, a mapping file consists of the classes to be
mapped:
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.acme.sample.domain.LineItem" table="line_items" />
<class name="com.acme.sample.domain.Order" table="orders" />
...
</hibernate-mapping>
The table attribute is optional, and defaults to
the class name.
id Element
Every mapped class needs to have an ID - to be mapped to a
table's primary key. When a record gets inserted, the
database will usually generate the primary key value,
automatically. Different strategies exist for primary key
generation, however, generator class="native" should
work with any database. id's name
attribute refers to the Java entity's property name (the
column refers to the table column name, is optional,
and defaults to the Java property name):
<hibernate-mapping>
<class name="com.acme.sample.domain.LineItem" table="line_items">
<id name="id" column="id">
<generator class="native" />
</id>
...
</class>
...
</hibernate-mapping>
property Element
Plain properties are mapped by using the property element:
<hibernate-mapping>
<class name="com.acme.sample.domain.User" table="users">
...
<property name="name" column="user_name" length="50"
not-null="true" unique="true" />
</class>
...
</hibernate-mapping>
The field's data type will be determined by Java
reflection, so the attribute type is optional. Field restrictions can be specified
(i.e., length and not-null). unique="true" will add a unique constraint,
and index="index_name" will build an
index.
Relations
Often, entity properties refer to other entities (Java
classes, respectively). These has-a properties can be
one-to-one, one-to-many, many-to-one,
and many-to-many relations. Collections can
be mapped, Lists, Sets, and
Maps.
Relations can be unidirectional or
bidirectional.
One-to-one Relation
The sample application does not have a use case for a
one-to-one relation. However, expressed in Java, an
example could be:
class Person {
Address address;
Address getAddress { return address; }
setAddress(Address address) { this.address = address; }
}
Matching Hibernate mappings would be:
<class name="com.acme.sample.domain.Address">
<id name="id">
<generator class="native" />
</id>
<property name="street" />
<property name="city" />
</class>
<class name="com.acme.sample.domain.Person">
<id name="id">
<generator class="foreign">
<param name="property">address</param>
</generator>
</id>
<property name="name" />
<one-to-one name="address" constrained="true" />
</class>
The address mapping is straight. The person
mapping specifies an id generator that will synchronize
the ids of both classes on SQL INSERTs. There is
a one-to-one relation to the address entity which
is constrained (expressing a foreign
key). By default, the id fields will be used for the
relation.
To make this relation bidirectional, one would add an
additional one-to-one relation to the
address mapping.
Alternatively to a one-to-one mapping, a
unique many-to-one mapping could
be used:
<class name="com.acme.sample.domain.Person">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<many-to-one name="address" column="person_id" unique="true" />
</class>
This would add a field person_id to the
person table, pointing via a foreign key to
the address table's id field, plus create
a unique constraint on the person_id field.
Optional, Bidirectional, One-to-one Relation
In the sample application there is an
optional, bidirectional, one-to-one
relation between a (Spring Security) User and a
BusinessUser (Customer or
Vendor):
To a User (having a login name and password -
authentication - as well as a set of privileges to access parts of
the application - authorization), a Customer or
Vendor can be assigned (for example, they
can see their own orders). On the other hand, a User with the role Administrator
(ROLE_ADMINISTRATOR) does not need to be a
BusinessUser.
Such a constraint can be expressed by a
many-to-one (on table
business_users), plus a
one-to-one (on table
users) relation in
Hibernate.
However, as stated in a
Bug Report, the
many-to-one relation still allows more than
BusinessUser be assigned to one
User.
A unique constraint would eliminate duplicates in
table business_users's column user_id,
nevertheless, many-to-one name="user" class="...User"
unique="true" in the Hibernate configuration does not create
one. Even if it would, Hibernate would probably not remove
duplicates on updates and inserts, but throw some constraint
violation exception.
As another issue, in practice, the relation is not
bidirectional: Updating the User by setting
setBusinessUser(BusinessUser) does not
reflect its changes on the database.
After all, this article's author might be
strongly mistaken. Further investigation would fall out of this
article's scope, anyways.
Collection Mappings
In Java code, one-to-many, many-to-one and
many-to-many relations are expressed as arrays,
Lists, Sets and Maps. Returned from a Hibernate Session, they behave like HashMap, HashSet, TreeMap,
TreeSet or ArrayList,
depending upon the interface type. Corresponding mapping elements
are array, primitive-array
(both for arrays), list, bag
(both for Lists), set and
map.
One-to-many Relations
A property of type Set, i.e., ...
class User {
private Set<Authority> authorities;
public Set<Authority> getAuthorities() { return authorities }
public void setAuthorities(Set<Authority> authorities {
this.authorities = authorities;
}
}
... is mapped as follows:
<class name="com.acme.sample.domain.User" table="users">
...
<set name="authorities" table="authorities">
<key column="user_id" />
<one-to-many class="com.acme.sample.domain.Authority" />
</set>
...
</class>
The Java property is mapped to the table authorities that gets an additional column
user_id and a foreign key that points from
authorities.user_id back to the
primary key users.id.
Many-to-one Relations
A many-to-one relation looks up one or multiple
persistent entities of a given type, i.e.:
<class name="com.acme.sample.domain.LineItem" table="line_items">
...
<many-to-one name="product" column="product_id"
class="com.acme.sample.domain.Product" />
...
</class>
The many-to-one element on the
LineItem's product property
makes Hibernate create a foreign key (from line_items.product_id to products.id).
Many-to-many Relations
A many-to-many relation associates one or more
entities of one type with one ore more entities of another type. An
example follows:
<class name="com.acme.sample.domain.Product" table="products">
...
<set name="vendors" table="vendors_products">
<key column="product_id" />
<many-to-many class="com.acme.sample.domain.Vendor"
column="vendor_id" />
</set>
...
</class>
In this case, vendor entities are assigned
to the current product entity.
An additional database look up table, vendors_products (not being backed by a Java class at
all), is used to hold the various possible
vendor_id/product_id combinations. On that table, a
composite primary key (vendor_id/product_id) and a foreign key
(vendors_products.product_id, referencing
products.id) are automatically generated by
Hibernate.
The Vendor class mapping defines a similar
many-to-many relation, vendors "having"
products.
Cascading Operations
Data access operations, including insert,
update, and delete, may be cascaded to
associated entities.
For example, there is an Order
entity that is associated with a Set<LineItem>. When this order object gets
inserted (persisted for the first time), its associated
lineitems should be inserted, automatically.
(Alternatively, the lineitems would need to be inserted by code,
after the order had been inserted.)
When the order gets deleted, this operation should
cascade on the lineitems, automatically. (Alternatively,
the lineitems would need to be removed manually before the order
can be removed.)
The cascade attribute can be
specified on the association mappings (i.e., on set, list, and map). Its
default value is none.
Possible values (
cascade types) of the
cascade attribute are:
none: Default. No cascading
save-update: Cascade operations on Hibernate's
Session.save(..) and Session.update() methods.
delete: Cascade operations on Hibernate's
Session.delete(..)
delete-orphan: Remove associated objects from the
database if they have been removed from the parent object (i.e., in
a Java collection).
all: Includes save-update and delete, but
not delete-orphan.
all-delete-orphan: Includes all other cascade
types (but none, of course).
Furthermore, in practice, behaviour will differ
(or even fail) amongst different databases. In the sample
application, in file
article.hbm.xml, the
customer mapping's association with the customer's
orders is set to
cascade="all-delete-orphan".
However, deletion of a persisted
Customer
- for example, in
CustomerDaoTest.testRemove(), on the call
customerDAO.remove(customer) - will fail with MySQL
and HSQLDB, but work with PostgreSQL and Oracle Express:
MySQL and HSQLDB:
Hibernate: update orders set customer_id=null where customer_id=?
PostgreSQL and Oracle Express:
Hibernate: update line_items set order_id=null where order_id=?
Hibernate: delete from line_items where id=?
Hibernate: delete from orders where id=?
Hibernate: delete from business_users where id=?
Nevertheless, investigating in this issue is beyond the article's
scope. Probably, the author is mistaken after all.
Additional DDL Commands
For entity relations - one-to-one,
one-to-many and many-to-one relations - Hibernate
will generate foreign keys, automatically. On single
fields, indexes and unique constraints can be
added manually by setting the index and unique attribute, respectively, of a
property mapping.
To ensure data integrity, however, additional rules are needed
by the sample application, i.e.:
- In table
business_users an index on
user_type would speed up read access, however, this
field is represented by a Hibernate discriminator
element, which, by syntax, does not allow for setting an
index.
- In table
line_items a unique constraint
on the fields order_id and product_id would grant that each line item is
unique.
- In table
line_items and products,
respectively, a check constraint would force the product
count and price,
respectively, to be > 0.
Additional DDL (
Data Definition Language) statements can
be specified through the
database-object
element in Hibernate mappings files, i.e.:
<hibernate-mapping>
...
<database-object>
<create>
ALTER TABLE products
ADD CONSTRAINT products_check_price_gt_0
CHECK (price > 0::double precision);
</create>
<drop>
ALTER TABLE products
DROP CONSTRAINT products_check_price_gt_0;
</drop>
<dialect-scope name="org.hibernate.dialect.PostgreSQLDialect" />
</database-object>
</hibernate-mapping>
The dialect-scope element is optional.
In the sample application, these additional DDL
definitions are implemented for the PostgreSQL database only.
Hibernate ORM and Inheritance Mapping
Customers and Vendors
share most of their properties. With a User login,
either a Customer or Vendor can be associated. - For these reasons, both
classes inherit from the BusinessUser class.
Object-relational mapping tools can use different
strategies to map an inheritance hierarchy to a relational
database:
- A single table per class hierarchy:
Both Customers and Vendors share the same table.
For each property of each subclass, a column must exist, even when
it is not used by other subclasses. Obviously, such columns need to
be nullable.
There is an additional discriminator column (of type
string, char or integer) that indicates
the type of subclass.
Advantages: No complex SQL JOINs, subselects or UNIONs
are needed; this is the simplest and often best-performing
solution.
Disadvantages: Nullable fields prevent from using
NOT NULL constraints. The table is not
normalized.
- A table per concrete class: Both
Customers and Vendors have
their own table. Plus, there is a table holding the properties of
the BusinessUser superclass.
The properties that are inherited will still be defined in each
table.
Advantages: It is possible to add NOT
NULL constraints.
Disadvantages: The ORM container needs to execute SQL
UNIONs or, alternatively, multiple queries. The
database layout is not normalized as it contains redundant
columns.
- A table per subclass: Both
Customers and Vendors have
their own table. Plus, there is a table holding the properties of
the BusinessUser superclass.
The properties that are inherited will only be defined in the
superclass' table.
Advantages: The database schema is normalized.
NULL constraints are possible.
Disadvantages: Performance is slightly degraded compared
to the "single table per class hierarchy" strategy.
The present article's sample application however makes use of
the "single table per class hierarchy" mapping:
<class name="com.acme.sample.domain.BusinessUser" table="BusinessUsers">
<id name="id">
<generator class="native" />
</id>
<property name="name" column="name"
length="255" not-null="true"
index="businessUserName" />
<property name="springSecurityUserID"
column="springSecurityUserID" not-null="true"
index="businessUserSpringSecurityUserID" />
<discriminator column="userType" type="string" length="10" />
<subclass name="com.acme.sample.domain.Vendor"
discriminator-value="VENDOR">
<set name="orders" table="Orders">
<key column="vendorID" />
<one-to-many class="com.acme.sample.domain.Order" />
</set>
<set name="products" table="VendorsProducts">
<key column="vendorID" />
<many-to-many class="com.acme.sample.domain.Product"
column="productID" />
</set>
</subclass>
<subclass name="com.acme.sample.domain.Customer"
discriminator-value="CUSTOMER">
<set name="orders" table="Orders">
<key column="customerID" />
<one-to-many class="com.acme.sample.domain.Order" />
</set>
</subclass>
</class>
Emphasized elements and attributes are discussed below:
id, name, springSecurityUserID: There is not much to
add on the identity field and the other properties. On the fields
name and springSecurityUserID, however, Hibernate will create
an index.
discriminator: This column indicates the type of the
subclass on a per-row basis. There are corresponding
discriminator-value attributes on the
subclass elements.
subclass Vendor: orders:
There is a one-to-many relation between a Vendor and her Orders,
defined by the Orders table's
vendorID field and - implicitly the
primary key is used - the Vendor table's
id field. Hibernate will create an appropriate
foreign key on the Orders table.
products: As many vendors can have the same product
in their assortments, and a vendor's assortment can consist of many
products, this is a many-to-many relation. The
VendorsProducts table acts as a look up table
for vendors' assortments. It is not mapped to a Java class but
defined inline in the Vendor and Product (sub)class mappings. Hibernate will generate
appropriate foreign keys as well as a composite
primary key to that table, automatically.
subclass Customer: Customers have one-to-many relations to their
Orders similar to Vendors.
Hibernate DAO Implementations
The sample application's DAO interfaces' - discussed in
chapter
Data Access Layer -
Hibernate implementations are located in the
com.acme.sample.dao.impl.hibernate package.
Basically, they use Spring's HibernateTemplate convenience class, that takes care
of retrieving and closing Hibernate sessions, handles session
lifecycle exceptions, and converts HibernateExceptions to subclasses of Springs
DataAccessExceptions (which are the same for all data
access technologies Spring integrates).
To a HibernateTemplate instance a -
dependency-injected - SessionFactory is
passed:
private HibernateTemplate hibernateTemplate;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
This
SessionFactory is an instance
of a Spring
LocalSessionFactoryBean,
configured as described in chapter
SessionFactory Setup.
The HibernateTemplate instance then is used to
implement the DAOs' data access methods:
@Override
public List get(String query, Object[] params) {
return this.hibernateTemplate.find(query, params);
}
@Override
public T get(int id) {
return (T) this.hibernateTemplate.get(this.tClass, id);
}
@Override
public List<T> get(String filterCriteria) {
String queryString = "from " + this.getEntityName()
+ " where name like ?" + orderBy;
return (List<T>) this.hibernateTemplate.find(queryString,
filterCriteria);
}
@Override
public List<T> get() {
String queryString = "from " + this.getEntityName() + orderBy;
return (List<T>) this.hibernateTemplate.find(queryString);
}
@Override
public void insert(T T) {
this.hibernateTemplate.persist(T);
}
@Override
public void remove(T T) {
this.hibernateTemplate.delete(T);
}
@Override
public void update(T T) {
this.hibernateTemplate.merge(T);
}
The HibernateTemplate class also
provides a method saveOrUpdate(Object) ...
Generic Data Access Objects
As most of the DAOs' data access methods (any of those quoted
above) would contain redundant code, these have been externalized
into an abstract, generic, DAO superclass, from which all other
DAOs inherit. GenericDaoImpl.java is defined as:
public abstract class GenericDaoImpl
<T extends AbstractIdentifyable>
implements GenericDAO<T> {
// implementation code ...
}
Subclasses extend GenericDaoImpl:
public class CustomerDaoImpl
extends GenericDaoImpl<Customer>
implements CustomerDAO {
// inherited methods, which don't need to be overridden ...
}
HibernateTemplate defines a
number of methods that take a Class as method
argument, i.e., method .. Object get(Class, Serializable)
.. (to retrieve an object by its type and primary
key).
Normally, one would call that method like,
get(Customer.class, 42), however, that
does not work with the generic type: get(T.class, 42)
(or just T.class) will not compile due to Java
type erasure!
A
workaround is to use Java reflection to retrieve
the class:
this.tClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
JDBC
Spring provides an abstraction layer over the JDBC API, taking
care of opening and closing database connections, offering several
convenience classes and methods, and - last not least - eliminating
the need of verbose error handling by converting JDBC's
checked SQLException to
appropriate, unchecked, subclasses of Spring's
DataAccessException.
Spring Data Access Exception Hierarchy
So, in Spring, there is a data access
exception hierarchy. Since version 1.6, Java natively provides
a somewhat comparable hierarchy (before, there was the - relatively
uninformative - SQLException only),
however, there are still two basic differences:
Spring's DataAccessExceptions
extend RuntimeException, which means, they are not
checked - and don't need to be handled. That's
because Spring presumes most data access exceptions to be
non-recoverable (cannot be successfully repeated without
modification, anyways).
Also, Spring's DataAccessExceptions are
not specific to JDBC: Spring also converts specific
exceptions of the various O/R Mappers (ORMs) it supports, to its
own, unified, exception hierarchy.
Central JDBC Worker Classes
Central JDBC
worker classes include:
JdbcTemplate, containing methods like Object
execute(..), Object query(..), List queryForList(..), Object
queryForObject(..), SqlRowSet
queryForRowSet(..), and others.
SimpleJdbcTemplate, a Java 5 convenience wrapper
around JdbcTemplate, allowing for
varargs and generics.
NamedParameterJdbcTemplate, which, too, wraps
JdbcTemplate.
StoredProcedure, which allows for calling a
stored procedure. TODO: More details.
In this article's sample application, there
is an example implementation for each of the aforementioned
classes, in the com.acme.sample.dao.impl.jdbc
package.
To change which class is used, edit
appContext-dataAccessObjects.xml. - I.e., change ...
<bean id="businessUserDAO" class="com.acme.sample.dao.impl.jdbc.JdbcTemplateBusinessUserDaoImpl" />
to ...
<bean id="businessUserDAO" class="com.acme.sample.dao.impl.jdbc.StoredProcedureBusinessUserDaoImpl" />
... and run it by using the BusinessUserDaoTests JUnit test.
JdbcTemplate
JdbcTemplateBusinessUserDaoImpl in the sample
application is a wrapper around Spring's
JdbcTemplate:
public class JdbcTemplateBusinessUserDaoImpl implements BusinessUserDAO {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
...
}
To the JdbcTemplate's List
query(..) method, an SQL string, an array of SQL parameters
and a RowMapper instance are passed:
@Override
@SuppressWarnings("unchecked")
public List<BusinessUserWrapper> getCandidatesForLogin(int loginID) {
String query = "select id, name, security_user_id"
+ " from business_users where security_user_id <> ?"
+ " or security_user_id is null";
return (List<BusinessUserWrapper>) this.jdbcTemplate.query(
query,
new Object[] { loginID },
new RowMapper() {
@Override
public BusinessUserWrapper mapRow(ResultSet rs, int rowNum)
throws SQLException {
BusinessUserWrapper user = new BusinessUserWrapper();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setSpringSecurityID(rs.getInt("security_user_id"));
return user;
}
});
}
The anonymous RowMapper instance is used
to convert each row of the JDBC ResultSet
to an arbitrary data access object (DAO). The
JdbcTemplate instance will handle, and convert, the
SQLException that might be thrown.
SimpleJdbcTemplate
SimpleJdbcTemplate, wrapped by SimpleJdbcTemplateBusinessUserDaoImpl in the sample
application, behaves like JdbcTemplate,
except from the fact it considers Java 5 semantics:
public class SimpleJdbcTemplateBusinessUserDaoImpl implements BusinessUserDAO {
private SimpleJdbcTemplate simpleJdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
@Override
public List<BusinessUserWrapper> getCandidatesForLogin(int loginID) {
String query = "select id, name, security_user_id"
+ " from business_users" + " where security_user_id <> ?"
+ " or security_user_id is null";
return this.simpleJdbcTemplate.query(
query,
new BusinessUserRowMapper(),
loginID);
}
}
As Java 5 generics are used, no type cast is
needed when returning the result of SimpleJdbcTemplate's List<T>
query(..) method. The last method argument is of type
Object ..., a Java 5, variable-length, array
(vararg). The int parameter gets
autoboxed to an Integer instance.
The BusinessUserRowMapper used here converts each
JDBC ResultSet row to a DAO (exactly like
demonstrated above in the JdbcTemplate demonstration).
It implements Spring's ParameterizedRowMapper, which extends
RowMapper and adds Java 5 generic type
parametrization:
public class BusinessUserRowMapper implements
ParameterizedRowMapper<BusinessUserWrapper> {
@Override
public BusinessUserWrapper mapRow(ResultSet rs, int rowNum)
throws SQLException {
...
NamedParameterJdbcTemplate
NamedParameterJdbcTemplate (wrapped by
NamedParameterJdbcTemplateBusinessUserDaoImpl in the
sample application), supports the use of named SQL
parameters:
public List<BusinessUserWrapper> getCandidatesForLogin(int loginID) {
String query = "select id, name, security_user_id"
+ " from business_users" + " where security_user_id <> :id"
+ " or security_user_id is null";
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", loginID);
return (List<BusinessUserWrapper>) this.jdbcTemplate.query(query,
params, new BusinessUserRowMapper());
}
Instead of the Map, a SqlParameterSource could be used, which allows for
adding finer-grain information on the parameters passed, including
their data types (otherwise determined by reflection).
StoredProcedure
The abstract StoredProcedure class is a
convenience wrapper around (finally) JdbcTemplate, faciliating stored procedure
calls. In the sample application, it is implemented by StoredProcedureBusinessUserDaoImpl.
Obviously, for this demo class to work, a stored
procedure with a matching signature must exist in the database. In
the sample application, this is implemented for the PostgreSQL
database, only. Compare chapter
Additional DDL Commands,
where that procedure - for PostgreSQL, a
type and a
function - gets automatically created by Hibernate:
<database-object>
<create>CREATE TYPE business_user_wrapper as (
id integer, name character varying(255), security_user_id integer)</create>
<drop>DROP TYPE business_user_wrapper</drop>
<dialect-scope name="org.hibernate.dialect.PostgreSQLDialect" />
</database-object>
<database-object>
<create>CREATE FUNCTION get_candidates_for_login(login_id integer)
RETURNS SETOF business_user_wrapper
AS
'select id, name, security_user_id
from business_users
where security_user_id <> $1
or security_user_id is null'
LANGUAGE 'sql' VOLATILE;</create>
<drop>DROP FUNCTION get_candidates_for_login(integer)</drop>
<dialect-scope name="org.hibernate.dialect.PostgreSQLDialect" />
</database-object>
As usual, a DataSource reference needs to be
passed (typically, injected). After setting the SQL statement
(simply the name of the stored procedure to call) and in
and out parameters, the StoredProcedure's
central method Map execute(Map) is executed, passing a
Map of function parameters. One of the
possible return values (there may be multiple ones) is, in case of
the bespoken demonstration class, a List<BusinessUserWrapper>:
public class StoredProcedureBusinessUserDaoImpl extends StoredProcedure
implements BusinessUserDAO {
private static final String STORED_PROCEDURE_NAME = "get_candidates_for_login";
private static final String STORED_PROCEDURE_PARAM1 = "login_id";
private static final String RESULT_SET_NAME = "businessUsers";
@Autowired
public void setDataSource(DataSource dataSource) {
super.setDataSource(dataSource);
}
public StoredProcedureBusinessUserDaoImpl() {
this.setSql(STORED_PROCEDURE_NAME);
// input parameter
this.declareParameter(new SqlParameter(STORED_PROCEDURE_PARAM1,
Types.INTEGER));
// output (parameter)
this.declareParameter(new SqlReturnResultSet(RESULT_SET_NAME,
new BusinessUserRowMapper()));
// this call does have a return value
this.setFunction(true);
}
@SuppressWarnings("unchecked")
@Override
public List<BusinessUserWrapper> getCandidatesForLogin(int loginID) {
Map<String, Object> params = new HashMap<String, Object>();
params.put(STORED_PROCEDURE_PARAM1, loginID);
Map result = this.execute(params);
return (List<BusinessUserWrapper>) result.get(RESULT_SET_NAME);
}
}
Transaction Management
Transaction Definitions
Transactions in Spring are defined by the TransactionDefinition interface.
Propagation
Propagation means the scope of one or more
transactions.
In Spring, transaction propagation (see
TransactionDefinition and
Propagation) behaves analogous to the
Transaction
Attributes of Java EE
Container-Managed Transactions.
Possible values are (the descriptions are copied from the
JavaDocs):
PROPAGATION_REQUIRED: The default value.
Support a current transaction; create a new one if none
exists.
PROPAGATION_SUPPORTS: Support a current
transaction; execute non-transactionally if none exists.
Handle with care.
PROPAGATION_MANDATORY: Create a new transaction,
suspending the current transaction if one exists.
PROPAGATION_REQUIRES_NEW: Create a new
transaction, suspending the current transaction if one exists.
Will typically only work on Java EE application
servers.
PROPAGATION_NOT_SUPPORTED: Do not support a
current transaction; rather always execute
non-transactionally.
PROPAGATION_NEVER: Do not support a current
transaction; throw an exception if a current transaction
exists.
PROPAGATION_NESTED: Execute within a nested
transaction if a current transaction exists, behave like
PROPAGATION_REQUIRED else. There is no analogous Java EE Transaction
Attribute.
Isolation
An isolation level denotes the extend to which a
transaction can read uncommitted data changes of other
transactions.
Spring's TransactionDefinition defines the same
isolation levels as those defined in
java.sql.Connection. See these types' JavaDocs for
further information.
Other Transaction Settings
Transactions can have an execution timeout.
Transactions can be read-only. This does not
necessarily prevent from executing writing operations but
may be a hint to the underlying transaction manager to allow
optimizations.
Using the @Transactional annotation, conditions
for rollback and no-rollback can be set.
Transaction Managers
PlatformTransactionManager is the central
interface in Spring's transaction infrastructure, and defines the
methods commit(TransactionStatus),
rollback(TransactionStatus), and
getTransaction(TransactionDefinition).
All Spring transaction managers extend AbstractPlatformManager, which implements
PlatformTransactionManager.
Spring provides the following transaction managers:
| Transaction Manager |
Manages Transactions of |
| CciLocalTransactionManager |
javax.resource.cci.ConnectionFactory |
| DataSourceTransactionManager |
java.sql.DataSource |
| HibernateTransactionManager |
org.hibernate.SessionFactory (and
java.sql.DataSource) |
JdoTransactionManager |
javax.jdo.PersistenceManagerFactory
(and java.sql.DataSource) |
JmsTransactionManager (plus, JmsTransactionManager102 for legacy JMS
1.0) |
javax.jms.ConnectionFactory |
JpaTransactionManager |
javax.persistence.EntityManagerFactory (and
java.sql.DataSource) |
JtaTransactionManager |
an underlying javax.transaction.TransactionManager |
| TopLinkTransactionManager |
org.springframework.orm.toplink.SessionFactory
(and java.sql.DataSource) |
JtaTransactionManager delegates operations to an
underlying TransactionManager. By default
(the corresponding property is autodetectTransactionManager), it will try to resolve
a TransactionManager the application server, resp.,
container, provides. Java EE containers' JTA providers may support
distributed transactions (across multiple data sources)
and the PROPAGATION_REQUIRES_NEW transaction
scope.
For specific Java EE containers, special subclasses of
JtaTransactionManager exist:
OC4JJtaTransactionManager,
WebLogicJtaTransactionManager and
WebSphereUowTransactionManager.
Setting Up a Transaction Manager
In the sample application, a HibernateTransactionManager is used to manage
transactions (in applicationContext-dataSource.xml):
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />
It depends on a
LocalSessionFactoryBean (that creates a
org.hibernate.SessionFactory). The configuration of a
LocalSessionFactoryBean is described in
chapter
SessionFactory
Setup.
A
JtaTransactionManager (or one of its subclasses
mentioned in chapter
Transaction
Managers) may also be configured by using the
tx:jta-transaction-manager tag (its bean name will be
"transactionManager", automatically):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<tx:jta-transaction-manager />
...
Declarative Transaction Management
tx:advice XML Configuration
As a trivial example, the sample application adds read-only
transactions to any methods in the service layer whose names start
with "get".
As mentioned in chapter
Other Transaction
Settings, setting the
read-only property of a
transaction
may be a hint to the underlying transaction
manager for optimizations.
In appContext-businessFacadeInterceptors.xml, a
transactional advice is configured, an AOP
pointcut, and an AOP advisor:
<tx:advice id="readOnlyTransactionsAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceLayerGettersPointcut"
expression="execution(* com.acme.sample.businessfacade.impl.*.*(..))" />
<aop:advisor id="readOnlyTransactionsInServiceLayerAdvisor" advice-ref="readOnlyTransactionsAdvice"
pointcut-ref="serviceLayerGettersPointcut" />
</aop:config>
There is a JUnit test, TransactionsTests, that uses Spring's
AopUtils to ensure that the service layer is actually
proxied, and that the configured pointcut and
advisor are applicable:
@Test
public void testReadOnlyGetMethods() {
assertTrue("AdminFacade is not proxied by AOP.",
AopUtils.isAopProxy(adminFacade));
assertTrue("AdminFacade is not a JDK Dynamic Proxy.",
AopUtils.isJdkDynamicProxy(adminFacade));
Pointcut pointcut = (Pointcut) applicationContext
.getBean("serviceLayerGettersPointcut");
assertTrue(
"Pointcut serviceLayerGettersPointcut cannot be applied to AdminFacade.",
AopUtils.canApply(pointcut, AopUtils.getTargetClass(adminFacade)));
Advisor advisor = (Advisor) applicationContext
.getBean("readOnlyTransactionsInServiceLayerAdvisor");
assertTrue(
"Advisor readOnlyTransactionsInServiceLayerAdvisor cannot be applied to AdminFacade.",
AopUtils.canApply(advisor, AopUtils.getTargetClass(adminFacade)));
}
See chapter
Spring
AOP for further information on
aspect-orientated
programming in Spring.
By default, Spring transactions will automatically
rollback when
RuntimeExceptions
- or
Errors are thrown. This behavior can
be configured by adding the attributes
rollback-for and
no-rollback-for (which take a comma-separated list of
type names as arguments) to the
tx:method
element. Other possible attributes are
isolation,
propagation, and
timeout (see chapter
Transaction
Definitions).
@Transactional Annotation
The Transactional interface defines the same
properties as discussed above:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
This annotation can be applied at the class and method level,
requiring Java 5, the bean it is used with being managed by Spring,
and a tx:annotation-driven element in an XML
configuration file. By default, this element's attribute
transaction-manager points to a bean
named "transactionManager".
It is recommended not to set annotations on interfaces, but on
concrete classes instead: Annotations are not inherited from
interfaces and thus don't work with AspectJ proxies. On the other
hand, annotations will not work with methods which visibility is
anything but
public, and JDK proxies. See chapter
Spring AOP for more information.
In the sample application, AdminFacade.addOrEditUser(..) and CustomerFacade.addOrder(..) are annotated by
@Transactional, however, there is nothing special
about that.
Spring MVC
Controllers
In the sample application, all controllers reside in the
com.acme.sample.web.controllers Java package. They are
named by the scheme <user role><use
case>Controller.java, i.e.,
VendorListProductsController.java.
AbstractController
Simple controllers that - besides populating a model to supply
the view with - just forward to a single view in this case
all extend Spring's abstract AbstractController class. A sample goes as
follows:
public class CustomerListOrdersController extends AbstractController {
// ...
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
Map<String, Object> model = new HashMap<String, Object>();
model.put("orders",
facade.getCustomer(
request.getUserPrincipal().getName()
).getOrders());
return new ModelAndView("customerListOrders", model);
}
}
The .. handleRequestInternal(..)
.. method returns a ModelAndView instance,
which may (in most cases, it will) reference a view to
forward to (in most cases, a JSP page) and - in the shape of a
Spring ModelMap (implementing a
java.util.Map) - arbitrary object instances that the
view uses to populate its dynamic portions.
The view then can access the model's List<Order> object (BTW, a list of entitity
beans) by the EL (Expression Language) variable
$orders.
SimpleFormController
Often, a controller displays a form (which is a view
as well), which contents are then filled in by the user, and get
posted back to the controller. On success the controller
will display another view, on failure (i.e., a mandantory
form field has not been filled in by the user), the form will get
re-displayed, any validation errors marked and
depicted.
Such types of controllers that display a form that gets
posted back suite well to extend Spring's SimpleFormController class. - An example:
public class AdminAddVendorController extends SimpleFormController {
// ...
public AdminAddVendorController() {
// views can then access the "form backing object"
// by the EL variable $vendor
setCommandName("vendor");
// location of the form view to display
setFormView("adminAddVendor");
// what to display after the form data
// has been saved
setSuccessView("redirect:adminListVendors.htm");
}
// the model, accessible
// by the variable $vendor (in this case)
@Override
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
return new Vendor(); // a simple entity bean
}
// called after a form submit
@Override
protected void onBindAndValidate(HttpServletRequest request,
Object command, BindException errors) throws Exception {
// this form has only one editable field,
// so validation is simple
ValidationUtils.rejectIfEmpty(errors, "name", "validation.general.nameMustNotBeEmpty",
"Name must not be empty.");
}
// called after a form submit,
// if no BindExceptions were raised
@Override
protected void doSubmitAction(Object command) throws Exception {
// the command object
// (or form backing object)
// that the user has edited.
Vendor vendor = (Vendor) command;
// do some business action
facade.addVendor(vendor);
// the underlying SimpleFormController instance
// will now forward to our defined successView
}
}
So, using Spring's SimpleFormController class, there are two views
involved now - a form view and a success view. In
this case, these have been defined in in the controller's
constructor.
The MVC model is now considered a command, which
transfers state (the form data) bi-directionally to the form and
back to the controller. In the sample above, a simple entity bean
is returned by the Object
formBackingObject(HttpServletRequest) method. After the user
has edited and posted back the form, the - thereby edited -
command object can be evaluated and validated by
the controller (in this case, in the overriden methods ..
onBindAndValidate(..) .. and .. doSubmitAction()
..).
For to provide data that is used by the view - but not to be
edited therein - one can use the Map
referenceData(HttpServletRequest) .. method. Similar to the
model portion in a ModelAndView instance, a
java.util.Map implementation is used to hold arbitrary
objects, accessible by their respective keys.
Validation is discussed in the subsection called
Validation.
Finally, in the .. doSubmitAction(..) .. method,
some business method is used to persist the edited command data,
and the SimpleFormController forwards to
the successView we had set initially.
AbstractWizardFormController
More complex controllers can display an arbitrary number of
views - or pages. In this article's sample application, a
customer, who prepares to place an order, first selects a vendor
(in one form view), and next products from that vendor's products
assortment (in another form view).
Amongst other related methods, Spring's abstract
AbstractWizardFormController class provides the
method
int getTargetPage(HttpServletRequest, Object, Errors,
int), where the view to be displayed next can be specified
after evaluating request parameters, the command object (discussed
in chapter
SimpleFormController),
validation errors and - of course - the current page (by its page
ID).
Accordingly, there is a method setPages(String[]) to set the view names (the first
one set there will get the page ID 0, the next one 1,
consecutively).
To methods intended for form validation - including ..
onBindAndValidate(HttpServletRequest, Object, BindException,
int), the page ID is passed as a method argument, to enable
developers validate only those fields of the command object that
had been edited in the current view.
In the CustomerAddOrderController class, the
aforementioned methods are applied:
public class CustomerAddOrderController extends AbstractWizardFormController {
@Autowired
private CustomerFacade facade;
public CustomerAddOrderController() {
setCommandName("orderForm");
// specify a number of pages
setPages(new String[] { "customerAddOrderSelectVendor",
"customerAddOrderSelectProducts" });
}
@Override
protected int getTargetPage(HttpServletRequest request, Object command,
Errors errors, int currentPage) {
AddOrderForm form = (AddOrderForm) command;
if (currentPage == 0 && form.getVendor().getId() > 0) {
// a vendor has been selected;
// proceed with the "vendor's products" page
return 1;
}
else if (currentPage == 1) {
return 1;
}
return 0;
}
@Override
protected void onBindAndValidate(HttpServletRequest request,
Object command, BindException errors, int page) throws Exception {
// synchronize form properties
AddOrderForm form = (AddOrderForm) command;
switch (page) {
case 0: // "select vendor" page
// retrieve the vendor that has been selected in form 0
Vendor vendor = facade.getVendor(
form.getVendor().getId());
form.setVendor(vendor);
// provide the form with this vendor's products
form.setVendorProducts(
vendor.getProductsAsList());
break;
case 1: // "select products" page
// apply the customer's product selection
// ... (edit the form backing bean)
}
}
@Override
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
AddOrderForm form = new AddOrderForm();
form.setVendors(facade.getVendors());
return form;
}
@Override
protected ModelAndView processFinish(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
AddOrderForm form = (AddOrderForm) command;
form.setCustomer(getCurrentCustomer(request));
facade.addOrder(form);
return new ModelAndView("customerAddOrderFinish");
}
}
Other Controllers
Other controllers (not used in the sample application)
include:
- AbstractUrlViewController: Simply returns (a
ModelAndView instance with) a view
(decoupling the URL from the view's (JSP's) physical location). The
view will be derived from the URL.
Out-of-the box, there's an implementation, UrlFilenameViewController.
- BaseCommandController: Automatically
populates a command (form backing bean) with request
parameters. Is a superclass of both
AbstractWizardFormController and
SimpleFormController (used in the sample
application).
- MultiActionController: May handle
multiple actions per controller class. Action means any
method that explicitly or implicitly returns a ModelAndView, having the signature:
public [ModelAndView | Map | void]
anyMeaningfulName(HttpServletRequest, HttpServletResponse
[,HttpSession] [,AnyObject])
Spring' default MethodNameResolver
implementation - that handles which method is mapped to a URL - is
InternalPathMethodNameResolver, which
treats an URL's substring between the last slash (/) and dot (.) as
the method name (i.e., for the URL
http://acme.com/test/viewCart.xyz, the method
viewCart will be called.
There is still a wide range of alternative configuration settings
...
- ParameterizableViewController: Like
AbstractUrlViewController, simply returns a view. This
view would be set in an application context / bean definitions XML
file.
ServletForwardingController and
ServletWrappingController: Forward to or wrap the
servlet specified.
Mapping Controllers to URLs
Controllers are handlers for
incoming HTTP requests (that point to a certain URL).
Spring's default (it doesn't need to be explicitly specified
in the application's configuration) HandlerMapping implementation is BeanNameUrlHandlerMapping. It maps the bean's
name to the handling controller:
<bean name="/index.htm" class="com.acme.sample.web.controllers.MainController" />
Bean names may contain ANT-style wildcards. They need to start
with a slash.
DefaultAnnotationHandlerMapping is another
HandlerMapping that is pre-configured by Spring. It
evaluates Java 5 annotations:
@RequestMapping("/index.htm")
public class MainController extends AbstractController { ...
Using SimpleUrlHandlerMapping, one can explicitly
map URLs to handlers (wildcards are allowed):
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" p:order="0">
<property name="mappings">
<value>
/adminAddCustomer.htm=adminAddCustomerController
</value>
</property>
</bean>
When defining a HandlerMapping that
is not a default one, this will override the default
settings. Then otherwise implicit handlers need to be
explicitly declared:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
p:order="0">
...
</bean>
<!--
The following two handler mappings only need to be explicitly declared
when a non-default handler mapping (like SimpleUrlHandlerMapping
above) is declared.
—>
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"
p:order="1" />
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"
p:order="2" />
Other existing
HandlerMapping implementations
include:
- ControllerBeanNameHandlerMapping works quite like
BeanNameUrlHandlerMapping "but doesn't expect bean
names to follow the URL convention: It turns plain bean names into
URLs by prepending a slash and optionally applying a specified
prefix and/or suffix" (quoted from the JavaDoc).
- ControllerClassNameHandlerMapping uses the -
lower-cased - controller's class name - without the "Controller"
suffix: A controller named "WelcomeController" would map to the URL
"/welcome*" (to all URLs starting with "/welcome".
For MultiActionController and @Controller
beans, the class name "WelcomeController" would map to the URL
"/welcome/*".
Views
Views - which are based on interfaces and classes
in package org.springframework.web.servlet - are
not necessarily part of Spring's MVC framework (where the
controllers reside in package org.springframework.web.servlet.mvc).
Theoretically, Spring views (or MVC views in general) could be used
independently of controllers and models, or in other scopes.
This article's view on Spring views, however, is a more
practical one. The article needs to flatten information
depth.
Passing Model Data to Views
All Controller implementations (indirectly)
implement a method ModelAndView
handleRequest(..). The returned ModelAndView
instance itself provides a central getter Map
getModel(), which returns a Map of
key/value pairs. From within the views, the values are accessible
by their keys.
Additionally, Controllers that implement
BaseCommandController (including all Spring
controllers that have a "Form" in their class name) manage
a command object (of an arbitrary type) that is accessible
by the BaseCommandController's
String getCommandName() getter
method.
In JSP views, a ModelAndView Map's
values, i.e., ...
Map model = new HashMap();
model.put("key1", "hello world");
MyHelloWorld myHelloWorld = new MyHelloWorld();
myHelloWorld.setMessage("hello world");
model.put("key2", "hello back");
... would be accessible by using standard JSP expression
language (EL):
$key1
$key2.message
Similarly, a BaseCommandController
implementation's command object is accessible by the
key that had been set by using the class'
setCommandName(String) property.
In case of other AbstractView implementations,
that don't use EL, but access the model programmatically
instead (like an AbstractExcelView
implementation), there is always a method that takes the
ModelAndView's Map as a
method parameter, which can, for instance, be accessed as
follows:
public class OrderExcelView extends AbstractExcelView {
@Override
protected void buildExcelDocument(Map model, HSSFWorkbook workbook,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
Order order = (Order) model.get("order");
// ...
View Technologies
Out-of-the-box, Spring provides support for the following view
technologies:
- JSP & JSTL (JSP tag libraries, including Spring Forms)
- Apache Tiles (Composite View templating
framework)
- Velocity & Freemarker (expression evaluating templating
frameworks
- XSLT (XML transformations)
- PDF & Excel ("documents")
- JasperReports (reporting framework)
This article, and its sample application, cover views built
upon plain old JSPs (using standard EL (Expression
Language)), the JSPs partly enriched by Spring's Form and
Security tag libraries, and an Excel producer.
JSP and JSTL Views; Spring's Form Tag Library
Spring does not change the way JSPs work at all. However, it
provides a form tag library to bind the form-backing
command bean to HTML form fields, i.e., input
type="text", input type="checkbox", etc.. These
include "multiple HTML element" tags, like a series of connected
radiobuttons. There are also fields to display form validation
errors. - An example (adminAddOrEditProduct.jsp),
demonstrating the usage of Spring's form tag library,
follows:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<form:form commandName="product">
Name:
<form:input path="name" />
<form:errors path="name" cssClass="errors" />
<br />
Price:
<form:input path="price" />
<form:errors path="price" cssClass="errors" />
<input type="submit" value="Save" />
<br />
</form:form>
The form tag's commandName property matches the BaseCommandController's (and subclasses', like
AbstractFormController's and
SimpleFormController's) property of the same name. If
not set there, it defaults to "command".
The form does not need an
action but, by default, POSTs back to the controller
that had set the view.
Next in the above code snippet, there are form:input and form:errors
tags, bound to a path attribute - that
matches a property of the form-backing command bean. Naturally,
error messages are only displayed when errors do exist for that
form field.
form:checkboxes and
form:radiobuttons are
checked under the
following conditions:
- The bound bean property is a Boolean,
and true:
<form:checkbox path="riskAware" />
- The bean property bound to the path
attribute equals the value of the
value
attribute:
<form:checkbox path="lastName" value="Doe"
/>
- The bean property bound to the path
attribute is an array or a Collection (
List, Set) and
contains the value of the value
attribute:
<form:checkbox path="lotteryNumbers" value="-1"
/>
The form:select, form:checkboxes and
form:radiobuttons tags allow for
selecting one or multiple values (the tag's items
attribute) from a list of possible values (the tag's
path attribute). Additionally, they may have an
itemLabel displayed for the underlying
itemValue. An example (from
vendorListProducts.jsp) follows:
<form:checkboxes items="${products.allProducts}" path="vendorProductIDs"
itemLabel="name" itemValue="id"
element="div" />
The path attribute can hold an
array or Collection value,
and, in the above code snippet, refers to the property vendorProductIDs of the form's backing bean. It
defines the values currently being selected.
The items attribute can hold an
array, Collection or
Map value. If it is a Map, and the
itemLabel and itemValue
attributes are not set, the Map's keys and
values will be used. This list of all possible
values might refer to a property of the form backing bean
(the command object), however, it should be
separated into the AbstractFormController's (its subclasses') Map
referenceData(..).
The element attribute defines which HTML element
should surround each input type="checkbox". The
default element is span.
Excel Views
In the sample application, an order can be exported to a
Microsoft Excel document, which is written to the browser. The view
that creates the Excel document extends Spring's abstract class
AbstractExcelView and simply overrides one method
(from OrderExcelView.java):
protected void buildExcelDocument(Map model, HSSFWorkbook workbook,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
Order order = (Order) model.get("order");
HSSFSheet sheet = workbook.createSheet("Order");
int row = 0;
for (LineItem item : order.getLineItems()) {
HSSFCell cell = getCell(sheet, row, 0);
setText(cell, String.valueOf(item.getCount()));
cell = getCell(sheet, row, 1);
setText(cell, String.valueOf(item.getProduct().getName()));
cell = getCell(sheet, row, 2);
setText(cell, String.valueOf(item.getProduct().getPrice()));
row++;
}
}
This view is used by a controller like any other view type,
including JSPs - once it is registered to the BeanFactory (or ApplicationContext). In the sample application, that
is done by adding a normal <bean ... />
definition.
The AbstractExcelView class
extends the abstract class AbstractView (like
AbstractPdfView and AbstractXsltView
do).
By default, the web browser will prompt the user to
download the Excel document, not to display it inline.
This behavior can be changed by overwriting
AbstractView's abstract method boolean
generatesDownloadContent() (which returns true
with AbstractExcelView).
Resolving Views
In the sample application, mostly an InternalResourceViewResolver is used to resolve JSP
views:
<bean p:order="1"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/views/"
p:suffix=".jsp" />
When a controller returns a ModelAndView("listProducts"), the JSP
/WEB-INF/views/listProducts.jsp will be rendered.
It is good practice to place JSP views under
/WEB-INF to prevent direct access to them, as, per
definition, files within /WEB-INF are not accessible by
URL.
Just like AbstractExcelView and other classes,
the JstlView class extends the
AbstractView class which implements the
View interface's central method void render(Map,
HttpServletRequest, HttpServletResponse) throws
Exception.
The ResourceBundleViewResolver is more flexible
than the InternalResourceViewResolver and lets one
specify the view's class and location per single
view:
<bean p:order="0"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver"
p:basename="views" />
The baseName attribute "views"
refers to a file named views.properties in the root of the
classpath (or, i.e., views_de_DE.properties, when
internationalization applies). This attribute may be omitted, the
default is "views".
views.properties, in the sample application, contains
the following text (for a view named "index"):
index.class=org.springframework.web.servlet.view.JstlView
index.url=/WEB-INF/jsp/index.jsp
Both aforementioned ViewResolvers
implement the Ordered interface: By specifying the
beans' order attribute,
their precedence can be set (a lower int - starting by
Integer.MIN_VALUE meaning a higher precedence).
Both
InternalResourceViewResolver and
ResourceBundleViewResolver refer to a physical
location of the targeted view. Plus, they let JstlView
handle JSP documents. However, our Excel view
(OrderExcelView.java) is a Java class, and can't be
addressed that way.
Internationalization
Spring's central ApplicationContext interface
extends the MessageSource interface, which - by a
number of String getMessage(..) overloaded methods -
provides access to - optionally, parametrized or localized -
messages.
MessageSource Implementation
Basically, there are two MessageSource implementations: ResourceBundleMessageSource and ReloadableResourceBundleMessageSource. Like the
standard java.util.Properties class, both use either
*.properties files as message repositories, i.e.,
...
validation.general.nameMustNotBeEmpty='Name' must not be empty.
views.includes.navi.youAreLoggedInAs=You are logged in as
... or *.xml files, i.e.,
<entries>
<entry key="validation.general.nameMustNotBeEmpty">'Name' must not be empty.</entry>
<entry key="views.includes.navi.youAreLoggedInAs">You are logged in as</entry>
</entries>
The location of one or more of these
properties files can be set via the setBaseName(String) and setBaseNames(String[]) methods.
Just like with the standard java.util.ResourceBundle class, such a base
name, for instance, could be "messages". Following the rules
described in java.util.ResourceBundle.getBundle(..), depending on
the given (or default) Locale and the
physical existance of these files, a file named
"messages_en_US.properties",
"messages_de_DE.properties",
"messages_en.properties", or
"messages.properties" (the default) might be used for
input.
While the ResourceBundleMessageSource caches the
messages, the ReloadableResourceBundleMessageSource does
not if its property cacheSeconds is
set.
An application server might cache files, itself.
Read ReloadableResourceBundleMessageSource's JavaDoc for
tips.
Another difference to ResourceBundleMessageSource and java.util.ResourceBundle is that file encodings can
be specified.
Retrieving Messages
In this article's sample application (in file
appContext-internationalization.xml), a ReloadableResourceBundleMessageSource is configured
as follows:
<bean id="messageSource"
p:cacheSeconds="3" p:defaultEncoding="UTF-8"
p:basenames="/WEB-INF/classes/messages"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
</bean>
In JSPs, messages can now be retrieved by, i.e. (in
navi.jsp):
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<div><spring:message code="views.includes.navi.youAreLoggedInAs" /></div> ...
In the sample application's MVC controllers, validation errors
are localized:
ValidationUtils.rejectIfEmpty(errors, "name",
"validation.general.nameMustNotBeEmpty");
Only navi.jsp and the MVC controllers
are localized in the sample application.
It has been already mentioned that Spring's ApplicationContext extends the MessageSource interface, so the latter's String
getMessage(..) methods are available anywhere where an
ApplicationContext is accessible.
Resolving and Changing Locales
LocaleResolver implementations Spring provides
include AcceptHeaderLocaleResolver, which
looks at the client's (browser's) accept-language HTTP
header field, SessionLocaleResolver, which tries to
resolve a Locale from the HttpSession,
and CookieLocaleResolver, which adds and
retrieves HTTP Cookies in order to persist and resolve
selected Locales.
In the sample application (in
appContext-internationalization.xml), a CookieLocaleResolver is configured:
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />
LocaleResolver beans must have the
id "localeResolver", else they are not recognized by
Spring.
In order to enable the user change the locale, a LocaleChangeInterceptor is configured:
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
This interceptor needs to be advised to any applicable
handler mappings, i.e. (in
appContext-modelViewController.xml):
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"
p:interceptors-ref="localeChangeInterceptor" />
The LocaleChangeInterceptor looks at a HTTP
request parameter (with a default name of "locale"), and uses a
LocaleResolver to map the parameter's
value to a (newly,) selected, Locale. For instance, a
HTTP request pointing to the URL "index.htm?locale=de"
would set a German locale.
Providing a List of Selectable Locales
This section is rather integrational than
conceptual.
In order to let an application's user select
a locale from a list, the latter needs to be displayed in at least
one view, in this case, in any view.
To add the list (Map in this case) to all
ModelAndViews, each method call that returns a
ModelAndView is intercepted by an Around
advice:
@Aspect
public class I18nAdvice {
private Map<String, String> supportedLocales;
/**
* Specify which locales are supported by the application.
* @param locales Key: Locale code (i.e., "en", or "en_US").<br>
* Value: Message code for the display language (i.e., "languages.english",
* or "languages.german").
*/
@Required
public void setSupportedLocales(Map<String, String> locales) {
this.supportedLocales = locales;
}
/**
* Add a Map of supported locales to each ModelAndView.
*/
@Around("execution(org.springframework.web.servlet.ModelAndView *.*(..))")
public ModelAndView addSupportedLocales(ProceedingJoinPoint joinPoint)
throws Throwable {
ModelAndView mav = (ModelAndView) joinPoint.proceed();
mav.addObject("supportedLocales", supportedLocales);
return mav;
}
}
As with localized messages, an application deployer
can specify which locales are supported in a configuration file
(appContext-internationalization.xml):
<bean id="i18nAdvice" class="com.acme.sample.web.controllers.advices.I18nAdvice">
<property name="supportedLocales">
<map>
<entry key="en" value="general.languages.english"></entry>
<entry key="de" value="general.languages.german"></entry>
</map>
</property>
</bean>
The map's keys represent the locale's country (and,
optionally, country and variant) code (see the Locale JavaDocs). The map's values contain the key
for the locale's (or language's) display name, as in the localized
resource bundles (properties files) the application provides.
Finally, the selectable list can be displayed in the views,
here, in navi.jsp:
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt'%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
...
<c:forEach
items="${supportedLocales}"
var="locale">
<a href="?locale=${locale.key}">
<spring:message code="${locale.value}" />
</a>
</c:forEach>
Request Character Encoding
The CharacterEncodingFilter allows for specifying
the character encoding of requests (i.e., posted forms). It is
configured in web.xml:
<filter>
<filter-name>encoding-filter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Additionally to setting the encoding property -
that only applies if the request does not specify an
encoding itself -, the forceEncoding property can be
set. This will cause the HttpServletRequest to even
override an encoding specified with the request, and also use the
encoding as default HttpResponse
encoding.
Integration
As an architectural simplification, the article's sample
application contains an imaginary second application: a gateway to
a vendor's ERP (Enterprise Resource Planning) system,
which is located in package com.vendor1, and
sub-packages.
A customer, saving a new order, triggers interactions with
this, vendor's, application.
There is also the
Spring Web Services framework, which
implements the
contract-first approach of developing the
WSDL file first and derive the Java code from that. This article,
however, does not cover that.
Remoting
Spring supports a number of remoting technologies
out-of-the-box.
Remoting technologies can be differentiated by their transport
protocol (i.e., HTTP, TCP), contents (i.e., binary, XML), and
serialization mechanisms (i.e., Java serialization).
In general, binary remoting is faster than, i.e., web services
(see
Java Remoting: Protocol Benchmarks). On
the other hand, interoperatibility - amongst different programming
languages - is normally better with web services, although
Hessian provides APIs for a wider range of programming
languages.
Remote Method Invocation (RMI)
Remote Method Invocation (RMI) is a binary, Java-to-Java only,
remoting API.
Server-side Configuration
The remote interface is defined as follows:
public interface RmiConnector {
/**
* Notify the vendor that the given order had been placed in the shop application.
* @throws RemoteException
*/
void notifyAboutNewOrder(Order order);
/**
* Acknowledge that the order with the given ID has been received.
*/
boolean isOrderNotificationReceived(long orderID);
}
The interface neither needs to extend the java.rmi.Remote interface, nor do methods need to
declare RemoteExceptions. Any objects passed - like
Order and its properties - need to implement
Serializable.
The remote interface's implementation, RmiConnectorImpl, is trivial in this case.
The XML configuration,
appContext-remotingServicesNonHTTP.xml, contains the
following settings:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"
p:serviceName="VendorRmiConnector"
p:serviceInterface="com.vendor1.erp1.RmiConnector"
p:registryPort="1199">
<property name="service">
<bean class="com.vendor1.erp.rmi.RmiConnectorImpl"></bean>
</property>
</bean>
Spring's RmiServiceExporter exposes the service
based on the Remote interface
serviceInterface, implemented by a
service class, under the name of
serviceName and the port of
servicePort.
Client-side Configuration
At the client side, a Spring RmiProxyFactoryBean is used to create a proxy for the
remote RMI service.
<bean id="vendorRmiConnector"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
p:serviceUrl="rmi://localhost:1199/VendorRmiConnector"
p:serviceInterface="com.vendor1.erp.rmi.RmiConnector" />
Standalone JAX-WS Web Service
The standalone JAX-WS web service in the sample
appliction does not work with the Jetty appliction (used for
testing) (while it does work with Tomcat). Due to related bugs it
was extraordinary hard to debug this issue. Thus, an example for
this type of web services is commented out in the sample
application.
JAX-WS web services are successors to JAX-RPC web
services.
Standalone JAX-WS web services run on the lightweight
Java HTTP Server that comes with Java SE
1.6.
Server-side Configuration
Comparable to the RMI Remote interface, the
remote interface is defined as follows:
@WebService(serviceName = "VendorJaxWsService")
@SOAPBinding(parameterStyle = ParameterStyle.BARE)
public interface JaxWsConnector {
@WebMethod
void notifyAboutNewOrder(Order order);
@WebMethod
boolean isOrderNotificationReceived(long orderID);
}
Spring's SimpleJaxWsServiceExporter will detect
all beans annotated with @WebService, and
all methods therein annotated with @WebService.
This article's author found it quite hard
not to get a com.sun.xml.internal.ws.model.RuntimeModelerException
("runtime modeler error: Wrapper class
com.vendor1.erp.jaxws.jaxws.IsOrderNotificationReceived is not
found. Have you run APT to generate them?").
This error may occur if a) the web methods declare to throw any
Throwable, or b) @SOAPBinding(parameterStyle = ParameterStyle.BARE) is
missing (in contrast to ParameterStyle.WRAPPED, which
is the default setting).
Theoretically, there might be a Maven APT plugin to
generate the wrapper classes at compile time, however, the author
didn't figure out how to apply it.
The interface's implementation class, like in the RMI sample,
is trivial in this case.
The XML configuration,
appContext-remotingServicesHTTP.xml, contains the
following settings:
<bean id="jaxWsServiceExporter"
class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter"
p:baseAddress="http://localhost:9999/" />
Client-side Configuration
At the client side, a Spring JaxWsPortProxyFactoryBean is used to create a proxy
for the remote web service.
<bean id="vendorJaxWsConnector"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"
depends-on="jaxWsServiceExporter"
p:serviceInterface="com.vendor1.erp.jaxws.JaxWsConnector"
p:wsdlDocumentUrl="http://localhost:9999/VendorJaxWsService?WSDL"
p:namespaceUri="http://jaxws.erp.vendor1.com/"
p:serviceName="VendorJaxWsService"
p:portName="JaxWebServiceEndpointPort" />
First-off, the depends-on (on the server bean) is
needed as the server is not already started in the sample
application (which contains, and starts, both client and
server).
The wsdlDocumentUrl property composes from the
server's / exporter's baseAdsress and the
service name (set up in
@WebService(serviceName =
"VendorJaxWsService")).
The namespaceUri property corresponds to the
service interface's Java namespace, plus "javaws".
The serviceName property has already been
mentioned. To the author it is not clear by which rules the
portName property is generated into the WSDL
file.
At development time it may be unclear which
values are applicable for some of these properties. In succession,
temporary exceptions will be thrown. Developers, using an IDE, can
set a breakpoint at code points that are mentioned at the top of
these exceptions' stack trace, and then point a web browser to the
wsdlDocumentUrl property value. This should open the
WSDL file, containing these values.
JAX-WS Web Service via Apache CXF
The
Apache CXF framework supports a variety of
services, including - used in the sample application - JAX-WS web
services.
Server-side Configuration
In web.xml, Apache's CXFServlet is configured to listen under a given
address:
<servlet>
<servlet-name>jaxws</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jaxws</servlet-name>
<url-pattern>/jaxws/*</url-pattern>
</servlet-mapping>
In web.xml as well, a Spring XML configuration file
is associated with Spring's central ContextLoaderListener:
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContexts/vendor/appContext-remotingServicesHTTP.xml
...
</param-value>
</context-param>
...
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
...
</web-app>
In this Spring XML configuration file, our POJO (Plain Old
Java Object) gets exposed as a JAX-WS endpoint:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="vendorJaxWsService" class="com.vendor1.erp.remoting.jaxws.JaxWsServiceImpl" />
<jaxws:endpoint implementor="#vendorJaxWsService" address="/VendorService" />
</beans>
Client-side Configuration
At the client-side, a proxy is made available by the following
XML configuration:
<jaxws:client id="vendorJaxWsConnector"
serviceClass="com.vendor1.erp.remoting.jaxws.JaxWsService"
address="http://localhost:${ports.http}/springarticle/jaxws/VendorService" />
This Spring bean can now be dependency-injected into other
beans:
@Resource(name = "vendorJaxWsConnector")
JaxWsService jaxWsConnector;
Hessian
Hessian is a
binary protocol with
transports via HTTP. APIs are provided for a wider range of
languages.
There is an alternate, XML-based, remoting
protocol named
Burlap, which works for Java servers and
clients, only. From a Spring point of view, configuration equals to
a large degree.
Server-side configuration
First-off, the interface is quite similar to those
used with the RMI and JAX-WS samples:
public interface HessianConnector {
void notifyAboutNewOrder(long orderID);
boolean isOrderNotificationReceived(long orderID);
}
Other than with RMI (and, like with JAX-WS), the interface
does not have to extend another interface
(Remote for RMI) and does not have to throw
RemoteExceptions.
While in the other samples an Order had been
serialized and remotely transported, in the sample
application, a long primitive is passed, merely.
When serialization fails with Hessian, a low-level (thus, hard
to debug) Hessian HessianProtocol
exception will be thrown (as always, wrapped into a Spring
RemoteAccessException).
As with the other sample application's remoting samples, the
interface's implementation is trivial, in this case.
In appContext-remotingServicesHTTP.xml, Spring's
HessianServiceExporter creates a proxy of the service
class, implementing its interface:
<bean id="hessianConnectorImpl" class="com.vendor1.erp.hessian.HessianConnectorImpl"></bean>
<bean name="hessianExporter"
class="org.springframework.remoting.caucho.HessianServiceExporter"
p:service-ref="hessianConnectorImpl" p:serviceInterface="com.vendor1.erp.hessian.HessianConnector" />
Finally, the servlet that makes up the front facade
is being made available through the web.xml configuration,
setting up a Spring HttpRequestHandlerServlet:
<servlet>
<servlet-name>hessianExporter</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hessianExporter</servlet-name>
<url-pattern>/hessianExporter/HessianConnector</url-pattern>
</servlet-mapping>
The servlet name corresponds to the exporter's
bean name.
Client-side configuration
At the client side, the proxied service is made available by a
HessianProxyFactoryBean, using its interface (in
appContext-remotingServicesHTTP.xml):
<bean id="vendorHessianConnector"
class="org.springframework.remoting.caucho.HessianProxyFactoryBean"
p:serviceUrl="http://localhost:8080/springarticle/hessianExporter/HessianConnector"
p:serviceInterface="com.vendor1.erp.hessian.HessianConnector" />
The serviceUrl property corresponds to the
server-side url-pattern property in
web.xml.
Spring HTTP Invokers
Like Hessian, Spring HTTP invokers expose services through
HTTP. Unlike Hessian however, HTTP invokers use the standard Java
serialization mechanism, which allows for serialization of more
complex objects than with Hessian.
In general, configuration settings equal those of
Hessian.
The following sections will therefor outline the
differences to the
Hessian setup,
only.
Server-side configuration
As Spring HTTP invokers use standard Java serialization, an
Order object can be serialized. In contrast, in the
Hessian sample, a long primitive had been
passed:
public interface HttpInvokerService {
void notifyAboutNewOrder(Order order);
boolean isOrderNotificationReceived(long orderID);
}
In appContext-remotingServicesHTTP.xml, Spring's
HttpInvokerServiceExporter is used instead of the
HessianServiceExporter.
Like in the Hessian sample, the bean is mapped to a
servlet, and URL pattern in
web.xml.
Client-side configuration
At the client side (in
appContext-remotingClientsNonHTTP.xml), the proxied
service is made available by a HttpInvokerProxyFactoryBean instead of a
HessianProxyFactoryBean.
Java Message Service (JMS)
Java Message Service (JMS) is a form of loosely coupled
distributed communication, where messages are not directly
transferred from a message producer to a
consumer, but to an intermediate Destination instead, which can be a
Queue or a Topic. Messages
usually are sent asynchronously, and it is possible to send replies
to a repy-to Destination.
A (point-to-point) queue
is a message reservoir where messages are consumed in the order
sent (FIFO, first-in-first-out), and thereby removed from the
queue.
A (publish-and-subscribe) topic, in
contrast, is a message reservoir to which different consumers can
subscribe.
Java EE application servers usually bring their own JMS
provider. In this article's sample application, the
Apache
ActiveMQ framework is embedded.
There is much to say on JMS, and implementations
may become fairly complex. This article only covers sending to and
consuming from a Queue, as well as a sample reply to
the original producer.
Consuming Messages
At the (ficticious) vendor's ERP system, a message
consumer, or listener, is configured and
implemented.
Involved are an ActiveMQ BrokerService (managing the transport layer's
operations), an ActiveMQ ConnectionFactory implementation (connecting to
configured queues or topics), and an ActiveMQ Queue implementation (containing the messages
sent):
<amq:broker useJmx="false" persistent="false">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://localhost:0" />
</amq:transportConnectors>
</amq:broker>
<amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost" />
<amq:queue id="destinationQueue" physicalName="com.vendor1.erp.jms.Queue1" />
The ActiveMQ ConnectionFactory
is wrapped by a Spring SingleConnectionFactory adapter that always returns
the same connection. (There are alternatives.)
A Spring DefaultMessageListenerContainer references the
aforementioned SingleConnectionFactory
and Queue, as well as, finally, the
sample application's MessageListener
implementation:
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory"
p:targetConnectionFactory-ref="jmsFactory" />
<bean id="vendor1JmsConsumer" class="com.vendor1.erp.jms.JmsConsumer" />
<bean id="jmsListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="destinationQueue" />
<property name="messageListener" ref="vendor1JmsConsumer" />
</bean>
The JmsConsumer is defined as follows:
public class JmsConsumer implements SessionAwareMessageListener {
@Override
public void onMessage(Message message, Session session) throws JMSException {
Order order = (Order) ((ObjectMessage) message).getObject();
if (message.getJMSReplyTo() != null) {
TextMessage replyToMessage = new ActiveMQTextMessage();
replyToMessage.setJMSCorrelationID(message.getJMSCorrelationID());
replyToMessage.setText("there needs to be some text");
session.createProducer(message.getJMSReplyTo())
.send(replyToMessage);
}
}
}
Implementing the SessionAwareMessageListener, defining the
onMessage(Message, Session) method, allows for
(consuming and) producing messages through the Session
implementation passed.
At first in the onMessage(..) method, the
Message object is cast to an
ObjectMessage. Various Message
implementations exist, including TextMessage,
ByteMessage, and MapMessage.
The Message.getJMSReplyTo() method returns a
Destination - if any - to send a reply to. A
jMSCorrelationID is an application or provider
specific string that, in the sample, is used to reference the
original message. Finally, the Session is used to
create a MessageProducer which sends a new
TextMessage as a reply to the Destination
that was passed with the original message.
Producing Messages
At the client side, a message producer is configured
and implemented.
For the sake of simplicity, the sample refers to the
ConnectionFactory set up in the server-side
configuration.
The application's JmsVendorNotifier refers to a
Spring JmsTemplate (which provides several convenience
methods regarding producing and consuming messages, inspecting and
converting messages, and accessing connections and sessions), as
well as to the consumer's destination queue:
<import resource="../vendor/appContext-jms.xml"/>
<bean id="producerJmsTemplate" class="org.springframework.jms.core.JmsTemplate"
p:connectionFactory-ref="connectionFactory" />
<bean id="vendorJmsProducer"
class="com.acme.sample.businessfacade.interceptors.orderinserted.JmsVendorNotifier"
p:destinationName="com.vendor1.erp.jms.Queue1" p:jmsTemplate-ref="producerJmsTemplate" />
A number of steps are involved to create a temporary
Destination for the message's jMSReplyTo
property:
Connection replyToConnection = jmsTemplate.getConnectionFactory().createConnection();
replyToConnection.start();
Session replyToSession = replyToConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Destination replyToDestination = replyToSession.createTemporaryQueue();
MessageConsumer replyToConsumer = replyToSession.createConsumer(replyToDestination);
replyToConsumer.setMessageListener(this);
Finally, a new ObjectMessage is created and sent
within a callback object supplied to the
JmsTemplate:
jmsTemplate.send(destinationName, new MessageCreator() {
@Override
public Message createMessage(Session session)
throws JMSException {
ObjectMessage message = session.createObjectMessage(order);
// prepare reply-to
message.setJMSReplyTo(replyToDestination);
message.setJMSCorrelationID("ID:" + String.valueOf(order.getId()));
return message;
}
});
Alternatively, a convenience method of the JmsTemplate can be used:
jmsTemplate.convertAndSend(order);
In the sample, the first message that has been sent has been
provided with a reply-to Destination, a
corresponding Connection has been
started.
The message producer now itself implements the
MessageListener interface, receiving reply-to messages
in the onMessage(Message) method.
Exposing and Accessing EJBs
Spring supports accessing stateless session beans
(SLSB) as well as resource injection into them.
Stateful session beans are not
supported by Spring, probably because they have a lifecycle more
difficult to implement.
Exposing EJBs
The sample application uses the OpenEJB framework
which automatically scans the classpath for EJB and JPA
annotations, including @Stateless on the
Ejb3StatelessBeanService class in the sample
application, and @Remote on its remote
interface, which OpenEJB will expose.
The Ejb3StatelessBeanService SLSB in the sample
application is also annotated by Spring's
@Interceptors(SpringBeanAutowiringInterceptor.class)
allowing for autowired resource injection into the SLSB.
Unfortunately, the Spring reference documentation does
not mention a mandantory prerequisite for this to work: A
ClassPathXmlApplicationContext needs to
be defined in an XML configuration file used:
<bean class="org.springframework.context.support.ClassPathXmlApplicationContext" />
The default location of this file is
classpath*:beanRefContext.xml.
The file's location could be changed by overwriting ...
public class MySpringBeanAutowiringInterceptor extends
SpringBeanAutowiringInterceptor {
@Override
protected BeanFactoryLocator getBeanFactoryLocator(Object target) {
return ContextSingletonBeanFactoryLocator
.getInstance("classpath:some/xmlConfigFile.xml");
}
}
To the ClassPathXmlApplicationContext bean mentioned above,
XML configuration file locations can be passed via an XML
constructor-args element.
Accessing EJBs
In the sample application, a SimpleRemoteStatelessSessionProxyFactoryBean is used
to look up the remote EJB via JNDI, retrieve it, and expose a proxy
using its remote interface. The Spring jee namespace
is used:
<jee:remote-slsb id="vendorEjb3SlsbConnector"
cache-home="false" jndi-name="Ejb3StatelessBeanServiceRemote"
business-interface="com.vendor1.erp.ejb3.Ejb3StatelessBeanServiceRemote">
<jee:environment>
java.naming.factory.initial=org.apache.openejb.client.LocalInitialContextFactory
</jee:environment>
</jee:remote-slsb>
To access local SLSBs, a LocalStatelessSessionProxyFactoryBean would be used,
expressed by the XML equivalent jee:local-slsb.
The default JNDI name (jndi-name XML attribute)
is <Class Name>(Remote|Local) with
OpenEJB.
Obviously, the business interface plays a central role, plus,
one needs to specify from where to obtain an InitialContext.
Automated Testing
In general, there are different scopes of testing:
- Unit testing: Testing individual, isolated, pieces of
code, like single methods.
- Integration testing: Testing combinations of
individual software modules.
- System testing: Testing an entire system in the
context of its specific requirements. This may include GUI
Testing, Usability Testing, Performance
Testing, etc.
The JUnit Testing Framework
There is a number of ways to configure tests for JUnit. In
this article's sample application, this is done via
annotations.
Basically, there are test cases and test
suites, where the latter may consist of multiple test cases.
In the sample application, the AllTestsSuite class
runs all test cases available:
@RunWith(Suite.class)
@SuiteClasses( { /* DAO tests: */BusinessUserDaoTest.class,
CustomerDaoTest.class, OrderDaoTest.class, ProductDaoTest.class,
UserDaoTest.class, VendorDaoTest.class, /* MVC tests: */
AdminAddCustomerControllerTest.class, /* Transactions tests: */
TransactionsTest.class, /* Integration tests: */InsertOrderTest.class,
HttpLoginTest.class,
/* Remoting tests (Non-HTTP) */RmiTest.class, Ejb3RemoteSlsbTest.class,
/* JMS */
JmsTest.class })
public class AllTestsSuite extends AbstractTests {
}
The @SuiteClasses annotation specifies the
classes to be run when a class, AllTestsSuite here,
annotated with @RunWith(Suite.class) is run.
In the test cases itself - for instance, in
BusinessUserDaoTest - any methods annotated by
@Test are executed:
@Test
public void testGetCandidatesForLoginInt() {
List<BusinessUserWrapper> loginCandidates = businessUserDAO
.getCandidatesForLogin(user.getId());
assertEquals(0, loginCandidates.get(loginCandidates.size() - 1)
.getSpringSecurityID());
assertNotNull(loginCandidates.get(0).getName());
assertTrue(loginCandidates.get(0).getId() > 0);
}
The org.junit.Assert class provides a large
number of assertXXX(..) methods that evaluate
conditions to test. If a condition cannot be met, these methods
delegate to the fail(..) method, which in turn throws
an AssertionError, which will cause this specific test
to fail.
Before and after tests are run, additional code can be
executed, i.e., establishing or closing a database connection. In
those sample application's test cases that test HTTP-based
services, an embedded web server is started before the tests run
and stopped thereafter:
public class AbstractWebServerTests extends AbstractTests {
...
@Before
public void startWebServer() {
...
webappRunner.start();
...
}
@After
public void stopWebServer() {
...
webappRunner.stop();
...
}
}
Analogously to the @Before and
@After annotations on instance methods, there
are the @BeforeClass and @AfterClass
annotations, to be used with static methods.
Spring Testing Support
Support for automated testing in Spring can be grouped into
the following domains:
- The TestContext Framework, handling application
contexts and dependency injection, tests' lifecycles (like
before and after methods), transactions, caching
of application contexts, etc.
- Mock objects, particularly for web / servlet, as well
as portlet, environments
- A set of annotations to further control test methods'
behavior, particularly, transactions
Out of the box, there is support for the JUnit 3.8, JUnit 4.4,
and TestNG frameworks.
Spring TestContext Framework
All test cases in the sample application either extend
Spring's AbstractJUnit4SpringContextTests or
AbstractTransactionalJUnit4SpringContextTests
convenience classes.
Both create an ApplicationContext based on the
file locations specified in the @ContextConfiguration
annotation, ie.,
@ContextConfiguration(locations = {
"classpath:applicationContexts/backend/appContext-dataSource.xml",
"classpath:applicationContexts/backend/appContext-dataAccessObjects.xml"})
public abstract class AbstractDaoTests extends AbstractTransactionalJUnit4SpringContextTests { ...
The AbstractxxxSpringContextTests delegate to the
underlying base testing framework (i.e, JUnit), and set up a number
of TestExecutionListeners, including a
DependencyInjectionTestExecutionListener. The
AbstractTransactionalJUnit4SpringContextTests class
additionally sets up a
TransactionalTestExecutionListener, that processes
annotations related to transactional behavior, as well as simple
JDBC support. It requires a DataSource to be present
in the application context loaded, as well as a
PlatformTransactionManager (which name must be
"transactionManager").
For each test case, Spring instantiates a
TestContextManager, which holds a
TestContext, which, in turn, loads an application
context by looking at the @ContextConfiguration
annotation at the type level. The application context will be
cached and re-used for any tests.
AbstractTransactionalxxxSpringContextTests
Spring's
AbstractTransactionalxxxSpringContextTests (for each
supported testing framework, i.e.,
AbstractTransactionalJUnit4SpringContextTests)
add transactional behavior to each test. By default, data changes
will be rolled back, automatically.
These classes provide access to a
SimpleJdbcTemplate instance, as well as offering some
JDBC convenience methods. The sample application's
AbstractDaoTests class, from which all
DAO Tests inherit, tests the transactional
functionality by using Spring's convenience method
countRowsInTable(String):
int[] rowCounts = new int[7];
String[] tableNames = { "authorities", "business_users", "line_items",
"orders", "products", "users", "vendors_products" };
@BeforeTransaction
public void countTableRowsBeforeTransaction() {
for (int i = 0; i < rowCounts.length; i++) {
rowCounts[i] = countRowsInTable(tableNames[i]);
}
}
@AfterTransaction
public void countTableRowsAfterTransaction() {
for (int i = 0; i < rowCounts.length; i++) {
assertEquals("Transaction didn't rollback with table "
+ tableNames[i], rowCounts[i],
countRowsInTable(tableNames[i]));
}
}
Methods annotated with @BeforeTransaction will be
executed before transactions, methods annotated with
@AfterTransaction thereafter.
Related annotations include @NotTransactional to
indicate that a test method is not transactional, and
@Rollback, which takes a boolean value to
indicate whether to rollback (which is the default) the transaction
or not.
Common Annotations
It has been mentioned that application contexts are cached per
TestContext (test case class). The
@DirtiesContext annotation indicates that a method may
change an application context, and causes Spring to rebuild it for
subsequent tests.
The @ExpectedException annotation indicates that
a test is supposed to throw the exception type specified.
Tests can be executed conditionally, using the
@IfProfileValue annotation along with a
@ProfileValueSourceConfiguration annotation, pointing
to a custom implementation of the ProfileValueSource
interface.
Tests can be executed @Repeatedly and
@Timed, indicating that the test has failed when its
execution time exceeds the specified duration.
Tests in the Sample Application
Other than in a real life scenario, the tests use
the "production" settings, including the database.
Excursus: Embedding the Jetty HTTP Server
Vast parts of the sample application - including the MVC/GUI
parts and a number of remoting services - are to be accessed via
the HTTP protocol, and to be served by a web or application server.
In order to allow for automated testing, the application needs to
be deployed to such a server, and the server needs to be
started.
One way to accomplish this would be to set up an appropriate
Maven goal.
In the sample application, however, the
Jetty HTTP
server and servlet container is
embedded, and started (and
stopped) during tests. Jetty is started and stopped by the
following code:
public class JettyUtils {
public static Server startServer(Properties serverProps) throws Exception {
Server server = new Server(Integer.parseInt((String) serverProps
.get("port")));
WebAppContext context = new WebAppContext((String) serverProps
.get("webappContextPath"), (String) serverProps
.get("webappPartialURL"));
context.setClassLoader(ClassLoader.getSystemClassLoader());
server.addHandler(context);
server.start();
return server;
}
public static void stopServer(Server server) throws Exception {
if (server != null && !server.isFailed() && !server.isStopped()
&& !server.isStopping()) {
server.stop();
}
else {
throw new IllegalStateException();
}
}
}
To the WebAppContext, the context path is passed
(the path to a WAR file or exploded WAR directory), as well as a
partial URL (i.e.,
http://localhost:8080/springarticle).
Alternatively, Jetty can be configured to run as a Spring
managed bean, in Spring XML configuration. The blog article
Embedding Jetty 6 in Spring provides a
sample.
Running the Tests
The tests can either be started using Maven at the
command line, or by using the Eclipse GUI.
Several tests start an embedded web server (see chapter
Excursus: Embedding
the Jetty HTTP Server). Edit
src/test/resources/testing-jetty.properties to configure
the server. Before tests can be run that start that embedded web
server, the application needs to be "deployed", i.e., by executing
the following command in a shell:
mvn package -Dmaven.test.skip=true
This will create a WAR file in the target
directory.
After that, tests can be run using the command line,
again:
mvn test
This will run all tests within classes that are named
**/Test*.java, **/*Test.java, or
**/*TestCase.java.
To run a specific test, pass the fully qualified class name,
i.e.:
mvn test -Dtest=com.acme.sample.test.remoting.http.JaxWsTest
Alternatively to running the tests using the command line,
Eclipse can be used: Right-click on the file to test
(AllTestsSuite for all tests available) and select
Run as —> JUnit Test.
DAO Tests
The generic, abstract, class
AbstractAbstractNameableDaoTests<T extends
AbstractNameable>, from which most test classes inherit,
contains common functionality. There is the convenience
AllDaoTestsSuite class that runs all DAO tests.
Remoting Tests
These test the application's remote services (see chapter
Integration).
AOP Tests
The
TransactionsTest test case uses several
methods of Spring's
AopUtils class -
isAopProxy(..),
isJdkDynamicProxy(..) and
canApply(..) - to test whether an AOP transactional
advice (see chapter
tx:advice XML
Configuration) is in place.
This doesn't reach to the method level, though.
There are also no tests for functionality.
Integrational Tests
Testing HTML Forms
To test the functionality in
login.jsp, the web
application is served by the embedded web server (see chapter
Excursus: Embedding
the Jetty HTTP Server). The
HttpUnit framework is used to simulate a user
filling in HTML form fields, and submitting the form:
WebConversation conversation = new WebConversation();
WebRequest request = new GetMethodWebRequest(
"http://localhost:" + serverProps.getProperty("port")
+ "/springarticle");
WebResponse response = conversation.getResponse(request);
WebForm loginForm = response.getForms()[0];
request = loginForm.getRequest();
request.setParameter("j_username", userName);
request.setParameter("j_password", password);
response = conversation.getResponse(request);
assertTrue("Login for " + userName + "/" + password
+ " not successful.", response.getText().indexOf(
"Main Page") > -1);
Testing a MVC Controller
In the sample application (in package com.acme.sample.test.mvc), there is only one,
demonstrational, test case, which tests the AdminAddCustomerController MVC controller.
In the @Before method, a Spring-managed instance
of this controller is obtained by querying the
ApplicationContext
AbstractJUnit4SpringContextTests provides:
@Before
public void setUp() {
this.controller = (AdminAddCustomer) applicationContext
.getBean("adminAddCustomerController");
}
The managed bean could also be retrieved by using
the @Autowired annotation.
That way (not creating the instance with the new keyword), the bean is managed by Spring, as well
as its dependencies (e.g., autowired or otherwise
dependency-injected instance fields).
As the MVC controller needs a HttpServletRequest (it might need a
HttpServletResponse and HttpSession,
too), the request object needs to be replaced by a
mock object for testing purposes:
@Test
public void testFormBackingObjectHttpServletRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
try {
assertEquals(new Customer(), controller.formBackingObject(request));
}
catch (Exception e) {
fail(e.toString());
}
}
The controllers validation can be tested by providing
a BindException instance:
@Test
public void testOnBindAndValidateHttpServletRequestObjectBindException() {
MockHttpServletRequest request = new MockHttpServletRequest();
Object command = new Customer();
BindException errors = new BindException(command, "customer");
// validate a Customer with an empty name
try {
controller.onBindAndValidate(request, command, errors);
}
catch (Exception e) {
fail(e.toString());
}
assertEquals(1, errors.getAllErrors().size());
// validate a valid Customer
errors = new BindException(command, "customer");
((Customer) command).setName(StringUtils.getRandomString());
try {
controller.onBindAndValidate(request, command, errors);
}
catch (Exception e) {
fail(e.toString());
}
assertEquals(0, errors.getAllErrors().size());
}
Finally, the controller's submit action gets
invoked:
@Test
public void testDoSubmitActionObject() {
Customer customer = new Customer();
customer.setName(StringUtils.getRandomString());
try {
controller.doSubmitAction(customer);
Customer savedCustomer = customerDAO.get(customer.getId());
assertEquals(customer, savedCustomer);
}
catch (Exception e) {
fail(e.toString());
}
}
Under the hood, the application's service layer is
used to perform the action, which is secured by ...
@RolesAllowed( { "ROLE_ADMINISTRATOR" })
public interface AdminFacade {
...
However, this annotation does not get interpreted as Spring -
with the test case - is not configured to do so:
<global-method-security jsr250-annotations="enabled"
secured-annotations="enabled" />
... is missing in the configuration files passed to the
ApplicationContext that runs the tests (see chapter
Authorization).
Spring's ModelAndViewAssert convenience class
contains several methods to test for the validity of a controller's
ModelAndView:
ModelAndView mav = controller.handleRequest(request, response);
ModelAndViewAssert.assertAndReturnModelAttributeOfType(mav,
"customer", Customer.class);
ModelAndViewAssert.assertAndReturnModelAttributeOfType(mav,
"supportedLocales", Map.class);
ModelAndViewAssert.assertModelAttributeAvailable(mav, "customer");
ModelAndViewAssert.assertModelAttributeValue(mav, "customer",
new Customer());
ModelAndViewAssert.assertViewName(mav, "adminAddCustomer");
Testing the "Insert Order" Use Case
From a testing point of view, there is nothing special with
this test. However, it covers a larger number of secondary use
cases, along with their functionalities.
Patterns in Spring
Architectural Patterns
Dependency Injection Pattern
Problem
Classes, resp., components, should be loosely coupled in order
to faciliate substitution (i.e., substituting the data access layer
for testing purposes, externalize service layers, change
presentation layer components, etc.).
Solution
Spring provides a Dependency Injection (DI) container
that injects concrete instances into classes managed by Spring.
Ideally, the latter classes refer to their dependencies by
specifying interfaces instead of implementation classes. In Spring,
there is constructor, field, setter, and
method argument injection.
While DI can be configured by Java 5 annotations in Spring, it
can also be configured by XML configuration files. In case of using
configuration files, implementation details can be substituted
without recompiling.
Dependency Injection is a sub-form of the
Inversion of
Control (IoC) Pattern, which, in terms of
Design Patterns, can also be
implemented by the
Factory
Pattern, the
Observer and
Visitor
Patterns, callback functions or any type of plug-ins.
Model-View-Controller (MVC) Pattern
The MVC pattern may be regarded an
architectural as well as a design pattern.
Problem
In rather loosely structured applications, data access code,
business logic code, and presentation code may be mixed. Such
applications are difficult to maintain and test as concerns are
highly coupled. Modifications of either layer will affect the
others.
Applications may have different, even multiple, presentation
layers, i.e., web (i.e., Java Server Faces with or without AJAX
support) or rich client (i.e., Swing or SWT) client GUIs. The
presentation layer should be exchangeable, without affecting the
business logic.
Data access code, and the resulting data structures, may be
used by multiple presentation layers across an application.
Data access code should be reusable.
Solution
The MVC (Model-View-Controller) pattern separates concerns
into model, view, and controller:
The model represents the domain-specific
information on which the application operates (domain
model). This may be the result of a database query for a
specific list of customers. Submodels may be combined, that is, a
model may contain different types of data.
A model may also contain application or session state
(application model), i.e., the display language a user has
selected for the presentation layer.
Generally, the model contains (at least) any variable data to
be rendered in a view of the presentation layer.
The model in Spring is a Map containing arbitrary
types of objects.
The view renders the contents of the model,
typically into a GUI (graphical user interface), and possibly
offers user interaction facilities (like input or selection). By
default, Spring MVC provides views for web pages, Excel and PDF
documents, and others. These extend the AbstractView class.
The controller processes and responds to
events that typically arise from user interactions (like clicking a
link or posting a form).
The controller may validate and interpret user interactions,
and update the model.
Controllers in Spring MVC implement the Controller interface (and typically extend the
AbstractController class), and their
handleRequest(..) method returns a
ModelAndView instance.
The model the controller returns may consist of the complete
data the controller acquired for a specific request, or just a
subset. It is called command object.
BTW, the model and the view objects may compose
of one or more sub objects, implementing the
Composite
Pattern. Model and view issue
commands to each other
(i.e., a view to a model, when posting a form) (
Command
Pattern). As model and view don't know about each other, these
commands can be seen as adapters, according to the
Adapter
Pattern (also see chapter
Exporter Pattern).
Based on HandlerMappings, Spring's central
DispatcherServlet decides which handler object to
execute - typically, a controller.
Further information is available in chapter
Spring MVC.
Design Patterns
Commonly, design patterns are described by their
context, the underlying problem, and the
resulting solution, and pattern, finally. This
article abstains from following this more comprehensive
methodology.
Creational Design Patterns
Factory Pattern
The Factory Pattern slightly differs
from the Abstract Factory Pattern and the Method
Factory Pattern, which are not discussed at this place.
Problem
The creation of an object may require additional processes to
be involved rather than mere instantiation. There may be the need
to determine which object - also, object of which type - actually
to instantiate, in which scope to instantiate it (see the chapters
Singleton Pattern and
Bean Scopes), to configure it,
i.e., provide it with its dependencies, etc.
If the code necessary to create an object instance exceeds
a certain amount (and thus would lead to duplicate code within an
application), or if the type of the object to be created may vary,
instantiation code should be centralized into a factory.
Solution
A factory defines one or more methods for other
classes to obtain an instance of the target object.
The root interface for accessing a Spring bean container is
BeanFactory (one of its implementations is
ClassPathXmlApplicationContext), with
their (overloaded) method Object getBean(..).
Analogously, Spring AOP ProxyFactory
instances - implementing AopProxy - analogously define
a method Object getProxy().
Singleton Pattern
Problem
An application needs to make sure that only one instance of a
class exists, which is globally accessible. This way the
application can hold global state, i.e., user settings.
Solution
In a normal Java application, the following code would ensure
that there is at most one instance of UserSettings per classloader:
public class UserSettings {
private static UserSettings userSettings;
private UserSettings() {}
public static UserSettings getInstance() {
if (userSettings == null) {
userSettings = new UserSettings();
}
return userSettings;
}
public Locale getLocale() {
...
}
}
It is quite important to realize that Spring
beans' singleton pattern, resp., scope,
fundamentally differs from the Singleton Pattern the GoF (Gang of
Four) described.
Firstly, Spring beans are typically not meant to maintain
state that is specific to a client. Rather, they act as
services, where all necessary parameters are passed to the methods
being executed.
Furthermore, in contrast to the "traditional" Singleton
approach, only one bean instance exists per application
context, not per classloader.
It is possible to define Spring beans in other
scopes than
singleton. With
prototype-scoped beans, a new instance is created for
every
BeanFactory.getBean(..) call. Additional,
pre-defined, scopes include the HTTP
request scope. See
chapter
Bean Scopes for a
discussion.
Structural Design Patterns
Exporter Pattern
Problem
An object needs to be exposed to an API (or infrastructure)
that requires a specific interface to be implemented, or a specific
class to be inherited from. The object, however, does not fulfill
that contract because it needs to be independant from that target
infrastructure, or because the Java language does not support
multiple inheritance.
For instance, an object shall be made available remotely via
RMI. However, it does not implement the necessary
Remote interface, and does not throw
RemoteExceptions.
Solution
An exporter locates and wraps the target object, delegates to
it, and provides the target infrastructure with an object that
fulfills the required contract.
The Adapter Pattern is quite similar in structure,
however, it usually implements the delegating functionality more
hard-coded. - A Spring exporter normally just needs to be
configured:
<bean id="rmiServiceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter"
p:serviceName="VendorRmiConnector" p:serviceInterface="com.vendor1.erp.remoting.rmi.RmiService"
p:registryPort="1199">
<property name="service">
<bean class="com.vendor1.erp.remoting.rmi.RmiServiceImpl"></bean>
</property>
</bean>
That way arbitrary Spring beans can be exported to
all Remoting infrastructures Spring supports by mere
configuration.
Proxy Pattern
Problem
A target object is to be accessed but, for various possible
reasons, it shall not be accessed directly.
For example, the target object exists in a remote system, and
additional code is required to obtain an instance of it, while it
is desirable to treat it as any other local object. Other reasons
include the need to perform additional security checks, or to cache
one or more instances of the target object, etc.
Solution
A proxy object implements the target object's interface,
delegates to the target object, may perform additional actions, and
can be accessed in place of the target object.
In Spring, for example, an RmiProxyFactoryBean is used to create a proxy for a
remote object:
<bean id="vendorRmiConnector" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
p:serviceUrl="rmi://localhost:1199/VendorRmiConnector"
p:serviceInterface="com.vendor1.erp.remoting.rmi.RmiService" />
The above statements, such as, the proxy
delegates to the target object, are simplyfied: Actually, the
factory bean performs the delegation and additional work.
Data Access Object (DAO) Pattern
Problem
Persistent data - typically modeled in object-orientated
entities (entity beans, domain objects) - needs
to be accessed and manipulated.
If, however, these portions of code are mixed with other logic
(i.e., business logic), the separation of concerns
principle gets violated: Maintainability decreases, changing the
underlying data access technology becomes difficult, and testing
the business logic independently of the persistence layer becomes
complicated.
Data access code should be encapsulated and
interchangeable.
Solution
For each domain object, there is a DAO (Data Access
Object) that contains code to access and manipulate the data,
but hides the persistence technology it is using.
There is a challenge with object networks (when a
domain object refers to one or more other, related, objects).
Depending on a number of factors, eager fetching or
lazy fetching may be preferrable.
In this article's sample application, the DAOs reside in the
com.acme.sample.dao.impl.hibernate and
com.acme.sample.dao.impl.jdbc packages.
Decorator Pattern
Problem
An existing class' functionality shall be extended without
modifying it.
Solution
In
Spring AOP, proxies
(so-called advices) add functionality to proxied classes and
delegate calls to them while implementing their interfaces.
In basic Java programming, the
decorating functionality is often achieved by passing the
target object as a constructor argument, i.e.:
public GZIPOutputStream(OutputStream out) .. { ...
Exception Translator Pattern
Problem
Instead of "reinventing the wheel", the Spring framework
commonly uses other, existing, frameworks. In instance, it wraps a
number of data access frameworks, under a unified API.
All of these frameworks used throw their own exception types.
Thus, different types of exceptions may be thrown for the same
cause by different frameworks. In part, the exceptions' causes are
not even reflected by their type, but hidden in the exceptions'
messages.
The exception hierarchy of different frameworks should be
unified.
Moreover, often checked exceptions are thrown.
Checked exceptions need to be excplicitely handled in code. On the
other hand, many exceptions - for example, most data access
exceptions - are fatal, which means, they cannot be
recovered, anyways. The - mandantory - handling of such
exceptions is tedious and unnecessary work for programmers.
Fatal exceptions should be unchecked (derive from the
RuntimeException type).
Solution
Spring translates exceptions thrown by underlying frameworks
into its own, unified, exception hierarchy. Examples are
DataAccessException and
TransactionException and their subclasses.
These, translated, exceptions are unchecked: They do
not need to be handled but, of course, they can be if that is
useful.
Spring's DataAccessException hierarchy is
illustrated by the following image:
Behavioral Design Patterns
Listener Pattern
The Listener Pattern is close-by related to the
Observer Pattern, which is not discussed in this article.
See a
Sun Java Tech Tip for more
information.
Problem
One or more objects need to be notified on certain events of a
target object.
Solution
EventListeners implement a specific interface,
which defines one or more methods, in which
EventObjects are passed. These event objects may
contain information on the state of the target object, or even a
reference to it, so they can access or manipulate it.
The event listeners register (possibly also
unregister) with the target object, they subscribe to be
notified.
The target object provides methods to register (add) and
unregister (remove) listeners that implement the aforementioned
specific interface. When appropriate, it calls the aforementioned
interface methods on any registered listener (it thereby
publishes an event).
Spring's AbstractApplicationContext contains the
method addListener(ApplicationListener) to allow a
listener to be subscribed. The ApplicationListener
interface defines the method
onApplicationEvent(ApplicationEvent), which the
AbstractApplicationContext (indirectly, in this case)
calls on any registered ApplicationListener.
An
ApplicationListener implementation is Spring's
central
DispatcherServlet (with its superclass
FrameworkServlet implementing the
onApplicationEvent(ApplicationEvent) method. If the
ApplicationEvent is of type
ContextRefreshedEvent (which means, all beans have
been configured), the
DispatcherServlet will
initialize its
Strategy
objects, including bean handler mappings and view resolvers.
Strategy Pattern
Problem
Depending on a context, alternative algorithms shall be used
for the same task.
Solution
The tasks are encapsulated in classes which all implement the
same, strategy, interface. The context application
instantiates a strategy implementation, possibly, based on
configuration.
In Spring, Scope is such a strategy interface.
Depending on the implementation used, different rules apply
regarding the decision on whether to create a new bean or reuse an
existing instance.
In the sample application, the DAO interfaces (in package
com.acme.sample.dao) are implemented by Hibernate and
JDBC implementations. Implementations that were using other
frameworks could be added and interchanged by merely changing the
bean configuration.
Template Pattern
Problem
In commonly used operations there are amounts of
variant and invariant code.
Specifically, APIs are used that require external resources
that need to be freed after operating on them completes (whether
successfully or unsuccessfully).
For example, with JDBC, all, the ResultSet, the Statement,
and the Connection must be closed. Error
handling, in this case, can be tedious and error-prone.
Resource handling, as well as other common operations,
should be externalized to a central place.
Solution
Template objects take care of handling resources. The code
that needs those resources gets passed to the template. Usually,
the template defines an interface that the code must implement. The
template then provides the resources to the code, executes the
code, and finally frees the resources.
Instead of implementing an interface, such code can be passed
to the template as an anonymous inner class - a callback object.
This inner class has access to the template's instance variables,
if needed.
In the following example, an anonymous RowMapper is passed to the JdbcTemplate:
return (List<BusinessUserWrapper>) this.jdbcTemplate.query(query, new Object[] { loginID },
new RowMapper() {
@Override
public BusinessUserWrapper mapRow(ResultSet rs, int rowNum)
throws SQLException {
BusinessUserWrapper user = new BusinessUserWrapper();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setSpringSecurityID(rs.getInt("user_id"));
return user;
}
}
);
Templates may also provide convenience methods for commonly
used operations, for example, there are several overloaded
query(..) methods defined in the
JdbcTemplate type.
Resources
All links retrieved at the date of
publishing.
Spring Framework (Core)
- Johnson, Rod:
Introduction to the Spring Framework 2.5, theserverside.com,
2007
- Johnson, Rod, et al.:
Spring Framework - Reference Documentation,
springframework.org, 2008
- Lorimer, R. J.: Spring: A Quick Journey Through Spring AOP,
javalobby.org, 2005
- Wolff, Eberhard: Spring 2 - Framework für die Java-Entwicklung,
dpunkt.verlag, 2007
Spring Integration (Remoting, etc.)
- A. J.: BigInteger and hessian. Using custom hessian
Serializers with Spring, ayax79.wordpress.com, 2009
- Anonymous: hessian binary web service protocol, Caucho
Technology, Inc., 2008
- Anonymous: OpenEJB EJB3 Examples,
openejb.apache.org
- Anonymous: Spring EJB and JPA, openejb.apache.org
- Burke, Bill & Monson-Haefel, Richard: Enterprise JavaBeans
3.0, O'Reilly Media, Inc., 2006
- Gredler, Daniel: Java Remoting: Protocol Benchmarks,
daniel.gredler.net, 2008
- Jendrock, Eric, et al.: The Java EE 5 Tutorial, java.sun.com, 2008
- Shannon, Bill: JavaTM Platform, Enterprise Edition (Java EE)
Specification, v5, Sun Microsystems, Inc., 2006
- Sierra, Kathy & Bates, Bert: Head First Servlets and JSP,
O'Reilly Media, Inc., 2008
- Winterfeldt, David: Embedded Spring Web Services,
springbyexample.org, 2009
Spring Framework (Other)
- De Smet, Geoffrey & De Bruyker, Peter: Spring RichClient Reference Guide,
spring-rich-c.sourceforge.net, 2008
- Johnson, Rod: Open Source, Open Strategy: The SpringSource
Manifesto, blog.springsource.com, 2008
- Johnson, Rod, et al.: Spring Security - Reference Documentation,
springframework.org, 2007
- Poutsma, Aren, et al.: Spring Web Services - Reference
Documentation, springframework.org
- Winterfeldt, David: Spring by Example, springbyexample.org,
2009
Hibernate ORM
- Anonymous: Hibernate Annotations Reference Guide,
(JBoss, 3.4.0.GA)
- Anonymous: How inheritance works in Hibernate,
simsonlive.wordpress.com, 2008
- Anonymous: Generic Data Access Objects, Red Hat, Inc.,
2009
- Bauer, Christian & King, Gavin: Java Persistence with
Hibernate, Manning Publications Co., 2007
- Iltchenko, Andrei: Hibernate doesn't support optional one-to-one
associations, Atlassian JIRA, 2006
- King, Gavin, et al.: Hibernate Reference Documentation, Red
Hat, Inc., 2009
Testing
- Anonymous: JUnit Testing Framework, junit.sourceforge.com,
2009
- Anonymous: HttpUnit Testing Framework,
httpunit.sourceforge.net, 2008
- Anonymous: Inclusions and Exclusions of Tests
(Maven), maven.apache.org, 2008
- Anonymous: JWebUnit Testing Framework,
jwebunit.sourceforge.com, 2009
- Anonymous: Spring Support For Testing, De Luca
Consulting, 2008
- Aston, Philip & Fitzgerald, Calum: The Grinder
Testing Framework, grinder.sourceforge.net, 2009
- Beust, Cedric: TestNG Testing Framework, testng.org, 2009
- Johnson, Rod: System Integration Testing Using Spring,
InfoQ, 2007
- Penchikala, Kavitha & Penchikala, Srinki: Software Testing With Spring Framework, InfoQ,
2007
Design Patterns
Other Resources