DEV Community

Cover image for Multiple Hierarchical Contexts in Spring Boot
Nikola Micić
Nikola Micić

Posted on

Multiple Hierarchical Contexts in Spring Boot

TL;DR

In addition to creating only one ApplicationContext in a typical Spring Boot application, there is a possibility to create multiple instances of ApplicationContext in a single application. These contexts can form parent-child relationships between them. This way we don't have any longer a single ApplicationContext containing all beans defined in the application. We can rather have a hierarchy of contexts each containing beans defined within themselves. Forming the parent-child relationships between contexts, accessibility of beans defined in a context can be controlled.

Introduction

In a typical Spring Boot application written in Java, our main method will look similar like the one below.

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }

}
Enter fullscreen mode Exit fullscreen mode

Static method run from SpringApplication class will create a single ApplicationContext. The context will contain all custom beans defined in the application, as well as beans which will be created during Spring's auto-configuration process. This means that we can request any bean from the context to be auto-wired, with help of Spring's dependency injection mechanism, wherever in our application. In most cases this will be preferred behavior.

However, there could be situations where we don't want that all defined beans are accessible within the whole application. We might like to restrict access to some specific beans because a part of the application shouldn't know about them. The real-world example could be that we have a multi-modular project, in which we don't want that beans defined in one module know about beans defined in other modules. Another example could be that in a typical 3-layer architecture (controller-service-repository) we would like to restrict access to controller beans from repository or service related beans.

Spring Boot enables creating multiple contexts with its Fluent Builder API, with the core class SpringApplicationBuilder in this API.

Single context application

We will start with a simple Spring Boot application having only one context. The application will be written in Java and will use Maven as building tool.

The application will contain only core Spring and Spring Boot dependencies with a help of spring-boot-starter. Resulting pom.xml file should look like the one on the link.

There are two different packages in the application

  • web package
  • data package

both containing relevant beans (WebService and DataService).

Source code of the single context application where WebService has dependency on DataService can be found here.

If we look into the main method and retrieve the context from the SpringApplication's run method we can query for beans in the context like in the code snippet below.

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        final var context = SpringApplication.run(MainApplication.class, args);
        final var webService = context.getBean(WebService.class);
        final var dataService = context.getBean(DataService.class);
    }

}
Enter fullscreen mode Exit fullscreen mode

Spring will resolve both beans and return singleton instances (by default) for both classes (WebService and DataService). This means that both beans are accessible from the context via getBean method and Spring can auto-wire an instance of DataService into the instance of WebService via dependency injection mechanism.

We could achieve reversed scenario where DataService has dependency on WebService. Spring will be able to auto-wire bean of type WebService into the DataService bean.

The source code with reversed dependency direction can be found here.

All this is possible because all beans are part of the same context.

Multiple hierarchical contexts

With a help of SpringApplicationBuilder we can create multiple contexts in our application and form parent-child relationships between them. This way we can restrict access to the WebService bean in the way that it cannot be injected into DataService.

The rule for multiple context hierarchy with parent-child relationship is that beans from parent context are accessible in child contexts, but not the other way around. In our case, a context which defines WebService will be a child context of the context which defines DataService. Another rule is that a context can have only one parent context.

This could be achieved in a couple of steps

  • Step One would be to create context for web related beans in a new class, annotated with @Configuration , in the web package. This context will scan all beans defined within the web package with a help of @ComponentScan. Annotation @EnableAutoConfiguration will take care of triggering Spring's auto-configuration process.
@ComponentScan
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class WebContextConfiguration {
}
Enter fullscreen mode Exit fullscreen mode
  • Step Two would be to create a context for data related beans in a new class, annotated with @Configuration in the data package. Beans defined within the data package will be discovered the same way as the one defined in the web package.
@ComponentScan
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class DataContextConfiguration {
}
Enter fullscreen mode Exit fullscreen mode
  • Step Three is to use only @SpringBootConfiguration annotation on our main class instead of a well-known @SpringBootApplication annotation in order to prevent component scanning and auto-configuration. As we saw before, both processes (component scanning and auto-configuration) will be done in each child context separately.

  • Step Four is to use SpringApplicationBuilder class instead of SpringApplication class as in the code snippet below. Method web in SpringApplicationBuilder specifies which type of web application the specific context will define (possible values are NONE, SERVLET, and REACTIVE defined in WebApplicationType).

@SpringBootConfiguration
public class MainApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder()
                .sources(MainApplication.class)
                    .web(NONE)
                .child(DataContextConfiguration.class)
                    .web(NONE)
                .child(WebContextConfiguration.class)
                    .web(NONE)
                .run(args);
    }

}
Enter fullscreen mode Exit fullscreen mode

Context hierarchy defined above in the SpringApplicationBuilder can be represented by the following diagram.

main-data-web-contexts-hierarchy

If we now try to autowire WebService into DataService bean we will get NoSuchBeanDefinitionException stating that WebService bean cannot be found. This comes from the statement that beans defined in child contexts are not accessible from the parent context. The source code with these changes can be found here.

There will be no exception thrown in the case when we try to auto-wire DataService into WebService as beans defined in parent context are visible to beans defined in child contexts. The source code of the application with multiple contexts where WebService depends on DataService can be found here.

Parent-child-sibling context hierarchy

Another interesting hierarchy which can be applied is when we have a single parent context with multiple children contexts, where each child context forms a sibling relationship with other child contexts.

The rule for sibling contexts is that beans in one sibling context cannot access beans defined in other sibling contexts.

Let us see how our main class looks like for this case.

@SpringBootConfiguration
public class MainApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder()
                .sources(MainApplication.class)
                    .web(NONE)
                .child(DataContextConfiguration.class)
                    .web(NONE)
                .sibling(EventContextConfiguration.class)
                    .web(NONE)
                .sibling(WebContextConfiguration.class)
                    .web(NONE)
                .run(args);
    }

}
Enter fullscreen mode Exit fullscreen mode

As we can see, we have introduced additional context EventContextConfiguration in a separate package event. The context is defined in a similar way as the other child contexts.

@ComponentScan
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class EventContextConfiguration {
}
Enter fullscreen mode Exit fullscreen mode

Diagram for this kind of context hierarchy is shown below.

main-data-web-event-contexts-hierarchyt

As we can see from the diagram, all child contexts share the same parent and form sibling relationship.

If we retain the same dependency hierarchy, where WebService depends on DataService, we would get NoSuchBeanDefinitionException exception, because it is not accessible from the sibling context. The source code for this stage of the application can be found here.

Thing to note is that child contexts can still access beans defined in the parent context.

Summary

In this installment we have seen how we can create a Spring Boot application containing multiple contexts and how they can form parent-child relationships.

We have also mentioned a couple of real-world scenarios where we could structure contexts in hierarchy and how.

Also, we have seen that there are multiple ways to form parent-child relationships including sibling relationships.

References

Discussion (0)