-
Notifications
You must be signed in to change notification settings - Fork 1k
Dependency Injection
Dependency Injection (Inversion of Control) was an Enterprise Software trend that started with some undoubtedly real concerns about modularity and reuse, and grew into what could be characterized as an enormous cargo cult of buzzwords, infinitely extensible magic frameworks, and XML-as-code. OpenTripPlanner originally used the Spring framework, and many months were spent fighting with or just seeking to understand the reasoning behind some of its patterns. This document was part of a process of evaluating what exactly DI was and why our tools were dragging us into doing it. We eventually realized that our use of DI amounted to serious over-engineering, and removed all traces of framework-assisted dependency injection from OTP.
The javax.inject package Javadoc describes dependency injection (DI) (also known as Inversion of Control (IoC)) is “a means for obtaining objects in such a way as to maximize reusability, testability and maintainability compared to traditional approaches such as constructors, factories, and service locators (e.g., JNDI). This process, known as dependency injection, is beneficial to most nontrivial applications.” 1
Since its inception, OTP has used Spring dependency injection to wire up its components and set component parameter values at startup. Spring is relatively complex and makes heavy use of XML for configuration. There is agreement among many OTP developers that we should move away from Spring to a more lightweight injection framework. Annotation-based dependency injection is now standardized and there are several implementations to choose from. The Java Community Process (JCP) produces Java Specification Requests (JSRs) 2.
Spec | Name/Description | Full reference implementation |
---|---|---|
JSR-299 | CDI: Java EE Contexts and Injection | Weld |
JSR-330 | Dependency Injection for Java | Guice 3.0 (javax.inject package provides API) |
JSR-311 | JAX-RS 1 Annotation-driven REST | Jersey |
JSR-339 | JAX-RS 2 Annotation-driven REST | Jersey 2 (http://jcp.org/en/jsr/detail?id=339) |
JSR-330 is a part of the Java6 and Java EE6 standards. It describes a small set of annotations used for dependency injection 3 (the
annotations). It leaves the implementation up to outside frameworks, including Spring and Guice. JSR-299 builds upon JSR-330. Both are provided in application servers (Java EE environments) and the reference implementation packages are what is needed to use them outside such a context. Some comments on the 299/330 distinction: “…the programmatic configuration in Guice is…clearer than [JSR-299] beans.xml (where you are back to the ‘class names are strings in a configuration file, not code’ problem).” “CDI is very JEE oriented.” “After a few projects I’ve found that I like the Guice configuration best instead of the opaque magic happening in Weld.” 4
Jersey has built-in injection capabilities. Because Jersey REST resource classes do not inherit from some common superclass, the only way to get request information into them is via injection. This is also important for REST resources and other objects that are request-scoped. But, “it would be much better if Jersey’s own legacy dependency manager would be completely removed and for Jersey just to ride on top of any standard javax.inject and/or CDI implementation.” 5 This will be the case in Jersey 2, which uses hk2 for DI. “HK2 is a JSR-330 compliant micro-kernel for service injection. It serves as the micro-kernel for GlassFish application server and the Jersey JAX-RS implementation.” 6 Jersey uses both @Context (for injecting Jersey built-ins like UriInfo) and @InjectParam (for injecting everything else).
Similarly to StringReaders and StringReaderProviders for incoming query parameters, Jersey uses injector providers to inject 7. These are implementations of InjectableProvider annotated with @Provider, which are picked up during Jersey package scanning. However, while Jersey can inject things into its REST resource classes (found by package scanning) it does not touch those injected dependencies themselves. If you want to wire together or otherwise configure those dependencies, you need an additional DI framework like Guice, Spring, etc. That framework then provides instances to Jersey via an IoCComponentProviderFactory. Examples of both methods can be found at 8. “Jersey has an explicit feature called @Inject that explicitly defers to the set of registered IoCComponentProviderFactory” 9. Jersey used to use the com.sun.jersey.spi.inject.Inject annotation in its REST resources, but this has been changed to @InjectParam, avoiding name conflics with javax.inject.Inject which is used everywhere else by JSR-330 implementations. Thus, Jersey’s @InjectParam is used on REST resources, and JSR-330’s java.inject.Inject within the services that are injected.
Jersey IoCComponentProviders must implement one of IoCManagedComponentProvider, IoCFullyManagedComponentProvider, IoCInstantiatedComponentProvider, or IoCProxiedComponentProvider (which are all subinterfaces of IoCComponentProvider) depending on whether Jersey or the DI framework is handling the lifecycle of the injected components, i.e. who is constructing and destroying them. See the Javadoc of these interfaces for the distinctions.
Guice 3.0 is integrated with JSR-330 (and is the reference implementation). “JSR-330 standardizes annotations like @Inject and the Provider interfaces for Java platforms. It doesn’t currently specify how applications are configured, so it has no analog to Guice’s modules.” 10 For configuration, Guice can load values from Java properties files and associate them with @Named annotations, which are then used to inject into constructors and the like. 11 In Guice, you collect bindings from interfaces to concrete class names together in a Module. You include that module in an injector. 12
In practice in OTP, outside Jersey REST resources we are only applying injection to fields (not constructors or method paramters) and all of our injected components are singletons. The range of functionality needed is quite small, and does not require a complex framework. See for example the OTPComponentProvider added in OTP commit 091bac427125d019683e7f31657442a4f06e6385.
Notes: Spring’s @Autowired annotation interferes with Jersey @InjectParam if they are on the same field.
Other References:
- http://www.adam-bien.com/roller/abien/entry/what_is_the_relation_between
unless you are intentionally working with legacy versions of OpenTripPlanner. Please consult the current documentation at readthedocs