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.
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 a preferred way to do it.
However, there could be situations where we don't want that all defined beans are accessible within the whole application. We might want 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.
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.
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(WebApplicationType.NONE)
.child(DataContextConfiguration.class).web(WebApplicationType.NONE)
.child(WebContextConfiguration.class).web(WebApplicationType.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.
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(WebApplicationType.NONE)
.child(DataContextConfiguration.class).web(WebApplicationType.NONE)
.sibling(EventContextConfiguration.class).web(WebApplicationType.NONE)
.sibling(WebContextConfiguration.class).web(WebApplicationType.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.
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.