{"id":24381854,"url":"https://gitlab.com/SpaceTrucker/modular-spring-contexts","last_synced_at":"2025-09-29T12:32:34.429Z","repository":{"id":57724552,"uuid":"1213630","full_name":"SpaceTrucker/modular-spring-contexts","owner":"SpaceTrucker","description":"","archived":false,"fork":false,"pushed_at":null,"size":null,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":null,"default_branch":"master","last_synced_at":"2024-11-16T01:16:00.822Z","etag":null,"topics":["java","spring"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":null,"metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-22T13:30:12.277Z","updated_at":"2019-05-01T20:32:12.904Z","dependencies_parsed_at":"2022-09-02T07:12:07.846Z","dependency_job_id":null,"html_url":"https://gitlab.com/SpaceTrucker/modular-spring-contexts","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/SpaceTrucker%2Fmodular-spring-contexts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/SpaceTrucker%2Fmodular-spring-contexts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/SpaceTrucker%2Fmodular-spring-contexts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories/SpaceTrucker%2Fmodular-spring-contexts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/owners/SpaceTrucker","download_url":"https://gitlab.com/SpaceTrucker/modular-spring-contexts/-/archive/master/modular-spring-contexts-master.zip","host":{"name":"gitlab.com","url":"https://gitlab.com","kind":"gitlab","repositories_count":4516889,"owners_count":6657,"icon_url":"https://github.com/gitlab.png","version":null,"created_at":"2022-05-30T11:31:42.605Z","updated_at":"2024-07-18T11:24:13.055Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/gitlab.com/owners"}},"keywords":["java","spring"],"created_at":"2025-01-19T09:13:29.477Z","updated_at":"2025-09-29T12:32:28.727Z","avatar_url":null,"language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introduction\n\nmodular-spring-contexts is a small utility library intended to make \n[spring application contexts](https://spring.io/understanding/application-context) \nmore modular and manageable.\n\nSpring application contexts for large applications tend to contain dozens or \neven hundreds of beans. Keeping an overview becomes more and more difficult and \nname collisions may happen. Also every bean is able to see every other bean in \nthe context.\n\n# Requirements\n\nThis library currently requires Spring 4.3.x.\n\n# Usage\n\nAdd a dependency to this library to your classpath.\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.gitlab.spacetrucker\u003c/groupId\u003e\n    \u003cartifactId\u003emodular-spring-contexts\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\n## Define modules\n\nA simple module definition is just a simple xml fragment:\n```xml\n\u003cmodule name=\"someModule\"\u003e\n    \u003cconfig location=\"/someModuleBeans.xml\" /\u003e\n\u003c/module\u003e\n```\n\nThis fragment defines a bean of type \n[`ApplicationContext`](http://docs.spring.io/spring-framework/docs/4.3.x/javadoc-api/org/springframework/context/ApplicationContext.html) \nwith name `someModule`. The file `/someModuleBeans.xml` is a spring xml context\ndefinition containing the bean definitions of application context `someModule`.\n\nIt is recommended to define one spring context xml file that contains the \nmodules the application requires.\n\nThe 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. \n\n### Starting modules\n\nModules are started by using the static factory method\n`ModuleStartup.startModules(String moduleRootConfig)`. This will create a module\nthat contains all the bean definitions from `moduleRootConfig`.\n\n### Module with multiple config locations\n\nA module may define multiple config locations.\n```xml\n\u003cmodule name=\"someModule\"\u003e\n    \u003cconfig location=\"/someModuleBeans1.xml\" /\u003e\n    \u003cconfig location=\"/someModuleBeans2.xml\" /\u003e\n    \u003cconfig location=\"/someModuleBeans3.xml\" /\u003e\n\u003c/module\u003e\n```\nIt is recommended to keep modules simple and therefore a module should only \ncontain bean definitions of one spring context xml file.\n\n### Module with dependencies\n\nA module may define dependencies to other modules. Only beans from dependent\nmodules may be imported.\n\nA dependency declaration looks like this:\n```xml\n\u003cmodule name=\"serverModule\"\u003e\n    \u003cconfig location=\"/serverModuleBeans.xml\" /\u003e\n\u003c/module\u003e\n\n\u003cmodule name=\"clientModule\"\u003e\n    \u003cconfig location=\"/clientModuleBeans.xml\" /\u003e\n    \u003crequires module=\"serverModule\" /\u003e\n\u003c/module\u003e\n```\nThis tells spring to make `serverModule` available as a source for \n[bean imports](#import) via it's wiring mechanism.\n\n`clientModuleBeans.xml` may then make use of the declared dependency like this:\n```java\npublic class Foo implements ApplicationContextAware {\n\tpublic void setApplicationContext(ApplicationContext applicationContext) {\n\t\tApplicationContext serverModule = \n\t\t\tapplicationContext.getBean(\"serverModule\", ApplicationContext.class);\n\t\t// do something with serverModule\n\t}\n}\n```\n\n### Nesting modules\n\nModules may also be nested. That means the xml bean definitions of a module may\ncontain other module definitions.   \nFile `rootModules.xml`\n```xml\n\u003cmodule name=\"parentModule\"\u003e\n    \u003cconfig location=\"/parentModuleBeans.xml\" /\u003e\n\u003c/module\u003e\n```   \nFile `parentModuleBeans.xml`\n```xml\n\u003cmodule name=\"childModule\"\u003e\n    \u003cconfig location=\"/childModuleBeans.xml\" /\u003e\n\u003c/module\u003e\n```\nThis setup will create two modules `parentModule` and `childModule`. The module \n`childModule` doesn't know anything about `parentModule`, but `childModule` is \njust a normal bean within `parentModule`.\n\nA module may also explicitly pass down dependent modules to nested modules \nusing the dependency mechanism as if the passed down module was declared in the\ncurrent module. \n\n### Parallel module startup\n\nThe startup of modules may be parallelized. Therefore an executor must be \ndefined in the application context that is defining the modules (see \n[The `executor` element](http://docs.spring.io/spring/docs/4.3.x/spring-framework-reference/html/scheduling.html#scheduling-task-namespace-executor)). \nThe `module` element than allows the configuration of that executor as the one \nwhich starts up the modules.\n```xml\n\u003ctask:executor id=\"moduleStartingExecutor\" pool-size=\"10\"/\u003e\n\n\u003cmodule name=\"someModule1\" moduleStarter=\"moduleStartingExecutor\" \u003e\n    \u003cconfig location=\"/someModuleBeans1.xml\" /\u003e\n\u003c/module\u003e\n\n\u003cmodule name=\"someModule2\" moduleStarter=\"moduleStartingExecutor\" \u003e\n    \u003cconfig location=\"/someModuleBeans2.xml\" /\u003e\n\u003c/module\u003e\n```\nThe executor will receive a task for each module it shall start. If no \n`moduleStarter` is specified, modules will be started within the thread that is \ninitializing the application context defining the modules.\n\n### Details of the application contexts that are used as modules\n\nA module is an instance of [ApplicationContext](http://docs.spring.io/spring-framework/docs/4.3.x/javadoc-api/org/springframework/context/ApplicationContext.html). \nThe module never has a parent, even though it may be a nested module.\n\n# Importing beans from modules {: #import}\n\nThe definition of modules is just one step to make large spring xml contexts \nmore manageable. The second step is to use imports to request specific beans\nfrom dependent modules.\n```xml\n\u003cimport id=\"importedBean\" sourceModule=\"dependentModule\" sourceBean=\"someBean\" /\u003e\n```\nThe above xml fragment will make the bean with name `someBean` of module \n`dependentModule` available to the currently defined module.\n\nSpecifying the `sourceBean` is optional. If this is not specified, the value of \nthe `id` attribute is used instead.\n```xml\n\u003cimport id=\"importedBean\" sourceModule=\"dependentModule\" /\u003e\n```\n\n## Static bean imports\n\nStatic bean imports can be used without modules in pure spring xml application \ncontexts. The `sourceModule` must be a bean of type `ApplicationContext` defined\nin the current application context. The bean defined by `sourceBean` is then \nimported as a singleton in the current application context.\n\n## Dynamic bean imports\n\nDynamic imports can only be used within modules. \n\nDynamically imported beans will be imported into the module that is declaring \nthe import with the same scope as the bean is defined in the context it is \nimported from.\n\nDuring resolving of dynamically imported beans the visibility of beans to be \nimported will also checked. A bean definition can specify that it is eligible \nfor imports by adding a `meta` element `exported` with value `true`.\n```xml\n\u003cbean id=\"importableBean\" ... \u003e\n    \u003cmeta key=\"exported\" value=\"true\"/\u003e\n\u003c/bean\u003e\n```\n\n# Testing modules\n\nThis requires\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.gitlab.spacetrucker\u003c/groupId\u003e\n    \u003cartifactId\u003emodular-spring-contexts-testing\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\nSince modules declare dependencies on other modules, it is possible to test \nmodules in isolation.\n\nThe 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)\nthat contains the beans which are imported by the module under test from the \ndependent module.\n```java\n@Configuration(\"dependentModule\")\npublic static class TestReplacementModule {\n\n\tprivate final Object exportedBean = new Object();\n\n\t@Bean\n\tpublic Object exportedBean() {\n\t\treturn exportedBean;\n\t}\n}\n```\n\nThe second step is to create the test instance that declares that the module \nunder test will be used together with an instance of `TestReplacementModule`.\n```java\npublic class ClientModuleTest {\n\n\t@MockedModule\n\tprivate TestReplacementModule serverModule;\n\n\t@Resource\n\tprivate Object beanUnderTest;\n\n\t@Before\n\tpublic void beforeMethod() {\n\t\tModuleTesting.initIsolatedModule(\"/moduleUnderTest.xml\", this);\n\t}\n\t\n\t@Test\n\tpublic void shouldWork() {\n\t\t// do the test\n\t}\n}\n```\nUsing `@MockedModule` indicates that the field annotated with it is going to be \nset to the module that the module under test depends on. The name of the field \nmust be the same as the name of the module which the module under test depends \non.\n\nInvoking `ModuleTesting.initIsolatedModule(\"/moduleUnderTest.xml\", this);` will \ninitialize the `@MockedModule` fields and start the module defined in \n`/moduleUnderTest.xml` using all `@MockedModule` fields as dependent modules. \nAfter the module under test is started `this` will be autowired and therefore \nall `@Resource` will be set to the spring beans contained in the module under \ntest.\n\nNote that beans from `@MockedModule` are also eligible for injection via \n`@Resource` fields, but this should not be used. The preferred way of accessing \nbeans in dependent modules is via the `@MockedModule` field like this:\n```java\n\t@Test\n\tpublic void shouldWork() {\n\t\tserverModule.exportedBean().getSomething();\n\t}\n```\n\n## Testing modules with Mockito\n\nIn order to use [mockito](http://mockito.org) to test interactions between the \nmodule under test and dependent modules, the module under test must depend on\nmodules that provide mockito mocks. This is achieved via:\n```java\n@Configuration\npublic static class TestReplacementModule {\n\n\t@Mock\n\tprivate List\u003cObject\u003e exportedBean;\n\n\t@PostConstruct\n\tpublic void postConstruction() {\n\t\tMockitoAnnotations.initMocks(this);\n\t}\n\n\t@Bean\n\tpublic List\u003cObject\u003e exportedBean() {\n\t\treturn exportedBean;\n\t}\n}\n```\nBefore the module under test is startet, this Java-based configuration is \ncreated and the `@PostConstruct` method makes sure that the mocks are \ninitialized.\n\nDuring tests the mocks can be used as in any other mockito test:\n```java\npublic class ClientModuleTest {\n\n\t@MockedModule\n\tprivate TestReplacementModule serverModule;\n\n\t@Resource\n\tprivate Object beanUnderTest;\n\n\t@Before\n\tpublic void beforeMethod() {\n\t\tModuleTesting.initIsolatedModule(\"/moduleUnderTest.xml\", this);\n\t}\n\t\n\t@Test\n\tpublic void shouldWork() {\n\t\tMockito.when(serverModule.exportedBean().contains(Mockito.any()).thenReturn(true);\n\t\t\n\t\tbeanUnderTest.doSomething();\n\t\t\n\t\tMockito.verify(serverModule.exportedBean()).clear();\n\t}\n}\n```\n\n## Testing module startup and shutdown\n\nA module may interact with other modules during startup and shutdown. Tests for these phases should be put in their own tests. \n\nIn 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:\n```java\npublic class StartupModuleTestingIntegrationTest {\n\n\tprivate static class BarModuleFooServiceStartup {\n\n\t\t@Configuration\n\t\tprivate static class FooModule {\n\n\t\t\t@Mock\n\t\t\tprivate FooService fooService;\n\n\t\t\tprivate Foo foo = new Foo();\n\n\t\t\t@PostConstruct\n\t\t\tpublic void postConstruction() {\n\t\t\t\tMockitoAnnotations.initMocks(this);\n\t\t\t\tMockito.when(fooService.doSomething()).thenReturn(foo);\n\t\t\t}\n\n\t\t\t@Bean\n\t\t\tpublic FooService fooService() {\n\t\t\t\treturn fooService;\n\t\t\t}\n\t\t\n\t\t\tpublic Foo getFoo() {\n\t\t\t\treturn foo;\n\t\t\t}\n\t\t}\n\n\t\t@MockedModule \n\t\tprivate FooModule fooModule;\n\t\n\t\t// defined in barModule.xml\n\t\t@Resource\n\t\tprivate BarService barService;\n\t}\n\n\t@Test\n\tpublic void shouldInvokeFooServiceDuringStartup() {\n\t\tBarModuleFooServiceStartup moduleUnderTest = new BarModuleFooServiceStartup();\n\t\t\n\t\tModuleTesting.initIsolatedModule(\"/barModule.xml\", moduleUnderTest);\n\n\t\tMockito.verify(moduleUnderTest.fooModule.fooService()).doSomething();\n\t\tAssert.assertSame(moduleUnderTest.fooModule.getFoo(), moduleUnderTest.barService.getFoo());\n\t\t// other assertions\n\t}\n}\n```\nThe `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.\n\nInteractions 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:\n```java\npublic class ShutdownModuleTestingIntegrationTest {\n\n\t@Configuration\n\tprivate static class FooModule {\n\n\t\t@Mock\n\t\tprivate FooService fooService;\n\n\t\t@PostConstruct\n\t\tpublic void postConstruction() {\n\t\t\tMockitoAnnotations.initMocks(this);\n\t\t}\n\n\t\t@Bean\n\t\tpublic FooService fooService() {\n\t\t\treturn fooService;\n\t\t}\n\t}\n\n\t@MockedModule\n\tprivate FooModule fooModule;\n\n\tprivate ConfigurableApplicationContext moduleUnderTest;\n\n\t@Before\n\tpublic void beforeMethod() {\n\t\tmoduleUnderTest = ModuleTesting.initIsolatedModule(\"/barModule.xml\", this);\n\t}\n\n\t@Test\n\tpublic void shouldInvokeFooServiceDuringShutdown() {\n\t\tMockito.when(serverModule.dependentBean().compare(Mockito.anyInt(), Mockito.anyInt())).thenReturn(0);\n\n\t\tmoduleUnderTest.close();\n\n\t\tMockito.verify(serverModule.fooService()).doSomething();\n\t}\n}\n```\n\n# Integration with preexisting spring contexts\n\nTo use modules in an already existing spring context, you can add the following \nbean definition to set up modules:\n```xml\n\u003cbean id=\"rootModule\"\n    class=\"com.gitlab.spacetrucker.modularspringcontexts.module.ModuleStartup\"\n    factory-method=\"startModules\"\u003e\n    \u003cconstructor-arg index=\"0\"\n    value=\"/moduleDefinitions.xml\" /\u003e\n\u003c/bean\u003e\n```\n\nThis will set up the modules defined as bean `rootModule` in the already \nexisting spring context. Static bean imports can then be used to import beans \nfrom `rootModule`.\n\n# Difference to spring boot profiles\n\nBasically spring boot profiles are used to determine what is contained in an \napplication context. But profiles do not provide a mechanism to modularize an\napplication context. So an application using spring boot profiles will still\nhave a tendency to create a monolithic application context containing dozens\nor even hundreds of beans.\n\nIn contrast modular spring contexts focuses on dividing the set of beans an \napplication uses into small sets of coherent beans, each with a specific scope \nand purpose.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/gitlab.com%2FSpaceTrucker%2Fmodular-spring-contexts","html_url":"https://awesome.ecosyste.ms/projects/gitlab.com%2FSpaceTrucker%2Fmodular-spring-contexts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/gitlab.com%2FSpaceTrucker%2Fmodular-spring-contexts/lists"}