JSR ?: Annotations for Dependency Injection

Summary (30 words or less): 

We propose to specify annotations to enable the reuse of injectable classes across multiple dependency injection frameworks.

Section 1: Identification

JCP members submitting this proposal: Google and SpringSource

Name of Contact Person: Bob Lee and Rod Johnson

Name of Specification Lead: Bob Lee and Rod Johnson


Initial Group Membership: Google, SpringSource

Supporting this JSR: Joshua Bloch, Paul Hammant (ThoughtWorks, PicoContainer founder), Doug Lea, Tim Peierls, James Strachan, Hani Suleiman, Jason van Zyl (Maven, Plexus), Thiago H de Paula Figueiredo (Tapestry IoC)

Section 2: Request

2.1 Please describe the proposed Specification:


2.1.1 Introduction


This JSR will specify a means for obtaining objects in such a way as to maximize flexibility, 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.

Many types depend on other types. For example, a Stopwatch might depend on a TimeSource. The types on which a type depends are known as its dependencies. The process of finding an instance of a dependency to use at run time is known as resolving the dependency. If no such instance can be found, the dependency is said to be unsatisfied, and the application is broken.

In the absence of dependency injection, an object can resolve its dependencies in a few ways. It can invoke a constructor, hard-wiring an object directly to its dependency's implementation and life cycle:

class Stopwatch {
    final TimeSource timeSource;
    Stopwatch () {
        timeSource = new AtomicClock(...);
    }
    void start() { ... }
    long stop() { ... }
}

If more flexibility is needed, the object can call out to a factory or service locator:

class Stopwatch {
    final TimeSource timeSource;
    Stopwatch () {
        timeSource = DefaultTimeSource.getInstance();
    }
    void start() { ... }
    long stop() { ... }
}

In deciding between these traditional approaches to dependency resolution, a programmer must make trade-offs. Constructors are more concise but restrictive. Factories decouple the client and implementation to some extent but require boilerplate code. Service locators decouple even further but reduce compile time type safety. All three approaches inhibit unit testing. For example, if the programmer uses a factory, each test against code that depends on the factory will have to mock out the factory and remember to clean up after itself or else risk side effects:

void testStopwatch() {
    TimeSource original = DefaultTimeSource.getInstance();
    DefaultTimeSource.setInstance(new MockTimeSource());
    try {
        // Now, we can actually test Stopwatch.
        Stopwatch sw = new Stopwatch();
        ...
    } finally {
        DefaultTimeSource.setInstance(original);
    }
}

In practice, supporting this ability to mock out a factory results in even more boilerplate code. Tests that mock out and clean up after multiple dependencies quickly get out of hand. To make matters worse, a programmer must predict accurately how much flexibility will be needed in the future or else suffer the consequences. If a programmer initially elects to use a constructor but later decides that more flexibility is required, the programmer must replace every call to the constructor. If the programmer errs on the side of caution and write factories up front, it may result in a lot of unnecessary boilerplate code, adding noise, complexity, and error-proneness.

Dependency injection addresses all of these issues. Instead of the programmer calling a constructor or factory, a tool called a dependency injector passes dependencies to objects:

class Stopwatch {
    final TimeSource timeSource;
    @Inject Stopwatch(TimeSource TimeSource) {
        this.TimeSource = TimeSource;
    }
    void start() { ... }
    long stop() { ... }
}

The injector further passes dependencies to other dependencies until it constructs the entire object graph. For example, suppose the programmer asked an injector to create a StopwatchWidget instance:

/** GUI for a Stopwatch */
class StopwatchWidget {
    @Inject StopwatchWidget(Stopwatch sw) { ... }
    ...
}

The injector might:

  1. Find a TimeSource
  2. Construct a Stopwatch with the TimeSource
  3. Construct a StopwatchWidget with the Stopwatch

This leaves the programmer's code clean, flexible, and relatively free of dependency-related infrastructure.

In unit tests, the programmer can now construct objects directly (without an injector) and pass in mock dependencies. The programmer no longer needs to set up and tear down factories or service locators in each test. This greatly simplifies our unit test:

void testStopwatch() {
    ParallelCalculator sw = new Stopwatch(new MockTimeSource());
    ...
}

The total decrease in unit-test complexity is proportional to the product of the number of unit tests and the number of dependencies.

Programmers annotate constructors, methods, and fields to advertise their injectability (constructor injection is demonstrated in the examples above).  A dependency injector identifies a class's dependencies by inspecting these annotations, and injects the dependencies at runtime. Moreover, the injector can verify that all dependencies have been satisfied at build time. A service locator, by contrast, cannot detect unsatisfied dependencies until run time.

2.1.1 Scope

