ABSTRACT
This article summarizes our experience in simplifying application frameworks integration using Spring framework as well as provides some overview of Spring framework. We will try to show that Spring is a powerful framework, which helps you build a loosely coupled flexible architecture. It helps in decoupling components and greatly simplifies a software project. By using more pragmatic approach, Spring can boost considerably developers productivity and efficiency. In very simple and elegant way, Spring framework can address most infrastructure concerns of typical applications.
KEY POINTSBuilding business applications entails solving two types of problems. Some of these problems are unique to each application. For example, a health care management system catering to teachers has to accommodate coverage periods that span over an academic year rather than the typical calendar year. It also has to accommodate plans negotiated at the school district rather than at the employer level. These characteristics make it different than the typical heath care management system.
Other problems are common across many applications. For example, applications that have a presentation component must deal with user interaction. Applications that store objects into a relational database must deal with object-relational mapping. And so on.
The problems common across many applications provided fertile grounds for software reuse. Code reuse techniques such as class libraries and frameworks already provide the building blocks for many recurring problems. Their availability shortens development times and helps developers focus their energy on the unique aspects of their applications.
As the software platforms such as J2EE and .NET mature, the number of frameworks available for them increases. Frameworks represent skeleton applications that tackle specific problems. Examples include (in no particular order) application integration frameworks; common services frameworks; messaging frameworks; application architecture frameworks; connection frameworks; reference data frameworks; persistence frameworks; logging frameworks; caching frameworks; authorization frameworks; portal frameworks; rules management frameworks; optimization frameworks; business process management frameworks; data management frameworks; presentation layer frameworks; and so on.
With an increasing choice of available frameworks developers face the problem of using more than one framework in their applications, each of which addresses a specific problem. However integrating many frameworks while following good design practices and maintaining consistency is challenging. So how do you build applications that combine several frameworks?
Since frameworks provide large scale reuse, their usage typically affects the application’s structure (i.e., it has architectural impact). Until recently developers had limited options for providing integration among objects and frameworks within their application. In J2EE the only framework that partially addressed some of these issues was the Enterprise Java Beans (EJB) specification. For several years EJB represented the only framework for managing application tier objects.
The specification covered many nice features, such as declarative transaction in container management of business objects. However the difficulties related to managing and testing business transactions as well as other problems caused the emergence of alternatives: the lightweight containers.
These lightweight containers take some of the successful ideas from EJB to bring a greater level of sophistication to non-EJB architectures. They are similar to EJB architectures in being centered on a layer of managed business service object. However, this is where the similarity ends. Instead of running inside an EJB container, business objects run inside a lightweight container. Lightweight containers manage the business objects as Plain Old Java Objects (POJO) running within the container.The most popular lightweight containers are Spring framework, Pico container and HiveMind.
Lightweight containers employ Aspect Oriented Programming (AOP) interception to deliver enterprise services. For example, we can have a business service implemented as POJO that doesn’t have the responsibility to perform transaction management.
class EmployerService {
public Collection findAllEmployers(){}
public void save(Employer employer){}
}
Then we can declare the methods of the service to be intercepted via AOP and be transaction managed. A configuration file will look like this:
<bean id="transactional-employer-service" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
<property name="target">
<bean class="EmployerService"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
We can define different transaction mangers for the "transactionManager" property in "TransactionProxyFactoryBean" without any changes to our "EmployerService" class as well as define the transaction propagation strategy for the different methods in the same class.
Unlike EJBs, service objects managed by lightweight containers don’t usually need to depend on container APIs, meaning that they don’t need to inherit from a specific framework class. This makes the (re)usable outside of the container, which in turn greatly simplifies testing and improves reusability. If transaction management is the only requirement then implementing service objects as POJO may not appear as a real advantage. However, it becomes incredibly beneficial once the application developers bring in frameworks for security, persistence, and remoting, to name just a few.
Spring is an open source framework for managing business objects. It provides integration of the middle layer of an application’s components. Spring relies on two ideas for achieving this integration: Inversion of Control (IoC) and Dependency Injection(DI).
Inversion of Control removes the responsibility of looking up resources or services from the business objects, and places it to the container. Instead of making a library call, an IoC container like Spring injects the needed resources or services into the domain objects. This Inversion of Control with dependency injections addresses the problem of wiring an application’s layers in a loosely coupled manner.
One possible solution to the above problem relies on a Service Locator that hides the underlying container. This solution achieves the look up service with Inversion of Control. Service Locator pattern implements IoC with Dependency Lookup. For example:
public void SomeBusinessObject{
DataSource dataSource;
public SomeBusinessObject(){
dataSource = ServiceLocator.lookup("java:comp/env/jdbc/MyDataSource");
}
}
In contrast, Spring provides Inversion of Control through Dependency Injections. This solution is typical of the Spring framework.Injecting a resource or service into our business objects requires either setter methods (for setter injection) or constructor (for constructor injections). The following example demonstrates the latter:
public void SomeBusinessObject{
DataSource dataSource;
public SomeBusinessObject (DataSource dataSource){
this.dataSource = dataSource;
}
}
The Spring configuration file defines the actual data source:
<bean id="jdbcDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiname">
<value>java:comp/env/jdbc/MyDataSource</value>
</property>
</bean>
<bean id="someBusinesObject" class="SomeBusinessObject">
<constructor-arg><ref bean="jdbcDataSource"/></constructor-arg>
</bean>
When Spring first initializes the object this configuration injects "jdbcDataSource" into the business object.
Spring uses application context objects to handle the location and configuration of resources such as Session Factories, JDBC data sources, and other related resources, as well as inter-object dependencies. This makes these values easy to manage and change. Dependency injection and interface implementation allow us to lower the coupling between different architectural layers. For example, this provides a mechanism for injecting mocked services or recourses for testing. As a whole, Inversion of Control with Dependency Injections solve very elegantly all problems related provides a simple and consistent approach in handling configurations as well as lowers the complexity of all other application layers.
Another very important feature that Spring provides is Declarative Transaction. Spring's declarative transaction demarcation can be applied to any POJO target object. The choices for a back-end transaction manager range from simple JDBC-based transactions to full-fledged J2EE transactions via JTA. The application service interfaces and their implementations are not dependent on Spring or Spring transaction management in particular.
The Spring framework provides fine grained transaction management and flexibility in specifying transaction strategy. During runtime, Spring frameworks intercepts (via AOP-like interceptors) calls to service methods manage the transactions according to the declared transactional properties of the service methods. For example, in the above EmployerService example we declare transaction propagation for the two methods:
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
The second declaration (which includes the "readOnly" qualifier) marks a retrieval operation, when a business transaction doesn't need to be committed or rollbacked. Spring supports additional transaction strategies and can apply them not only at the method level but also to a group of methods or classes.
Inversion of Control with Dependency Injection and support for Declarative Transaction are Spring framework features that can greatly reduce the complexity of integrating different application layers as well as fosters better OO design and greatly improves testability. Let’s look in more details at some of the key benefits of building applications with Spring.
For the past twelve months, we have been building a large enterprise Java application. We used Spring to integrate Hibernate (ORM framework), Tapestry (component based MVC web framework) and Acegi (security framework). Let’s take a closer look at using Spring for integrating independently-developed frameworks with the same application.
Spring framework supports integration with variety of O/R mapping frameworks. Out of the box Spring integrates directly with JDBC or ORM solutions like Hibernate, JDO, TopLink, and iBatis. In fact, the Spring data access architecture allows it to integrate with any underlying data access technology. Spring and Hibernate are a particularly popular combination.
For the implementation of our DAO layer, we used Hibernate framework as well. Spring provides integration with it in the following ways:
Spring offers easy, efficient and safe handling of units of work such as Hibernate Session. Generally, ORM frameworks use the same "Session" object for efficiency and proper transaction handling. Spring can transparently create and bind a session to the current thread, using either a declarative AOP method interceptor approach, or by using an explicit "template" wrapper class at the Java code level. Thus Spring solves many of the usage issues that affect many users of ORM technology.
We used Spring to provide an abstraction around the Hibernate "Session" objects, thus wrapping the session in the form of one line helper method - getHibernateTemplate(). Since Hibernate Session is implementation of Unit of Work pattern, it needs to use the same Session object for efficiency and proper transaction handling. Spring provide thread safe access to this Session object by putting in it on ThreadLocal.
<bean id="exampleDAO" class="ExampleHibernateDAO>
<property name="sessionFactory">
<ref local="hibernateSessionFactory"/>
</property>
</bean>
Hibernate session is injected into ExampleHibernatedDAO object when created. This removes from the object the burden to know how to look up for any resources that it needs.
public void save(Object obj) {
try {
Session session = SessionManager.currentSession();
Transaction tx = session.beginTransaction();
session.save(obj);
tx.commit();
session.close();
} catch (HibernateException e) {
log.error("Save failed.");
} finally {
try {
SessionManager.closeSession();
} catch (HibernateException e1) {
log.error("Problem closing session");
}
}
}
Spring on the other hand wraps all of the exception handling as well as the transaction operation in one line method invocation. The above example using Spring will look like:
public void save(Object obj) {
getHibernateTemplate().saveO(obj);
}
You can still trap and handle exceptions anywhere you need to but they are nicely wrapped.
The service layer benefited the most from Spring:
Spring provided transaction management for business objects. Our service objects are stateless. However, very often services access shared resources as Hibernate Session. Thus we had the ability to configure the service object with either declarative transaction management, AOP method interceptor, or an explicit 'template' wrapper class at the Java code level. Spring provides seamless framework support for any of the solution above. However, we used only declarative transaction since this approach was sufficient for all services in our project.
Spring injected DAO and other business resources into service objects. We employed dependency injection when we needed particular business object or resource. Consequently our development team did not have to implement the Service Locator Pattern in order to request a particular service.
Spring encouraged coding against interfaces. Using this basic OO principle yielded a pluggable design. This allowed developers to mock or stub service objects during unit testing.
The following example shows our use of declarative transaction management, dependency injections of DAO, and coding against interfaces for a service. It also instantiates the service as a singleton:
<bean id="transactionManager" singleton="true" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="hibernateSessionFactory"/>
</property>
</bean>
<bean id="transactionProxyTemplate" abstract="true" singleton="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="set*">PROPAGATION_REQUIRED</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<bean id="transactionalExampleService" parent="transactionProxyTemplate" singleton="true">
<property name="target">
<bean class="ExampleService">
<property name="exampleDAO">
<ref bean="hibernateExampleDao"/>
</property>
</bean>
</property>
</bean>
This bean factory configuration files specifies the following:
Spring will create as singletons all of the above beans, excluding the abstract "transactionProxyTemplate".
Spring intercepts the creation of "transactionManager" object and injects the bean declared with name "hibernateSessionFactory".
The abstract class declaration of "transactionProxyTemplate" class provides declarative transaction management for all children object methods starting with declared prefix name. For example, assume a bean declared as a child of "transactionProxyTemplate". If this child object has method name starting with "store..." then it is guaranteed that it will be provided read and write transaction management during invocation of this method as well as thread safe access to all common resources that this method or any other nested object methods work with.
Spring also intercepts the creation of "transactionalExampleService". The implementation of this object is "ExampleService.class". This object has "transactionProxyTemplate" as a parent, which means that it inherits all properties and transaction management declared in the parent. Also, a bean declared with name "hibernateExampleDao" would be injected via setter method at the creation time.
We didn’t really need Spring here for anything but integration. We used Spring to inject the services into facade objects. Since our façade objects kept the state of the current user progress, they hat to be instantiated as new objects every time. In order to do that, we explicitly had to specify singleton property to be false. For example:
<bean id="facadeTemplate" abstract="true" class="BaseFacade">
<property name="exampleService">
<ref bean="transactionalExampleService" />
</property>
</bean>
<bean id="exampleFacade" class="ExampleFacade" parent="facadeTemplate" singleton="false" />
In this configuration file, we have defined the bean "exampleFacade" to be created as a new object every time when it is requested by another object. Since the parent "facadeTemplate" has property with name "exampleService", an implementation object of ExampleService interface will be injected during creation time.
We needed the Spring context to access façade or service objects in our controllers. We used Spring as Service Locator /Lookup pattern in order to locate business objects. For example, accessing the façade objects would be wrapped the following way:
SomeFacade façade = getBean(someServiceName);
The implementation of this method is the follows:
protected Object getBean(final String beanName) {
return getApplicationContext().getBean(beanName);
}
private ApplicationContext getApplicationContext() {
return (ApplicationContext) getFromGlobal(Engine.APPLICATION_CONTEXT_KEY);
}
which obtains the Spring context from the web server.Alternatively we could define services or resources declaratively. Fore example, the same can be achieved with the following declaration in Tapestry page specification file:
<property-specification name="someService" type="SomeService">
global.appContext.getBean("someServiceName ")
</property-specification>
In addition to facilitating the integration of frameworks Spring also affects the complexity and testability. In closing let’s take a closer look at these qualities.
Spring is both comprehensive and modular. Spring has a layered architecture, meaning that it can be chosen to use just about any part of it in isolation, yet its architecture is internally consistent. Since Spring is designed so that applications built with it depend on as few of its APIs as possible, most business objects in Spring applications have no dependency on Spring. Consequently all business objects can be implemented as POJO. In contrast, many other frameworks (including) require implementing some framework base classes. This introduces coupling between the domain and the infrastructure.
By using Spring framework, we were very successful in reducing overall complexity in our project. Spring greatly simplified the integration with Hibernated framework, as well as provided very consistent and straightforward way of using services or resources among the different application layers.
The use of POJO objects in Spring applications can run without modifications outside the container and thus are much easier to test. Spring's inversion of control approach makes it easy to swap implementations and locations of resources such as Hibernate session factories, datasources, transaction managers, and mapper object implementations (if needed). This facilitates isolating the area under test.
Our project we had a lot of business functionality related to historical data and effectively of this data. For this reason, in order to have reliable tests, we had to use different implementation of our TimeService interface. In tests, we overridden the default implementation and that allowed us to move the time according to a particular test needs. This is the default implementation:
<bean id="timeService" class=" TimeService"/>
Then in a different Spring configuration file, which is loaded only during testing we override the previous declaration:
<bean id="timeService" class=" TimeServiceStub"/>
Also, since our DAO weren’t transaction managed, we wired them for the test to be transactional. The application dao was declared as:
<bean id="hibernateExampleDao" class="ExampleDAOHibernate">
<property name="sessionFactory">
<ref local="hibernateSessionFactory"/>
</property>
</bean>
and the bean used in tests looks like:
<bean id="transactionalHibernateExampleDAO" parent="transactionalProxyTemplate">
<property name="transactionAttributes">
<props>
<prop key="store*">PROPAGATION_REQUIRED</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
<property name="target">
<ref bean=" hibernateExampleDao"/>
</property>
</bean>
- Vendor Independence
Different server solutions have different performance and other characterizes, and there is no perfect one size fits for all solution. Alternatively, it may be found that certain functionality is just not suited to an implementation using a particular server. Thus it makes sense an application be decoupled from the vendor-specific implementations of container managed transactions and lookup resources. If there is ever a need to switch to another server implementation for reasons of functionality, performance, or any other concerns, using Spring now can make the eventual switch much easier.
However, most places seems to have already invested in a vendor’s stack and are not very likely to changed form one to another. Even in cases like that, since Spring is modular as well as free open source framework just about any part of it can be used and benefited. In our project Spring enable us with completely portable solution. The production environment, user acceptance and quality insurance testing were done on WebSphere application server. However, the developers of the team developed and tested on very light web server - Jetty. This saved a lot of time of deployment and configuration during development.
Another very important benefit was that by using Jetty server together with Tapestry frameworks, we were able to see the changes on the screen immediately after refresh without any redeployment. Spring provided out of container transaction management declarative authentication with Acegi Security framework integrated with Spring, database connection pooling, property configuration manager. All of them were integrated by Spring out of the application server container.
Spring is a powerful framework, which helps you build a loosely coupled flexible architecture. It helps in decoupling and greatly simplifies a software project development by removing dependency on the complex specification of application servers. By using more pragmatic approach, Spring can boost considerably developers productivity and efficiency. It can address in very simple and elegant way most infrastructure concerns of typical applications as well as allows you to seamlessly integrate with other framework used in a project.
I would like to thank to Dragos Manolescu, an enterprise architect at ThoughtWorks, for his great help and contributions to this article. Without his consistent guidelines I wouldn't be able to put it together.