This is part three of a three-part series on dependency injection. Today’s topic covers dependency injection in Spring and its differences from OSGi dependency injection. Parts one and two provide an overview of dependency injection and contain additional information on dependency injection in OSGi.
Spring dependency injection shares many core concepts with OSGi dependency injection. Spring “components” (also called “beans”) are the Spring equivalent to OSGi services. Like their OSGi counterparts, they are declared with a concrete class, exposed interfaces, externally set properties, filter values, and component dependencies specified by required interfaces and filters. Components can also have an activation method that is called when their dependencies are met (usually created with a @PostConstruct annotation), and a deactivation method that can be called if they are required to shut down (usually created with a @PreDestory annotation).
The main difference between OSGi and Spring dependency injection techniques is that in Spring, dependencies are not allowed to be removed after being provided. Most components are not allowed to shut down after being activated; those that can shut down are called “scoped” components that are able to seamlessly provide replacements to services that require them if they shut down. Not having to deal with losing required dependencies greatly simplifies component code. Components generally do not need to specify deactivation methods to release their resources, components do not require un-setter methods to remove deactivated dependencies, and null checks for injected dependencies are not necessary. While Spring loses the runtime code deployment feature that OSGi provides (and OSGi’s classloader mapping, which will be covered in a future post), these losses come with the advantage of requiring less code to safely handle injected components.
Another major difference is that Spring always provides proxies to injected components instead of direct references, in a manner similar to Blueprint and iPOJO. Unlike Blueprint and iPOJO which primarily use these proxies to deal with dependency availability downtime, the primary purpose of these proxies is to enable aspect-oriented programming techniques that intercept interactions between components (such as repository layer exception translation and scoped components). The use of proxies also allows circular component dependencies to exist without issues.
Spring components can utilize “scopes” that change the way they behave unbeknownst to the components that require them. Scopes allow calls to components to resolve in different instantiations of the component within different contexts. For example, adding the “prototype” scope to a component’s configuration causes all components that require it to receive their own unique instantiation of the component. The only OSGi equivalent to scoped components is using Blueprint to provide a prototype scope.
Spring provides additional scopes related to HTTP sessions which currently surpass OSGi component model capabilities. For example, adding a “session” scope to a component can allow it to be invoked as though it were a single direct reference by code that requires it, while actually multiplexing all invocations to different instantiations of the component depending on the HTTP session that was ultimately responsible for spawning the thread the invocation occurred within. This is made possible by Spring looking for specific threadlocal variables during invocations on the session scoped proxy. Without these proxies, such logic would have to be hardcoded into services that required the scoped component instead of being a property of the scoped component itself. See more information on scoped components.
XML, Annotations, and Code Portability
Spring components can either be declared and configured using external Spring configuration XML, or using Spring or CDI annotations. Declarative Services, Blueprint, and iPOJO also support using external XML and/or annotations to configure services; while Gravity Service Binder only supports external XML configurations.
Using annotations can be useful when only single instantiations of components are necessary, reducing the amount of configuration developers need to be aware of. However, XML configurations are necessary when instantiating more than one component from the same concrete class with different properties and filter values without the use of scopes. Without XML configurations, all provided and required filter names would be hard-coded Strings within annotations and each component could only exist once per concrete class, greatly reducing any ability to reconfigure the project or reuse its components in other contexts without source code modification. Fortunately, annotations and XML configurations can be used in tandem within the same project, allowing complicated dependency injection problems to be delegated to XML configurations while simple injection issues can be resolved without the overhead of external configuration.
When XML configurations are used to declare and configure components, Spring components can be POJOs that are reusable outside of the Spring framework. The only way to achieve similar functionality with OSGi is to use Blueprint or iPOJO. Using XML instead of annotations with a dependency injection framework that does not require the use of framework-specific classes can greatly enhance code portability between frameworks. When creating Spring components, using the Java standard CDI annotations instead of Spring annotations when possible for basic dependency injection functionality (e.g., CDI’s @Inject instead of Spring’s @Autowired) can also enhance code portability, since other dependency injection frameworks (such as Weld) also support CDI annotations.
The following list summarizes the Spring framework’s major components. Similarly formatted summaries for OSGi component models can be found in part two of this blog series.
– Configuration: Project-specific XML with optional Spring and/or CDI component annotations.
– Service activation: All components are eagerly activated and cannot be deactivated unless scoped. Circular dependencies are possible due to Spring’s use of proxies when satisfying dependencies. See more information on Spring’s support for scoped components.
– Code impact: Spring configuration XML can start components and inject dependencies and properties without introducing any coupling to Spring classes. CDI annotations can be used instead of Spring annotations, maintaining component compatibility with other dependency injection frameworks such as Weld. However, if Spring annotations are used, coupling to Spring-specific classes will occur.
By utilizing these dependency injection techniques, developers can create modular, reusable code that can easily be reconfigured for different situations. Components can be swapped out with mock versions to enable unit tests without calling external services, upgrades can be made while still maintaining the option of using legacy versions of components, and developers gain a common lexicon and mental model to use while discussing and designing modular Java applications. Dependency injection is an invaluable tool in creating large-scale, robust applications in any object oriented programming language; and the Spring and OSGi frameworks discussed in this post are prime implementations of this pattern that can be used as a model for understanding dependency injection in any context.
If you have questions or comments, feel free to post in the comment section below or contact us at firstname.lastname@example.org. You can also follow us on Twitter at @CrederaOpen or connect with us on LinkedIn.