Over the past five years, I have had the pleasure of working on different Java enterprise projects based on OSGi, Java EE and Spring. Among these three platforms, Spring is the one I'm least happy with.
Granted, Spring lets you build complex enterprise applications without too much pain, or else it wouldn't be so popular. In the times of J2EE 1.4, Spring must have felt like a breath of fresh air, and without the competition of Spring and Hibernate, J2EE might not have evolved into what Java EE 6 is today.
But somehow the glory of Spring's founding myth of
killing the beast that was J2EE seems to be fading. The former beast is now as manageable and easy to use as Spring ever was, or even more so. Dependency Injection in Java is no longer synonymous with Spring (and in fact never was).
The Spring Framework needs a new
raison d'ĂȘtre, and the current strategy seems to be diversification: Projects like Spring Data or Spring Social offer new features that have no equivalent in Java EE or OSGi. And of course, any Spring XYZ project requires the Spring Framework. But looking at the capabilities of the Spring Framework itself, where are the killer features?
Does Spring provide solutions that are smarter or easier to use than equivalent constructs in Java EE?
Here is a list of reasons why I feel more productive on Java EE 6 than on Spring 3.1.
There is no Spring without Java EE
Most debates about Java EE vs Spring sound like an either-or question, but this is a misconception. You can build an application on Java EE without ever using a Spring API, but you can't really build an enterprise application on Spring without using a number of Java EE APIs and implementations.
A typical Spring-based application includes
- a web container, usually Tomcat, which means Servlet, JSP, EL, all of which is Java EE
- a service layer, which has the highest chance of being Spring-only, with Spring transactions and Spring dependency injection
- some web service endpoints, most likely JAX-RS or JAX-WS, which means Java EE again
- a persistence layer, traditionally Hibernate Native API + Spring templates, but these days there's really no reason for preferring vendor-specific APIs over JPA 2.0
- some messaging, e.g. with ActiveMQ, which means JMS and thus Java EE again.
Thus, Spring and Java EE applications mostly differ in the following areas only:
- the web framework (Spring MVC vs. JSF vs. Wicket vs. Vaadin vs. Struts vs.....)
- Spring Beans vs. EJB
- Spring Dependency Injection vs. CDI or Java EE 5
@EJB
or @Resource
injection
In the age of AJAX and component-oriented web frameworks, Spring MVC feels rather old-school. JSF 2.1 has a lot more to offer, and if you prefer an independent web framework like Vaadin, the underlying container does not matter anyway.
Who's afraid of EJBs? All you need is a
@Stateless
annotation on your Java class, and you get a thread-safe service where all public methods are transactional by default, which is just what you need in most cases.
And finally, CDI is much more powerful that Spring Dependency Injection, due to its seamless scope handling, its event model and its portable extension mechanism.
API vs. Implementation
Spring has no clear separation of API and implementation, neither at specification level nor at code level. The Spring Reference Manual documents the one and only Spring Framework implementation. The Spring Javadocs document each and every class contained in the Spring Framework. It is not easy to distinguish API level classes from container internal classes.
By contrast, Java EE is first and foremost a set of specifications, represented by a collection of thin API JARs or an all-in-one
javaee-api.jar
. There are different independent implementations by different providers. Provider specific implementation classes are not normally visible to the user.
Vendor Lock-In
Competition is a good thing. When you are having an issue with your application running on a given Java EE server, try another server. This will give you some insight whether or not the issue is caused by your application or the server.
With Spring, there's only Spring. You can't swap framework components. You can't even cherry-pick framework components. There is insufficient separation of concerns. Inherently, there is no reason why a web framework or a NoSQL persistence provider should have to be tied to a given dependency injection container. But you can't use Spring MVC or Spring Data without Spring Core, just like you can't run Microsoft SQL Server without Microsoft Windows.
JAR Hell
Java EE means one-stop shopping for the core functionality of an enterprise software stack.
javaee-api.jar
provides the interfaces you need to start developing your own applications. All the required runtime environment is provided by the application server.
Yes, the application server runtime does contain a lot of stuff you'll never need. But modern servers like GlassFish 3.x or JBoss AS7 are modular and lazily activate only the stuff you do need, so there is no runtime overhead. And even a monolithic server like Resin 4.x has a surprisingly small footprint.
My current project is a Spring web application deployed on Tomcat. The final WAR has more than 100 JARs in
WEB-INF/lib
. More than 30 of these JARs are due to Spring itself, its dependencies or other Java EE components not provided by Tomcat.
In other words, by moving from Spring to Java EE, I could drop a third of the dependencies of my application. And I'm not talking about saving 30 megabytes of disk space, but about the effort of maintaining these dependencies and resolving version conflicts of transitive dependencies.
Configuration Hell
The flip side of JAR hell is configuration hell. With Spring, once you have gathered all required components, you also need to configure them. You may get away with an automatic component scan for your own application beans, but there is always a certain amount of manual plumbing for setting up framework beans, and standard features like transaction management or property substitution have to be enabled explicitly.
Most Spring-based projects started before 2010 still use XML configuration which for my taste is hard to read, extremely verbose and hard to refactor. Spring 3.0 first introduced Java configuration as an alternative but left it unfinished. Spring 3.1 finally supports type-safe Java-only configurations. Unfortunately, the reference manual and Spring extensions like Spring Security still promote XML configuration and leave it to the user to figure out the Java equivalents, which can be rather challenging, especially when custom Spring XML namespaces are involved.
With Java EE 6, little or no configuration is required. Transaction management works out of the box. Use an empty
beans.xml
marker file to enable CDI. Define the data sources and connections pools for your application in your application server's administration console. That's it.
Thread Safety
Spring leaves thread safety up to you. Controllers and services have singleton scope by default, so you can't use instance variables, or you need to protect them explicitly.
With Java EE, stateless EJB are pooled, concurrent requests each get served by a new beans from the pool, so your beans are thread-safe by default, unless you modify static members or global singletons.
Singleton Antipattern
The fact that Spring beans are singletons and not thread-safe by default tends to promote a rather ugly procedural programming style. Suppose you have a service with one public method performing some non-trivial business logic, factored out into a number of private methods.
In a stateless EJB, your private methods can share state in member variables
within the same request (i.e. public method call), so in fact, the EJB is not quite as stateless as it may appear.
In a Spring bean, you often see private methods with long parameters lists, since you cannot share state in member variables, so you have to pass it around in method arguments.
Mixing Scopes
Due to these issues with singleton Spring beans, you may want to use the prototype scope, or the request scope in web applications.
Now when you inject a protoype or request scope bean into a singleton bean, Spring does not by default do the right thing to ensure that the injected bean is indeed a different instance per request. You need to resort to method injection or configure a scoped proxy explicitly.
CDI, on other hand, automatically proxies injected beans when needed.
Transactions
Spring itself does not provide global transactions across multiple resources. It can delegate to a JTA transaction manager but does not provide its own implementation.
Working with two or more persistence units in the same application, you need to configure a transaction manager per persistence unit and reference the correct instance in the
@Transactional
annotation of your service class or method.
In Java EE, transactions are managed by the container by default. EJBs are transactional by default. The container can handle multiple persistence units without further configuration. Global transactions spanning multiple datasources work out of the box if you configure XA datasources.
Load Time Weaving
Some persistence providers require load time weaving, also known as byte code enhancement. The JPA specification defines a hook for this purpose in
PersistenceUnitInfo.addTransformer().
With OpenJPA and GlassFish, this works like a charm. Using OpenJPA with Spring and Tomcat, the required set-up is
rather a nightmare, so I ended up using build-time enhancement, which is a lot easier to configure, but still requires additional manual configuration.
Summary
Spring has had its merits in giving relief to frustrated J2EE developers. But this is 2012 - compared to Spring, Java EE 6 now provides equivalent or better solutions for most standard tasks in enterprise application development.
Both Java EE and Spring are
far from perfect. All in all, Java EE is more integrated and easier to use - not by orders of magnitude, but noticeably so.
And none of this is an eternal truth, tables may turn again within a few years.