Reputation: 27265
I'm looking for Guidance on how to correctly architect this in SpringBoot.
At the core of my project I have a SpringBoot application that will be configured at run time via a properties value to be a specific AdapterType my.adapter.type
For this example possible values are:
Modem
Turkey
Computer
Beef
On the classpath will exist a jar file with a package: org.myapp.adapters
. In that package are a variety of Spring @Component
classes each implementing the SystemAdapter
interface which specifies a type
(one of the types listed above) and a getType()
method.
The application will use a
@ComponentScan(basePackages = {"org.myapp.adapters"})
at startup to discover which adapters exist, and then will call the getType()
method on each adapter to discover which type it applies to.
Next a SystemAdapterFactory
will match the my.adapater.type
property value to a type returned by the getType()
methods of the Beans returning the correct adapter for the given specified type.
The goal is to be able to provide the SystemAdapter
Interface and Enum jar to other teams who can write their own SystemAdapater libraries. Then at startup we could swap out the DefaultAdapaters.jar for say FoodAdapaters.jar
Using the @ComponentScan
annotation - and assuming the adapters are in the correct package I am thinking we can swap out implementations on the fly - without having to compile the main class.
Where I'm running into confusion is how to actually configure Gradle and SpringBoot to do what I want. It appears the default bootJar task compiles in a bunch of .class
files - and as such I'm unclear how to actually build this solution.
I'm thinking I need 3 separate projects (or modules)
Application
Interface
AdapterImplementation
I believe if i use the properties loader configuration in my Application
project:
bootJar {
manifest {
attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher'
}
}
I should in theory be able to pass in different Jar files on the classPath at run-time and it will pick them up.
The guiding design principal here is that if I should be able to load (at runtime) a different implementation jar file and have it work without having to mess with the main application.
So my questions are:
1) Am I on the correct approach as how to structure and possibly get this to work
2) Is there another method to do this that we've missed that would make things much simpler?
Thanks
Upvotes: 0
Views: 1451
Reputation: 27265
I managed to answer my own question. I documented the approach I took at medium: https://medium.com/@Jeef/dynamically-loading-libraries-into-a-springboot-application-at-run-time-80639ee5aab?sk=5851d25c7106307ff0f2b0a4171ab613
Also I built a sample project demonstrating the implementation on GitHub: https://github.com/jeeftor/SpringBoot-Dynamic-JarLoad
Upvotes: 0
Reputation: 8107
I think SpringBatch
has a good example of what you want to achieve. If you are happy with the defaults of SpringBatch
, you simply to @EnableBatchProcessing
, and it will use the beans already specified in your environment, such as your dataSource
, transactionManager
etc.
But if you want to change the dataSource
Spring uses as to save your batch jobs into another database, then you can extend the default configuration it has available:
package i.live.where.the.application.context.is.created;
@EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {
@Override
@Autowired
public void setDataSource(@Qualifier("batchDataSource") DataSource batchDataSource) {
super.setDataSource(batchDataSource);
}
}
Make sure though for any @Autowired
dependencies that don't specify a type though that they get the correct implementation of the service you want with @Qualifier
, or you can use @Primary
for a default.
I'm not sure if I understand your project structure exactly but I think this approach is maybe not what you have gone for because your module you are developing is also initializing the Application Context?
If this is the case then put your domain into its own module, which is configurable via the method I described. Then the actual module which creates the application context can then include your module in it's class path, as well as any library which has an implementation for a service e.g.
package some.other.teams.module;
@Component("batchDataSource")
public class BatchDataSource implements your.module.DataSource {/*** ***/}
In conjunction with the BatchConfiguration
above the provides developers with the ability to swap out implementations as they need.
Upvotes: 1