Software developers in general look for the following in a framework or tool:
- An easy and rapid way to get their development tasks done
- An easy way to change existing software (Refactor) for the better.
In the current J2EE development environment, the preferred method of configuration is XML. XML and Java seem to walk hand in hand. The structure provided by XML serves well as the place where configuration meta information is defined.
The Spring framework also uses the services of this heavenly match up to provide bean definitions and Dependency Injection. One of the complaints/concerns that I have heard from potential adopters and current users is that Spring is very configuration heavy and one has to deal with a lot of XML. The concern is valid and these concerns are abated to an extent via different strategies such as segregating beans across multiple configuration files, auto-wiring of beans, being selective about what are the dependencies that really need to be injected etc. However, the "Silver Bullet" answer seems elusive.
For the sake of discussion, consider a Simple Airline Application using Spring:
Without going into the anaemic data model debate, the following is a typical structure of layered MVC application:
- Spring Controllers
- Business or Service Delegates
- Data Access Objects
Spring Controllers service a request from the browser by delegating their business processing to service delegates which in turn utilize Data access objects to perform CRUD operations.
When developing a layered architecture, not all components of the different layers are available at the same time. Developing to interfaces and providing Mock implementations (Dummy objects or using JMock, Easy Mock etc) helps decouple the layers and facilitate the development of each layer independently. Once the components in different layers become available they can be "glued" for integration testing. This pattern has helped me on many projects and has led to rapid development in stringent time lines.
As an example, consider the following Flight Application:
The spring configuration without auto-wiring:
/**
* Controller
*/
public class FlightController extends MultiActionController {
private FlightService flightService;
......
public ModelAndView searchFlight(HttpServletRequest request,
HttpServletResponse response) throws Exception {
....
}
public ModelAndView bookFlight(HttpServletRequest request,
HttpServletResponse response) throws Exception {
....
}
}
/**
* Flight Service Definition.
*/
public interface FlightService {
public FlightSearchResult searchFlights(FlightSearchRequest request);
public Ticket bookFlight(BookingRequest request)
throws NoSuchFlightException, NoSeatAvailableException;
....
// Other definitions
}
/**
* Implementation of a Flight Service. Books flights, gets flight
* matching a criteria etc...
*/
public class FlightServiceImpl implements FlightService {
private FlightDAO flightDAO;
.....
//Setters/Getters, business logic etc.
...
}
/**
* Flight DAO definition.
*/
public interface FlightDAO {
public void savePassenger(...);
...
public Flight findFlight(Long id);
}
/**
* Hibernate implementation of the flight DAO
*/
public FlightDAOHibernate extends HibernateDAOSupport implements FlightDAO {
......
// Actual Hibernate implementation of the Interface.
}
/**
* Mock or Dummy Implementation of the Flight DAO
*/
public class FlightDAOMock implements FlightDAO {
...
// Mock implementation
}
<beans>
<!- A URL Mapper to map a 'url' to controller -->
<bean id="urlMapper"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/flight/search.html">flightController</prop>
<prop key="/flight/book.html">flightController</prop>
......
</props>
</property>
</bean>
<!-- One step below to map a key to a particular method -->
<bean id="propsResolver"
class="org....mvc.multiaction.PropertiesMethodNameResolver">
<prop key="/flight/search.html">searchFlights</prop>
<prop key="/flight/book.html">bookFlight</prop>
.....
</bean>
<!-- Controller class that uses the property resolver.
Note injection of Flight Service -->
<bean id="flightController"
class="com.welflex.flight.web.controller.FlightController">
<property name="methodNameResolver" ref="propsResolver"/>
<!-- Injection of flight service -->
<property name="flightService" ref="flightService"/>
</bean>
<!-- Transaction Template -->
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- Flight DAO -->
<bean id="flightDAO"
class="com.welflex.flight.dao.impl.FlightDAOHibernate">
...
</bean>
<!-- Actual Implementation of the Flight Service. Note injection of Flight DAO -->
<bean id="flightServiceTarget"
class="com.welflex.flight.service.impl.FlightServiceImpl">
<!-- Injection of Flight DAO -->
<property name="flightDAO" ref="flightDAO"/>
</bean>
<!-- Wraps the actual service with transaction support -->
<bean id="flightService" parent="txProxyTemplate">
<!-- Injection of Target service -->
<property name="target" ref="flightServiceTarget"/>
</bean>
....
</beans>
The XML Bean Bloat and auto-wiring problem:
As seen above there is a lot of bean definitions present in the configuration file that need to be wired (dependencies setup) together, i.e:
- Define and Inject Service level beans into controllers
- Define and Inject Data Access Objects into the Services
- Define resources and other ancillary artifacts like Data sources, transaction helpers etc.
As seen above, we are not only defining the beans but also defining what gets injected into them. The avoid the latter, we can use Spring's auto-wiring ability.
Auto wiring of the beans can be accomplished by name, by type, etc. Clearly auto wiring by type suffers when one has more than one bean of a type, the auto wiring will fail. For example, if I had two beans defined, a flightDAOHibernate and a flightDAOMock with both implementing the same interface of FlightDAO, the container will not be able to choose between the two implementations for injection.
Auto wiring by name looks like a good idea but results in having to explicitly give names to the various beans.
Further, there is a lack of fine grained control where one cannot auto wire some properties and not others, auto wire some by name and others by type etc.With the above stated, the general best practice has been, its OK to use auto-wiring if your app is small, i.e., few bean definitions, however, if you application is larger, it is better to use explicit wiring. There are ways to keep the XML files manageable by splitting the definitions across multiple files segregated by layer for example.
But regardless, there seems to be no escape from having to define the beans. The direction of having to have to go to XML to define the beans and the wiring has detracted some people from adopting Spring. Are we heading toward a meta-data hell here?
As an additional note, the Java code does not contain information as to what is injected and what is not, one has to fall back to the XML configuration to figure out the injections.Of course, we can mark via documentation as to what is injected etc but then we need to ensure that the documentation is updated when injected properties are added or removed. Additionally, there is no way of validating the documentation or enforcing its practice.
There has to be an easier way where we can get away from the bloated bean definitions when dealing with a decoupled layered architecture :-)!
Annotation Based Auto wiring:
To read more about annotations, the tutorial from Sun is the place to start (http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html)
Spring 2.0 introduced a powerful concept of recognizing stereotypes with the introduction of the @Repository annotation, an annotation that served as a marker for DAO level code. Spring 2.5 went further by adding additional stereotypes like @Controller, and @Service to serve as markers for Controller and Service level objects respectively. Spring 2.5 also introduced a generic marker annotation, @Component which the other stereotypes extend and denotes a Spring managed object.
With Spring 2.5, the ability to auto-wire using annotations was introduced.
The @Autowire annotation allows for more fine grained control than was possible with prior versions, i.e., control over what properties are auto wired and how they are wired, i.e., fields, constructors and methods. For an excellent article about the @Autowired annotation, refer to the article by Mark Fisher at (http://www.infoq.com/articles/spring-2.5-part-1)
The original Java code has been altered to use stereotype annotations and the @Autowire annotation as shown below:
/**
* Flight DAO marked with @Transactional annotation.
* Most of the methods here are readonly.
*/
@Transactional(readOnly = true)
public interface AirlineService {
public FlightSearchResult searchFlights(FlightSearchRequest request);
/**
* Transaction marked for rollback in case of exception when booking a flight.
*/
@Transactional(rollbackFor = {NoSuchFlightException.class, NoSeatAvailableException.class})
public Ticket bookFlight(BookingRequest request)
throws NoSuchFlightException, NoSeatAvailableException;
}
/**
* True hibernate implementation of the Flight DAO marked
* with the @Repository annotation
*/
@Repository
public class FlightDAOHibernate implements FlightDAO {
...
}
/**
* @Repository tag not required. Done to demononstrate
* multiple implementations of an interface.
*/
@Repository
public class FlightDAOMock implements FlightDAO {
}
/**
* Flight Service Implementation. Marked with the @Service implementation.
*/
@Service
public class FlightServiceImpl implements FlightService {
@Autowired
private FlightDAO flightDAO;
...
}
/**
* Controller class changes with not having to explictly extend
* MultiActionController.
*/
@Controller
public class FlightController {
// Flight service will be auto-wired in
@Autowired
private FlightService flightService;
......
@RequestMapping("/search.html")
public ModelAndView searchFlight(HttpServletRequest request,
HttpServletResponse response) throws Exception {
....
}
@RequestMapping("/book.html")
public ModelAndView bookFlight(HttpServletRequest request,
HttpServletResponse response) throws Exception {
....
}
}
The following are the changes that have been effected:
- Removed the transaction XML declarations in favor of the @Transactional annotation.
- The XML definitions of the Service, DAO and Controller have been replaced by stereotypes such as @Service, @Controller and @Resource which are defined in the java source. Further the use of the @Autowire annotation has helped in autowiring the components together.
- By using the @RequestMapping annotation, the mapping between web request to Controller method have been defined right in the Java class. Additionally, the FlightController is no longer extending the MultiActionController class.
The Bloated XML configuration has now reduced to:
<beans>
<!-- required for autowiring -->
<context:annotation-config/>
<!-- Scan the package and below for stereo types-->
<context:component-scan base-package="com.welflex.flight">
</context:component-scan>
...data sources etc...
</beans>
Rather small compared to the earlier configuration wouldn't you say?
The
It is to be noted that when beans are auto wired by type using annotations, the problem that existed with Spring pre-2.5 auto-wiring, i.e., where the container fails to auto wire when there is more than one implementation of the expected type is still an issue.
In the above example, there are two implementations of the FlightDAO, i.e., the FlightDAOMock and FlightDAOHibernate. Spring cannot determine which of the two mentioned beans should be injected into the FlightServiceImpl and will fail to auto-wire.
So what does one do in such a case ?:
1. Explicitly define Beans in Spring config files when there are multiple implementations of an injected type:
Note that in practice one might not have a single file for "true" and "mock" objects but have them separated in different files. I guess that depends on the way you choose your strategy. For the sake of this example, consider that both the beans are defined and available for the spring container to detect as shown below:
<beans>
....
<!-- Hibernate impl -->
<bean id="flightDAOHibernate" class="com....FlightDAOHibernate"/>
<!-- Mock Impl -->
<bean id="flightDAOMock" class="com...FlightDAOMock"/>
...
</beans>
The FlightServiceImpl class can decide which implementation to pick up using the @Qualifier tag which explicitly states the implementation the service expects to be injected as shown below:
public class FlightServiceImpl implements FlightService {
// Explicitly asking for the flightDAOHibernate
@Autowired
@Qualifier("flightDAOHibernate")
private FlightDAO flightDAO;
...
}
Spring will inject in the flightDAOHibernate. But we are back to defining our beans in XML :-(. Also we are forcing the implementation right in the code.
2. Use Annotations/Stereotypes and/or filtered component scanning:
Let us define a marker annotation called 'Mock' that will be applied to the objects that are mock or dummy implementations. The objective is to ensure that the true implementations are auto wired by Spring when running in an integration environment, i.e., objects such as flightDAOHibernate are injected and not their mock counterparts. The following is an annotation that will serve to 'mark' mock objects in the system.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Mock {
}
Mock objects like the FlightDAOMock are tagged with the Mock annotations as shown below:
@MockNow when using unit tests or when the real Hibernate DAO is not ready, the mock implementation are what should be used. But as mentioned when testing integration of the layers, one would need to use the true implementation of the FlightDAO.
public class FlightDAOMock implements FlightDAO {
...
}
This selection can be accomplished using selective classpath scanning of components to ignore the Mock implementations. The following shows the filtering of Mock objects when running in an integrated environment:
<context:component-scan base-package="com.welflex.flight">
<context:exclude-filter type="annotation"
expression="com.welflex.flight.util.annotation.Mock"/>
</context:component-scan>
The above component scanning will exclude objects marked with the Mock annotation.
As an alternative to using the @Mock annotation, if the test structure was under a test package, eg: package of com.weflex.flight.test, the class path scanning rule could be changed to exclude the classes under the test package.
So which beans are a case for annotated auto-wiring and which beans are a case for XML definition ? :
I don't think there is a clear definition of what is the best practice regarding whichobjects are best suited for annotated auto-wiring and which need explict XML definition.I guess it depends on a case by case basis and what direction is chosen.
From a personal perspective, I would choose to use the annotations for the defined stereotypes such as @Controller, @Service, @Repository objects. In cases where there is ambiguity regarding the types that are available, I would explicitly define the beans in XML and use the @Qualifier tag to select implementations.To separate by environment, I would definitely use custom annotations where possible.
Database resources, LDAP Resources, JMS Resources are some of the definitions that I beleive are best defined in XML configuration. Having more than one data source for example could lead to auto-wiring failure. In such a case the use of @Qualifier annotation would help select the right data source.
For other beans, the annotation driven approach seems to provide a promising solution.
Code Example:
I have linked herewith an example that I used for understanding Spring 2.5. In the example, I have explored the various 2.5 tags. The code itself has been largely based on the spring-ws example code by Arjen Poutsma. I have used MyEclipse 6.0 with Tomcat, My SQL and Hibernate. The code can be accessed from Code Samples. There are two files of interest, namely FlightReservation.zip and AirlineSQLBackup.sql. You will need to import the database into MySQL and then create a user with login airline and password airline. Deploy the application on Tomcat and see it in action.
Conclusion:
Sun had a great concept with annotations way back with the @deprecated and @author annotations. The introduction of 'Common Annotations for the Java Platform' with Java EE 5 makes the java language so much richer and easier to use. Spring's foray in the annotation based development model makes Spring so much a viable option IMO to the wary adopter. Some additional annotations are demonstrated in the FlightReservation example, i.e., annotations such as @Aspect, @PostConstruct and @PreDestroy.
As most people who have been deterred by the "XML Bean Bloat", I would suggest they re-think the option of using Spring 2.5 and onward. For my fellow developer:
1. The use of annotations, sterotypes etc can really make the process of developing with Spring straight forward, i.e., without having to contend with the XML Bean bloat problem.
2. Refactoring and researching existing code becomes rather easy as one needs to only look at the java source to determine:
- Which beans are being injected and which are not.
- Where and how transactions are applied.
- Which web requests map to which methods in a Controller.
Neat Links:
- Server side article on Spring 2.5 introduction. A must read.
- Java Annotations
- Nice Blog by Seema Richards
RAMBLING END....:-)
5 comments:
great article. Thank you for sharing
You're a born writer Sanjay -- that was a great article!
It is very detail, easy to understand. Ray was 100% correct, you are good writer. I have bookmarked your blog to catch up your exploration.
If your "multiple implementations" of identical components consist mostly of mock/live versions, its easy to set up <context:component-scan> with filters that include/exclude mock/live.
Hello all,
Pls correct the link containing source code since I found incorrect link.
AA Ries
Post a Comment