This JSR aims only to standardize a proven, non-controversial set of annotations that enable injectable classes to be used portably across injectors.  It does not cover external dependency configuration, which is the process of describing outside of the source code how dependencies are to be resolved. This area is left up to the injector implementation, so as not to quash ongoing innovation.

Injector implementations can take many forms.  An injector may rely on code generation or reflection. It may configure itself using XML, annotations, a domain-specific language (DSL), or even plain Java code. A "container" may double as an injector. The annotations defined by the proposed specification will aim to minimize restrictions on injector implementations, allowing most injectors to take advantage of them.

2.2 What is the target Java platform?


This specification requires version 5 of the Java Programming Language

2.3 The Executive Committees would like to ensure JSR submitters think about how their proposed technology relates to all of the Java platform editions. Please provide details here for which platform editions are being targeted by this JSR, and how this JSR has considered the relationship with the other platform editions.


This specification is useful in and of itself. Many other JSRs define ad hoc injection mechanism and will benefit from a shared set of annotations. This specification is more general and statically checkable than @Resource, an annotation which defines injection from a JNDI context. JSR 299 is defining a dependency injection framework for Java EE, and might support these annotations.

2.4 Should this JSR be voted on by both Executive Committees?


No.


2.5 What need of the Java community will be addressed by the proposed specification?


This specification defines a best-of-breed dependency injection annotation API that multiple injector implementations will support. Programmers will be able to reuse their source code with different injector implementations. Third party libraries will be able to utilize dependency injection without depending on one particular vendor's annotations.

2.6 Why isn't this need met by existing specifications?


Dependency injection frameworks have only recently converged on a common approach. JSR 299 defines injection annotations, but it requires EJB and the semantics of its annotations aren't compatible with those of most existing injectors.


2.7 Please give a short description of the underlying technology or technologies:


This specification will consist solely of annotation interfaces for injectable classes.

2.8 Is there a proposed package name for the API Specification? (i.e., javapi.something, org.something, etc.)


javax.inject or java.inject


2.9 Does the proposed specification have any dependencies on specific operating systems, CPUs, or I/O devices that you know of?


No

2.10 Are there any security issues that cannot be addressed by the current security model?


No.


2.11 Are there any internationalization or localization issues?


No.


2.12 Are there any existing specifications that might be rendered obsolete, deprecated, or in need of revision as a result of this work?


Other specifications may want to define how their objects get injected. For example, how do I inject dependencies into a servlet?


2.13 Please describe the anticipated schedule for the development of this specification:


We plan to keep the API to a minimal, proven, non-controversial set of 6 or 7 types, so we expect this JSR to proceed as quickly as the process allows.


2.14 Please describe the anticipated working model for the Expert Group working on developing this specification.:


All of the work will be done in the open. The mailing list may only allow expert group members to post messages, but anyone will be able to follow and read the archives. The specification will be maintained using publicly-accessible source control.

2.15 It is important to the success of the community and each JSR that the work of the Expert Group be handled in a manner which provides the community and the public with insight into the work the Expert Group is doing, and the decisions that the Expert Group has made. The Executive Committees would like to ensure Spec Leads understand the value of this transparency and ask that each JSR have an operating plan in place for how their JSR will address the involvement of the community and the public. Please provide your plan here, and refer to the Spec Lead Guide for a more detailed description and a set of example questions you may wish to answer in your plan.


See 2.14

2.16 Please describe how the RI and TCK will be delivered, i.e. as part of a profile or platform edition, or stand-alone, or both. Include version information for the profile or platform in your answer.:


For the RI, we have multiple open source projects to choose from. The TCK will consist of configuration instructions (since external configuration is injector-specific), a set of objects to inject, and tests to validate those objects after injection. We'd also like to provide an annotation processor that can be used by programmers to statically check the portability of their classes. All code will be publicly available, licensed under Apache 2.0.

2.17 Please state the rationale if previous versions are available stand-alone and you are now proposing in 2.16 to only deliver RI and TCK as part of a profile or platform edition (See sections 1.1.5 and 1.1.6 of the JCP 2 document).:


N/A


2.18 Please provide a description of the business terms for the Specification, RI and TCK that will apply when this JSR is final.:


Everything will be licensed under Apache 2.0.

Section 3: Contributions

3.1 Please list any existing documents, specifications, or implementations that describe the technology. Please include links to the documents if they are publicly available.


  • Spring's dependency injection annotation API: http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/beans/factory/annotation/package-summary.html
  • A talk given by Bob Lee about DI and Guice specifically: http://crazybob.org/2007/06/introduction-to-guice-video-redux.html
  • The Guice user's guide: http://docs.google.com/Doc?id=dd2fhx4z_5df5hw8


3.2 Explanation of how these items might be used as a starting point for the work.


This specification will reuse the best parts of existing DI annotation APIs without imposing too many unwanted features on existing implementations.