Anemic and full-blooded IoC, EJB vs. Spring
Inversion of Control (IoC) is a design pattern that moves responsibility for obtaining and initializing dependencies, which are objects, outside the object that uses them. The dependency itself is supplied from outside through Dependency Injection.
Suppose object A uses object B to do its work. A depends on B. Object A contains code that creates and initializes object B. The code to create and initialize B couples the two objects tightly and is specific to the environment, for example a JNDI lookup.
With IoC, that binding code is moved outside the object. A third party makes sure object A gets an initialized instance of object B at the right time. B is passed to A through a setter or constructor, for example. This form of IoC is called Dependency Injection.
You may or may not know that Dependency Injection (DI) is one of the key features implemented in J2EE 5.0. The Spring Framework popularized IoC, and DI as a way to achieve it, and DI quickly became a goal for the traditional J2EE world. For many developers, J2EE 5.0 and the introduction of DI were reasons to give up on Spring.
Even if we set aside the fact that Spring offers much more than DI, Spring and the EJB container differ substantially in what their DI can do. Let's recap.
EJB DI
- Dependency definition — annotations and an XML descriptor
- Applicability — Servlet, listener classes, web services endpoint, JAX-RPC handlers, DataSource, JMS, Mail, EJB, environment entries, EntityManager, UserTransaction, EJB beans, interceptors, web services endpoint, DataSource, JMS, Mail, environment entries, EntityManager, EJB Context, UserTransaction, TimerService
- Indirect dependencies — no
- Injection methods — setter and field annotation
- Extension points — interceptors and lifecycle interfaces
- Autowiring — no
Spring DI
- Dependency definition — XML descriptor, annotations, properties and Java configuration code
- Applicability — unrestricted
- Indirect dependencies — yes
- Injection methods — setter, field annotation and constructor
- Extension points — AOP, BeanPostProcessor, Factory, lifecycle interfaces and type conversion
- Autowiring — by type and by name
Dependency definition and applicability
EJB essentially offers annotations, overrides in the XML descriptor or a combination of the two. Besides the methods listed above and their combinations, Spring lets you use your own ways of describing dependencies. That is how an extension using annotations with equivalent dependency-injection semantics came about.
In J2EE, annotations can only be used on a predefined set of components. Spring imposes no such restriction. This matters especially for classes that are not inherently managed components, such as helpers, collections (maps and lists), or classes outside the EJB container, for instance in the web layer. Spring has no such constraint.
The standard XML descriptor is far less expressive than Spring's. For example, you cannot define collections. That is a problem if an object depends on a list of certain objects. Starting in version 2.0, Spring also lets you define custom tags to describe beans. The greater expressiveness of Spring's XML descriptor is worth a great deal more to developers.
Injection methods
Another difference is how a dependency is injected. The EJB container offers setters and fields via annotations. Spring offers both and adds constructors. An object can receive a dependency through its constructor if its API is designed for it. A particularly useful Spring-only feature is support for indirect dependencies. If objects are unrelated in code and we still need to guarantee their initialization order, for example, we need an indirect dependency. This ensures object X is initialized before object B.
Extension points
Extension points define how IoC behavior can be extended. In addition to lifecycle interfaces, the EJB container offers interceptors for managed components. Spring offers the same sort of support, using aspects in place of interceptors. Spring also provides BeanPostProcessor, which lets you customize initialized beans. Another big plus is support for Factory objects. The Spring IoC container can delegate the creation of certain objects to a specific client factory.
Type conversion turns values written in the XML descriptor into real objects. Suppose the XML descriptor contains the value 1.1.1970 and the setter parameter type is foo.MyDateWrapper. Spring lets you define a PropertyEditor that the container will call when it encounters that type. Again, only Spring supports this.
Autowiring
Autowiring supplies direct dependencies without listing them explicitly. If an IoC container manages object A and object B, it can automatically detect A's dependency on B by type, if A has a setter for object B, or by name, if A has a setter for a property whose name matches that of object B. Only Spring supports autowiring components.
These are only the most striking differences that occurred to me when comparing the Spring IoC container and the EJB container. We could certainly find more, but the aim was to point out that different implementations of IoC can offer very different things. Java 5 came a long way and IoC made it more convenient, yet Spring still has its strengths. The core IoC component that underpins Spring and, by extension, every application built on it is one of them.
Related articles
Related CZ Podcast episodes
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi and Filemon discuss Java 6 and web application frameworks. · Czech audio
- #1 · Vývojová prostředí v Javě
The first CZ Podcast episode looks at Java development environments. · Czech audio