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);
}
}
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);
}
}
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 {
}
- 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 {
}
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 ofSpringApplication
class as in the code snippet below. Method web inSpringApplicationBuilder
specifies which type of web application the specific context will define (possible values are NONE, SERVLET, and REACTIVE defined inWebApplicationType
).
@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);
}
}
Context hierarchy defined above in the SpringApplicationBuilder
can be represented by the following diagram.
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);
}
}
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 {
}
Diagram for this kind of context hierarchy is shown below.
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.
Top comments (0)