Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://gitlab.com/SpaceTrucker/modular-spring-contexts
https://gitlab.com/SpaceTrucker/modular-spring-contexts
java spring
Last synced: 10 days ago
JSON representation
- Host: gitlab.com
- URL: https://gitlab.com/SpaceTrucker/modular-spring-contexts
- Owner: SpaceTrucker
- License: apache-2.0
- Created: 2016-05-22T13:30:12.277Z (over 8 years ago)
- Default Branch: master
- Last Synced: 2024-11-16T01:16:00.822Z (2 months ago)
- Topics: java, spring
- Stars: 1
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Introduction
modular-spring-contexts is a small utility library intended to make
[spring application contexts](https://spring.io/understanding/application-context)
more modular and manageable.Spring application contexts for large applications tend to contain dozens or
even hundreds of beans. Keeping an overview becomes more and more difficult and
name collisions may happen. Also every bean is able to see every other bean in
the context.# Requirements
This library currently requires Spring 4.3.x.
# Usage
Add a dependency to this library to your classpath.
```xmlcom.gitlab.spacetrucker
modular-spring-contexts```
## Define modules
A simple module definition is just a simple xml fragment:
```xml
```
This fragment defines a bean of type
[`ApplicationContext`](http://docs.spring.io/spring-framework/docs/4.3.x/javadoc-api/org/springframework/context/ApplicationContext.html)
with name `someModule`. The file `/someModuleBeans.xml` is a spring xml context
definition containing the bean definitions of application context `someModule`.It is recommended to define one spring context xml file that contains the
modules the application requires.The definition of modules requires the schema `http://www.gitlab.com/SpaceTrucker/modular-spring-contexts xsd/modular-spring-contexts.xsd` to be declared in the spring xml context definition.
### Starting modules
Modules are started by using the static factory method
`ModuleStartup.startModules(String moduleRootConfig)`. This will create a module
that contains all the bean definitions from `moduleRootConfig`.### Module with multiple config locations
A module may define multiple config locations.
```xml
```
It is recommended to keep modules simple and therefore a module should only
contain bean definitions of one spring context xml file.### Module with dependencies
A module may define dependencies to other modules. Only beans from dependent
modules may be imported.A dependency declaration looks like this:
```xml
```
This tells spring to make `serverModule` available as a source for
[bean imports](#import) via it's wiring mechanism.`clientModuleBeans.xml` may then make use of the declared dependency like this:
```java
public class Foo implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContext serverModule =
applicationContext.getBean("serverModule", ApplicationContext.class);
// do something with serverModule
}
}
```### Nesting modules
Modules may also be nested. That means the xml bean definitions of a module may
contain other module definitions.
File `rootModules.xml`
```xml
```
File `parentModuleBeans.xml`
```xml
```
This setup will create two modules `parentModule` and `childModule`. The module
`childModule` doesn't know anything about `parentModule`, but `childModule` is
just a normal bean within `parentModule`.A module may also explicitly pass down dependent modules to nested modules
using the dependency mechanism as if the passed down module was declared in the
current module.### Parallel module startup
The startup of modules may be parallelized. Therefore an executor must be
defined in the application context that is defining the modules (see
[The `executor` element](http://docs.spring.io/spring/docs/4.3.x/spring-framework-reference/html/scheduling.html#scheduling-task-namespace-executor)).
The `module` element than allows the configuration of that executor as the one
which starts up the modules.
```xml
```
The executor will receive a task for each module it shall start. If no
`moduleStarter` is specified, modules will be started within the thread that is
initializing the application context defining the modules.### Details of the application contexts that are used as modules
A module is an instance of [ApplicationContext](http://docs.spring.io/spring-framework/docs/4.3.x/javadoc-api/org/springframework/context/ApplicationContext.html).
The module never has a parent, even though it may be a nested module.# Importing beans from modules {: #import}
The definition of modules is just one step to make large spring xml contexts
more manageable. The second step is to use imports to request specific beans
from dependent modules.
```xml```
The above xml fragment will make the bean with name `someBean` of module
`dependentModule` available to the currently defined module.Specifying the `sourceBean` is optional. If this is not specified, the value of
the `id` attribute is used instead.
```xml```
## Static bean imports
Static bean imports can be used without modules in pure spring xml application
contexts. The `sourceModule` must be a bean of type `ApplicationContext` defined
in the current application context. The bean defined by `sourceBean` is then
imported as a singleton in the current application context.## Dynamic bean imports
Dynamic imports can only be used within modules.
Dynamically imported beans will be imported into the module that is declaring
the import with the same scope as the bean is defined in the context it is
imported from.During resolving of dynamically imported beans the visibility of beans to be
imported will also checked. A bean definition can specify that it is eligible
for imports by adding a `meta` element `exported` with value `true`.
```xml
```
# Testing modules
This requires
```xmlcom.gitlab.spacetrucker
modular-spring-contexts-testing```
Since modules declare dependencies on other modules, it is possible to test
modules in isolation.The first step is to create a [Java-based container configuration](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java)
that contains the beans which are imported by the module under test from the
dependent module.
```java
@Configuration("dependentModule")
public static class TestReplacementModule {private final Object exportedBean = new Object();
@Bean
public Object exportedBean() {
return exportedBean;
}
}
```The second step is to create the test instance that declares that the module
under test will be used together with an instance of `TestReplacementModule`.
```java
public class ClientModuleTest {@MockedModule
private TestReplacementModule serverModule;@Resource
private Object beanUnderTest;@Before
public void beforeMethod() {
ModuleTesting.initIsolatedModule("/moduleUnderTest.xml", this);
}
@Test
public void shouldWork() {
// do the test
}
}
```
Using `@MockedModule` indicates that the field annotated with it is going to be
set to the module that the module under test depends on. The name of the field
must be the same as the name of the module which the module under test depends
on.Invoking `ModuleTesting.initIsolatedModule("/moduleUnderTest.xml", this);` will
initialize the `@MockedModule` fields and start the module defined in
`/moduleUnderTest.xml` using all `@MockedModule` fields as dependent modules.
After the module under test is started `this` will be autowired and therefore
all `@Resource` will be set to the spring beans contained in the module under
test.Note that beans from `@MockedModule` are also eligible for injection via
`@Resource` fields, but this should not be used. The preferred way of accessing
beans in dependent modules is via the `@MockedModule` field like this:
```java
@Test
public void shouldWork() {
serverModule.exportedBean().getSomething();
}
```## Testing modules with Mockito
In order to use [mockito](http://mockito.org) to test interactions between the
module under test and dependent modules, the module under test must depend on
modules that provide mockito mocks. This is achieved via:
```java
@Configuration
public static class TestReplacementModule {@Mock
private List exportedBean;@PostConstruct
public void postConstruction() {
MockitoAnnotations.initMocks(this);
}@Bean
public List exportedBean() {
return exportedBean;
}
}
```
Before the module under test is startet, this Java-based configuration is
created and the `@PostConstruct` method makes sure that the mocks are
initialized.During tests the mocks can be used as in any other mockito test:
```java
public class ClientModuleTest {@MockedModule
private TestReplacementModule serverModule;@Resource
private Object beanUnderTest;@Before
public void beforeMethod() {
ModuleTesting.initIsolatedModule("/moduleUnderTest.xml", this);
}
@Test
public void shouldWork() {
Mockito.when(serverModule.exportedBean().contains(Mockito.any()).thenReturn(true);
beanUnderTest.doSomething();
Mockito.verify(serverModule.exportedBean()).clear();
}
}
```## Testing module startup and shutdown
A module may interact with other modules during startup and shutdown. Tests for these phases should be put in their own tests.
In order to test startup interactions set up mocked modules with expected interactions in a `@PostConstruct` method. A Test method should then assert that an expected interaction happened. A mockito example looks like this:
```java
public class StartupModuleTestingIntegrationTest {private static class BarModuleFooServiceStartup {
@Configuration
private static class FooModule {@Mock
private FooService fooService;private Foo foo = new Foo();
@PostConstruct
public void postConstruction() {
MockitoAnnotations.initMocks(this);
Mockito.when(fooService.doSomething()).thenReturn(foo);
}@Bean
public FooService fooService() {
return fooService;
}
public Foo getFoo() {
return foo;
}
}@MockedModule
private FooModule fooModule;
// defined in barModule.xml
@Resource
private BarService barService;
}@Test
public void shouldInvokeFooServiceDuringStartup() {
BarModuleFooServiceStartup moduleUnderTest = new BarModuleFooServiceStartup();
ModuleTesting.initIsolatedModule("/barModule.xml", moduleUnderTest);Mockito.verify(moduleUnderTest.fooModule.fooService()).doSomething();
Assert.assertSame(moduleUnderTest.fooModule.getFoo(), moduleUnderTest.barService.getFoo());
// other assertions
}
}
```
The `BarModuleFooServiceStartup` class is used to encapsulate the test setup specific to how `FooService` is used during startup. Another such class could be defined for testing interactions of another bean defined in `fooModule` during startup.Interactions during shutdown are tested via test cases that close the module under test. Before the module is closed expectations for the shutdown procedure are set up. After shutdown those expectations are verified. An example using mockito would look like this:
```java
public class ShutdownModuleTestingIntegrationTest {@Configuration
private static class FooModule {@Mock
private FooService fooService;@PostConstruct
public void postConstruction() {
MockitoAnnotations.initMocks(this);
}@Bean
public FooService fooService() {
return fooService;
}
}@MockedModule
private FooModule fooModule;private ConfigurableApplicationContext moduleUnderTest;
@Before
public void beforeMethod() {
moduleUnderTest = ModuleTesting.initIsolatedModule("/barModule.xml", this);
}@Test
public void shouldInvokeFooServiceDuringShutdown() {
Mockito.when(serverModule.dependentBean().compare(Mockito.anyInt(), Mockito.anyInt())).thenReturn(0);moduleUnderTest.close();
Mockito.verify(serverModule.fooService()).doSomething();
}
}
```# Integration with preexisting spring contexts
To use modules in an already existing spring context, you can add the following
bean definition to set up modules:
```xml
```
This will set up the modules defined as bean `rootModule` in the already
existing spring context. Static bean imports can then be used to import beans
from `rootModule`.# Difference to spring boot profiles
Basically spring boot profiles are used to determine what is contained in an
application context. But profiles do not provide a mechanism to modularize an
application context. So an application using spring boot profiles will still
have a tendency to create a monolithic application context containing dozens
or even hundreds of beans.In contrast modular spring contexts focuses on dividing the set of beans an
application uses into small sets of coherent beans, each with a specific scope
and purpose.