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.
12 comments:
Very eloquent and compelling post. Couldn't agree more =)
I'm not trying to defend Spring, but... There is no Spring without Java EE: Spring wants to be compatible to JavaEE on purpose. It even encourages you to use JEE standard annotations, like @Inject and @PersistenceContext. I see this as a good thing. Spring MVC may feel old.. that's really up to the project. Not all of them need an interface that is component oriented. API vs. Implementation: Yes, if you have a complex web application Spring will be there. It provides a lot of configuration and flexibility, but you know exactly what components your project use. Vendor Lock-In: You have this too in JEE world. WebLogic is different than jboss. You need to configure them differently. Even Jboss 7 is different that Jboss 6, which is also different that Jboss 5.1 JAR & Configuration Hell: Is it a hell? WIth more power comes more responsibility (and again, more flexibility). Thread safety & Singleton Antipattern: What you are writing is the difference between stateful and stateless. Also, the singleton bean in Spring is not the same as the Singleton pattern in Java, although they share the same name. Lookup the wikipedia definition for the singleton pattern.Mixing scopes: Since I never use the request scope or the prototype scope I cannot comment about this.Transactions: Spring does not implement JTA, but there are many existing implementations. " you need to configure a transaction manager per persistence unit". This is false, you use the same JTA transaction manager, unless you want your persistence units to use different transactions, which is a no-no. LoadTime Weaving: I never had problems with it, although I've never have used OpenJPA.Summary:I'm sure JEE 6 is excellent, but Spring is alive and awesome as always. They are both far from perfect as you say, but what is perfect? Play Framework?
@Luciano: You need to set an EntityManagerFactory on a JpaTransactionManager - one and only one.
For two or more persistence units or entity manager factories, you need a JtaTransactionManager and thus JTA - another Java EE component.
Most people who drive a car don't want to get their hands dirty on the mechanics...
I agree with the premise of this article. The value in Spring now lies in specific Spring projects like Spring Data and Spring Integration. But unfortunately these require core Spring and pretty much require YOU to use core Spring. That's not really a benefit anymore.
Very interesting and well written.
"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."-- In my opinion CDI has one big flaw id does not support Configuration and makes apps tightly coupled. They may be simplier to develop to start. But if CDI had configuraton support that would be a plus.
The JAR HELL -- Once you get used to maven that solves this issue
Open JPA and Spring on Tomcat -- You should have turned Load Time Weaving off or told it not to scan your Domain Objects. And ran OpenJPA byte enhancer.
"In my opinion CDI has one big flaw it does not support Configuration" - assuming you are talking about something like Spring Config, multiple options already exists. For most cases, you may use CDI @Produces and/or CDI @Alternative. For more extensive cases, you can use CDI plugins like Seam 3 Config, Solder or DeltaSpike Core. Finally, you can have ultimate control by using the CDI portable extension SPI.
"The JAR HELL -- Once you get used to maven that solves this issue" - at best, this makes the redundant dependency management issue a little easier than it otherwise would be but it certainly does not eliminate the issue.
"You should have turned Load Time Weaving off or told it not to scan your Domain Objects. And ran OpenJPA byte enhancer." - the point is that all that is extra work that doesn't need to be done in the first place.
You seemingly don't get it at all what Spring is about. You miss the fact that while there are tons of modules for Spring, these are all optional and you don't have to carry them around if you don't need them.
In fact, given Spring's open source nature, it's easy to create your own spring module on top of whatever component you produce, and publish it via a public maven repo.
In contrast, any JEE app server carries tons of JEE APIs around, with intricate under-the-hood dependencies between their implementations, making JEE servers huge, heavy beasts, in no way fit for deployment of modern services-based apps into lightweight docker containers or similar environments. Such services most often do not require the huge overhead imposed by JEE app servers, but can benefit greatly from the core IoC engine and the many available Spring modules.
It may be true that by using Spring you buy into a single implementation - but just that of the core IoC container, not more. You have several alternatives available for various services, and you can easily wrap in your own implementation of anything, if none of the pre-existing solutions makes you happy.
In contrast, if you build your app on JEE, you buy into one particular JEE implementation, just slightly incompatible with any other, so that moving from one app server to another is too painful to ever be done. There's no longer a mix and match of different API implementations from different vendors possible, at least not without huge headaches.
(I've seen a documented example where somebody needed to disable all of JEE on JBoss and pull in alternative implementations - luckily his app was Spring-based, so this wasn't an issue - just because management decided to move to JBoss.)
Finally, good luck porting your JEE apps to a newer, more modern, better scalable platform, such as vert.x ... - it has been at least attempted, I'm not aware of the outcome, but the process wasn't nice.
"Most people who drive a car don't want to get their hands dirty on the mechanics..." - yeah right, but as programmers we are supposed to be the mechanics. And of course I don't want to use something where I can't do my mechanic's work.
You remind me of ancient VB6 programmers. They also didn't want to get their hands dirty, and were extremely happy with a platform that just worked. For whatever reason, they are mostly extinct nowadays ...
About the 'Singleton Antipattern' and huge list of parameters: please use a parameter object instead of wild parameters. Then you have a 'context' that you pass around all stateless methods.
Post a Comment