Spring Framework Component Container Decomposition

In this post I explain how one could split one huge components container into a number of smaller ones

Introduction

Spring Framework Component Container (or Spring Framework IoC Container) is an implementation of the Inversion of Control principle. This is also known as Dependency Injection. So a typical application is represented as a number of components (beans, services; read java classes) with dependencies (either declared or auto discovered). The Component Container uses an .xml configuration files and/or Annotations on java classes. For more details you may refer to the Spring Framework documentation

Problem statement

Say you have an Application that uses IoC. It is now very easy to add new components and to pass mostly every component as the dependency for the first one. But in the long run it could lead to several issues.

The first one is that most of components tend to depend transitively from most of the other components. So the Application turns into a knot of undetachable dependencies. So unit tests transform into complex integration tests, where the most of the Application components has to be created.

The secondproblem is with library dependencies classpath. One could easily use library A, while library B is used in the other part of the Application. Say A depends on C v1.0 and B depends on C v2.0. Here is the problem. The ideal solution is to remove A and B libraries from main classpath and load each separately to get rid of the need of resolving an ideal version of the library C.

Splitting components

How could we solve those issues? My answer is to split the application container into a number of sub containers. So we could hide some component implementation details from the other components. We split one container into the root container and a number of sub containers. The split would provide enough isolation both for component dependencies and for classpaths.

The split process could be done in the following way. We take one big component (facade) and move all it's implementation details into a sub container. Iteratively we may hide all huge components implementations from the rest of the application and thus resolve the first issue.

Speaking of the library dependencies. Each of sub container could load classes from extended classpath in a dedicated classloader. This helps to resolve the second issue.

There are another solutions for dependency isolation. For example, you may take a look at OSGi framework. The solution below would be a much easier and it is only up to you what solution to consider.

Implementation

Say you decided to split one component container in a number of sub containers. There is the list of tasks to implement:

  • Create a sub container with parent of a root container (A)
  • Make sub container scan classes from a specific classloader (B)
  • Allow components from a sub container to depend from components from the root container (C)
  • Declaratively export some components from a sub container to root container (D)

Simple Spring Sub Container (A, C)

A child Spring container (context) is created in the following way. Call the constructor of a ClassPathXmlApplicationContext. Pass current container (from the ApplicationContextAware interface implementation), specify configuration resources and the name.

The created sub container includes parent container components in the dependencies resolution.

NOTE. Sub container configuration resources must NOT overlap with configuration resources of any other container in the application. Otherwise sub container may re-load all components from the application and crash.

I recommend to consider classpath*:META-INF/app-root-configuration-*.xml for the root context and classpath*:META-INF/app-child-configuration-*.xml for a sub container to avoid the possible clash. Same applies for classes scan for annotations as well

We put a sub container creation code into the root container component. The component implements InitializingBean to trigger sub container start.

Using Custom Classloader (B)

Spring Framework provides API to specify custom classloader for the container. This is done in the following way:

Exporting components to the root container (D)

There is an expected need to be able to export some component implementations back to the root container. This could be easily done via BeanFactory. What we need is to declare a non generic getter methods in the sub container creating component. The getter methods should be registered as factory methods in the root container (in .xml file or via annotations). For the getter implementation you may simply call a wrapping method with explicit type over ApplicationContext#<T>getBean(Class<T> t) .

NOTE. Using a generic factory method may leave the container without knowing the exact type of the component. So application may randomly fail to resolve a component dependencies in the container. That is only why I recommend to avoid generic factory methods for the scenario.

Usage example

I decided to implement the pattern in the new application I started. The main reason for me was to split the application classpath in to several modules to hide nasty dependencies in them. I realised I need to avoid the dependency resolution hell.

Thanks to the pattern I split my application into several logical sub containers. This helped me to avoid dependencies hell. The pattern also forced me to hide implementation details. I have several components in the root container that are implemented by the number of internal components in sub containers. And each sub container depends on a number of libraries with intersecting transitive dependencies sets.

The definition of a sub-container in the application is the following:

To add the next sub container I only need to add a similar class to the root container. And that is it!

Conclusion

Crazy, but one may use this technique recursively to provide even better separation and/or interface/implementation design.

Hope I covered all details. Please let me know if you'd like me to cover some gaps

comments powered by Disqus