Dynamically generating Spring Data Repositories - java

I am working on creating a library built on top of spring-data so that it would be able to dynamically generate Spring Data repositories. I was hoping to create a generic wrapper of this process that operates on the Spring Data abstraction.
This way depending on what dependency is added to the classpath example: spring-data-jpa or spring-data-mongodb I would be able to generate repositories and inject them into my spring context.
I was having trouble finding good documentation on how to go about doing this. Any ideas?

Spring provides a #Conditional annotation for controlling which beans/configurations are picked up depending on classpath ( similar with the spring-boot autoconfiguration ).
You could define two configuration classes which are picked up by spring based on your classpath.
For example:
Configuration for JPA present on classpath could look something like.
#Configuration
#EnableJpaRepositories(basePackages=...)
#Conditional(JpaClassPathCondition.class)
public class JpaAutoConfiguration {
static class JpaClassPathCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ClassUtils.isPresent("javax.persistence.EntityManager", JpaAutoConfiguration.class.getClassLoader());
}
}
}
Configuration for MongoDB:
#Configuration
#EnableMongoRepositories(...)
#Conditional(MongoClassPathCondition.class)
public class MongoAutoConfiguration {
static class MongoClassPathCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return !ClassUtils.isPresent("javax.persistence.EntityManager", MongoAutoConfiguration.class.getClassLoader()); )//or whatever classpath element fits you best
}
}
}
All you need to do from this point forward is to use both configuration classes in your module and let spring do it's magic

Related

Spring Boot 2 - Do something before the beans are initialized

Problem Statement
I want to load properties from a properties file in a classpath or at an external location before the beans are initialized. These properties are also a part of Bean initialization. I cannot autowire the properties from Spring's standard application.properties or its customization because the same properties file must be accessible by multiple deployables.
What I Tried
I'm aware about Spring Application Events; in fact, I'm already hooking
ContextRefreshedEvent to perform some tasks after the Spring Context is initialized (Beans are also initialized at this stage).
For my problem statement, from the description of Spring Docs ApplicationEnvironmentPreparedEvent looked promising, but the hook did not work.
#SpringBootApplication
public class App {
public static void main(String[] args) throws IOException {
SpringApplication.run(App.class, args);
}
#EventListener
public void onStartUp(ContextRefreshedEvent event) {
System.out.println("ContextRefreshedEvent"); // WORKS
}
#EventListener
public void onShutDown(ContextClosedEvent event) {
System.out.println("ContextClosedEvent"); // WORKS
}
#EventListener
public void onEvent6(ApplicationStartedEvent event) {
System.out.println("ApplicationStartedEvent"); // WORKS BUT AFTER ContextRefreshedEvent
}
#EventListener
public void onEvent3(ApplicationReadyEvent event) {
System.out.println("ApplicationReadyEvent"); // WORKS WORKS BUT AFTER ContextRefreshedEvent
}
public void onEvent1(ApplicationEnvironmentPreparedEvent event) {
System.out.println("ApplicationEnvironmentPreparedEvent"); // DOESN'T WORK
}
#EventListener
public void onEvent2(ApplicationContextInitializedEvent event) {
System.out.println("ApplicationContextInitializedEvent"); // DOESN'T WORK
}
#EventListener
public void onEvent4(ApplicationContextInitializedEvent event) {
System.out.println("ApplicationContextInitializedEvent");
}
#EventListener
public void onEvent5(ContextStartedEvent event) {
System.out.println("ContextStartedEvent");
}
}
Update
As suggested by M.Deinum in the comments, I tried adding an application context initializer like below. It doesn't seem to be working either.
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(App.class)
.initializers(applicationContext -> {
System.out.println("INSIDE CUSTOM APPLICATION INITIALIZER");
})
.run(args);
}
Update #2
While my problem statement is regarding loading properties, my question/curiosity is really about how to run some code before the classes are initialized as beans and put into Spring IoC container. Now, these beans require some property values during initialization and I can't/don't want to Autowire them because of the following reason:
As stated in comments and answers, the same can be done using Spring Boot's externalized configuration and profiles. However, I need to maintain application properties and domain-related properties separately. A base domain properties should have at least 100 properties, and the number grows over time. Both application properties and domain-related properties have a property file for different environments (dev, SIT, UAT, Production). Property files override one or more of the base properties. That's 8 property files. Now, the same app needs to be deployed into multiple geographies. That makes it 8 * n property files where n is the number of geographies. I want all the property files stored in a common module so that they can be accessed by different deployables. Environment and geography would be known in run-time as system properties.
While these might be achieved by using Spring profiles and precedence order, I want to have a programmatic control over it (I also would maintain my own property repository). Eg. I would write a convenience utility called MyPropUtil and access them like:
public class MyPropUtil {
private static Map<String, Properties> repository;
public static initialize(..) {
....
}
public static String getDomainProperty(String key) {
return repository.get("domain").getProperty(key);
}
public static String getAppProperty(String key) {
return repository.get("app").getProperty(key);
}
public static String getAndAddBasePathToAppPropertyValue(String key) {
...
}
}
#Configuration
public class MyComponent {
#Bean
public SomeClass getSomeClassBean() {
SomeClass obj = new SomeClass();
obj.someProp1(MyPropUtil.getDomainProperty('domainkey1'));
obj.someProp2(MyPropUtil.getAppProperty('appkey1'));
// For some properties
obj.someProp2(MyPropUtil.getAndAddBasePathToAppPropertyValue('some.relative.path.value'));
....
return obj;
}
}
From the docs, it seems like ApplicationEvents and ApplicationInitializers fit my need, but I am not able to get them to work for my problem statement.
Bit late to the party but hopefully I can offer a solution to your updated problem statement.
This will focus on problem of how to run some code before the classes are initialized as beans and put into Spring IoC container
One issue I notice is that you're defining your application events via the #EventListener annotation.
These are only called once all beans are initiated since these annotations are processed by EventListenerMethodProcessor which is only triggered when the context is ready (see SmartInitializingSingleton#afterSingletonsInstantiated)
As such, some of the events that occur before the context is ready. e.g. ContextStartedEvent, ApplicationContextInitializedEvent won't make it to your listener.
Instead, what you can do is extend the interface for these events directly.
#Slf4j
public class AllEvent implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(final ApplicationEvent event) {
log.info("I am a {}", event.getClass().getSimpleName());
}
Note the missing #Component. Even bean instantiation can occur after some of these events. If you use #Component, then you'll get the following logs
I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent
Still better and more instant than the annotative listeners but will still not receive the initialization events. For that, what you need to do is follow the instructions found here
To summarize,
Create directory resources/META-INF
Create file spring.factories
org.springframework.context.ApplicationListener=full.path.to.my.class.AllEvent
The result:-
I am a ApplicationContextInitializedEvent
I am a ApplicationPreparedEvent
I am a DataSourceSchemaCreatedEvent
I am a ContextRefreshedEvent
I am a ServletWebServerInitializedEvent
I am a ApplicationStartedEvent
I am a ApplicationReadyEvent
In particular, ApplicationContextInitializedEvent should allow you to perform whatever per-instantiation tasks you need.
I think Spring Cloud Config is a perfect solution for your problem statement. Detailed documentation Here
Spring Cloud Config provides server-side and client-side support for externalized configuration in a distributed system.
So you can easily manage the configurations outside of the app, as well as all the instances will use same configurations.
Create a bean that will be a properties repository and inject it in other beans requiring properties.
In your example, instead of having static methods in MyPropUtil, make the class a bean itself with instance methods. Initialize Map<String, Properties> repository in the initialize method annotated with #PostConstruct.
#Component
public class MyPropUtil {
private static final String DOMAIN_KEY = "domain";
private static final String APP_KEY = "app";
private Map<String, Properties> repository;
#PostConstruct
public void init() {
Properties domainProps = new Properties();
//domainProps.load();
repository.put(DOMAIN_KEY, domainProps);
Properties appProps = new Properties();
//appProps.load();
repository.put(APP_KEY, appProps);
}
public String getDomainProperty(String key) {
return repository.get(DOMAIN_KEY).getProperty(key);
}
public String getAppProperty(String key) {
return repository.get(APP_KEY).getProperty(key);
}
public String getAndAddBasePathToAppPropertyValue(String key) {
//...
}
}
and
#Configuration
public class MyComponent {
#Autowired
private MyPropUtil myPropUtil;
#Bean
public SomeClass getSomeClassBean() {
SomeClass obj = new SomeClass();
obj.someProp1(myPropUtil.getDomainProperty("domainkey1"));
obj.someProp2(myPropUtil.getAppProperty("appkey1"));
// For some properties
obj.someProp2(myPropUtil.getAndAddBasePathToAppPropertyValue("some.relative.path.value"));
//...
return obj;
}
}
Or you can inject MyPropUtil directly to the SomeClass:
#Component
public class SomeClass {
private final String someProp1;
private final String someProp2;
#Autowired
public SomeClass(MyPropUtil myPropUtil) {
this.someProp1 = myPropUtil.getDomainProperty("domainkey1");
this.someProp2 = myPropUtil.getAppProperty("appkey1");
}
//...
}
I feel like your main issue is that you need to maintain application properties and domain-related properties separately.
From spring's perspective, it doesn't really matter since all properties files are kinda merged together after they have been loaded in memory.
So for example, you have two files that contain some properties:
application.related=property1 # this is in application.properties
domain.related=property2 # this is in domain-specific.properties
After they have been loaded, you will get one big thing that contains all properties, if I am not mistaken, it is a org.springframework.core.env.ConfigurableEnvironment instance.
Then what you need to do is just inject the property you need using something like #Value.
For the main issue, to separate properties into different files, you just need to specify spring's spring.config.name property (via environment variable, command line or programmatically). Following the above example, it should be spring.config.name=application,domain-specific.
Furthermore, if you really want to have programmatic control, you can add a custom EnvironmentPostProcessor which exposes the ConfigurableEnvironment instance.
As explaned in this post you can add external property files like this;
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
properties.setLocation(new FileSystemResource("/Users/home/conf.properties"));
properties.setIgnoreResourceNotFound(false);
return properties;
}
If you don't want to use this, just read the property file with jackson and set the properties to System.setProperty("key","value") in the main method before spring starts.
If you don't want to use this too, take a look at the BeanPostProcessor#postProcessBeforeInitialization method. It runs before bean properties initialized by spring.
I might be missing what exactly do you mean by "Beans initialization", probably an example of such a bean in a question could be beneficial.
I think you should differentiate between properties reading part and bean initialization.
By the time of bean initialization, properties are already read and available. Thats a part of spring magic, if you wish.
That's why the following code works for example:
#Component
public class MySampleBean {
public MySampleBean(#Value("${some.prop}" String someProp) {...}
}
It doesn't matter from where do these property come (spring boot defines many different ways of these places with precedence between them), it will happen before the initialization of beans happens.
Now, lets get back to your original question:
I want to load properties from a properties file in a classpath or at external location (before the beans are initialized - irrelevant).
In spring / spring-boot there is a concept of profiles that basically allows to create a file application-foo.properties (or yaml) and when you load with --spring.profiles.active=foo it will automatically load properties defined in this application-foo.properties in addition to the regular application.properties
So you can place the stuff that you want to "load from classpath" into application-local.properties (the word local is for the sake of example only) and start the application with --spring.profiles.active=local (in the deployment script, docker file or whatever)
If you want to run the property from external location (outside the classpath) you can use: --spring.config.location=<Full-path-file>
Note that even if you put some properties into a regular application.properties and still use --spring.config.location with the same key-value pairs they will take precedence over the properties in the classpath.
Alternatively you can use only --sring.profiles.active=local or remote and do not use config locations at all.
You can configure external location directly in the command line:
java -jar app.jar --spring.config.location=file:///Users/home/config/external.properties
You can use WebApplicationInitializer to execute code before classes are initialized as beans
public class MyWebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
var ctx = new AnnotationConfigWebApplicationContext();
ctx.register(WebConfig.class);
ctx.setServletContext(servletContext);
We create an AnnotationConfigWebApplicationContext and register a web configuration file with register().
You can check if PropertySource may help you.
Example:
#PropertySource({"classpath:persistence/persistence.properties"})
You can use this annotation on every #Configuration or #SpringBootApplication bean
It sounds like you want to take some ownership of a part of the bean initialization. Typically people think of Spring completing the bean configuration, but in your case it might be easier to consider Spring as starting it.
So, your bean has some properties you want to configure, and some that you want Spring to configure. Just annotate the ones you want Spring to configure (with #Autowire or #Inject, or whatever flavour you prefer), and then take over the control from there, using #PostConstruct or InitializingBean.
class MyMultiStageBoosterRocket {
private Foo foo;
private Bar bar;
private Cat cat;
#Autowire
public MyMultiStageBoosterRocket(Foo foo, Bar bar) {
this.foo = foo;
this.bar = bar'
}
// called *after* Spring has done its injection, but *before* the bean
// is registered in the context
#PostConstruct
public void postConstruct() {
// your magic property injection from whatever source you happen to want
ServiceLoader<CatProvider> loader = ServiceLoader.load(CatProvider.class);
// etc...
}
}
Of course your mechanism for property resolution would need to be available statically somehow, but that seems to fit with you MyPropUtil example.
Getting far more involved, you start looking at Bean Post Processors directly (#PostConstruct is a simple variant of sorts).
There's a previous question, with a useful answer, here How exactly does the Spring BeanPostProcessor work?, but for simplicity, you'd do something like
public class CustomBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// fixme: detect if this bean needs fancy initialization
return bean;
}
}
Clearly #PostProcess, or InitializingBean are simpler, but the custom post processor has a big advantage... it can be injected with other Spring managed beans. That means you can Spring manage your property injection whatever-thing, and still manually manage the actual injection process.
Just try to load everything you need in main before
SpringApplication.run()
call
public static void main(String[] args) {
// before spring initialization
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
SpringApplication.run(CyberRiskApplication.class, args);
}
You can use ApplicationEnvironmentPreparedEvent but it can't be configured using EventListener annotation. Because by this time Bean drfinitions are not loaded. See the below link on how to cofigure this event.
https://www.thetechnojournals.com/2019/10/spring-boot-application-events.html

What is the use case of #Import annotation?

According to official doc:
Annotation Type Configuration
Indicates that a class declares one or more #Bean methods and may be
processed by the Spring container to generate bean definitions...
#Configuration classes may be composed using the #Import annotation,
not unlike the way that works in Spring XML. Because
#Configuration objects are managed as Spring beans within the
container..
But i can also use #Configuration annotation without #Import. I have tested the code listed below and it works as expected. So what is the purpose to use #Import?
DispatcherServletInitializer
public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
WebMvcConfigurerAdapter
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "package.name" })
// #Import(OptionalConfig.class)
public class WebConfig extends WebMvcConfigurerAdapter {
// ...
}
OptionalConfig
#Configuration
public class OptionalConfig {
#Bean(name = "myClass")
public MyClass myClass() {
return new MyClass();
}
}
Service
#Service
public class MyServiceImpl implements MyService {
#Autowired
private MyClass myClass; // yes, it works
// ...
}
If component scanning is enabled, you can split bean definitions in multi #Configuration classes without using #Import. And you don't need to provide all of them to the application context constructor.
I think the main purpose for #Import is to provide you a way to simplify multiple configurations registration if you'd like to avoid component scanning (as of Spring Framework 4.2, per reference manual).
There's a note in Spring Reference Documentation about #Import usage:
As of Spring Framework 4.2, #Import also supports references to regular component classes, analogous to the AnnotationConfigApplicationContext.register method. This is particularly useful if you’d like to avoid component scanning, using a few configuration classes as entry points for explicitly defining all your components.
Thus far, we've seen how to break up bean definitions into multiple #Configuration classes and how to reference those beans across #Configuration boundaries. These scenarios have required providing all #Configuration classes to the constructor of a JavaConfigApplicationContext, and this is not always ideal. Often it is preferable to use an aggregation approach, where one #Configuration class logically imports the bean definitions defined by another.
The #Import annotation provides just this kind of support, and it is the direct equivalent of the <import/> element found in Spring beans XML files.
http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch04s03.html
I found a use of using the #Import annotation. I don't think it's the use case.
If you are developing a set of libraries using Spring, probably you don't have a SpringBootApplication. So, you have not enabled any auto-scan to resolve beans.
If a bean declared in a configuration file in the library library-a is referred through dependency injection in library-b you need to use #Import to tell Spring how to resolve the bean.
As we said, in library-a you have this configuration file.
#Configuration
public class ConfigurationA {
#Bean
public BeanA beanA() {
return new BeanA();
}
}
In library-b you must have this configuration file if you want to use BeanA
#Configuration
#Import(ConfigurationA.class)
public class ConfigurationB {
#Bean
public BeanB beanB(BeanA beanA) {
return new BeanB(beanA);
}
}
Hope it helps.
With component scanning enabled it's difficult to immediately see where #Import adds value if your view of the world is limited to your own application and its packages. Where it can help is if you are importing bean libraries with their own package structure that you don't want to component scan.
You can place such libraries on your classpath and use #Import to cherry-pick #Configuration classes from within them. That's why it's often referred to as composition because you are composing your #Configuration class from multiple sources.
A typical use case of #Import is when teams develop REST services and realize they need some common configuration.
Every service will have its own namespace i.e. org.mybusiness.specific and will have its #SpringBootApplication (and therefore component scan root) start at this package.
On the other hand, the common library will have its namspace set to org.mybusiness.common and will therefore be out of reach for component scanning.
Thus the need to reference the common configuration class using #Import(CommonConfig.class)
#Import works great when for some reasons you need to register not all but some of the components from a package. In such case #ComponentScan would be too complicated.
See details in the Spring boot component scan include a single class question.

Decide which JavaConfig class to load at runtime

There are multiple classes annotated with #Configuration and I want to decide in a top-level config component which one to register in the context.
#Configuration
public class FileSystemDataStoreConfiguration {
public #Bean DataStore getDataStore() {
return new FileSystemDataStore(withSomeConfigProperties);
}
}
#Configuration
public class DatabaseDataStoreConfiguration {
public #Bean DataStore getDataStore() {
return new DatabaseDataStore(withSomeConfigProperties);
}
}
#Configuration
public class DataStoreConfiguration {
// Some arbitrary logic to decide whether I want to load
// FileSystem or Database configuration.
}
I understand that I can use #Profile to select among multiple configuration classes. However, I already use profiles to distinguish between environments. The selection of the configuration classes is indepedent from the environment.
How can I select at runtime which configuration class to load?
Can I have multiple active profiles like "Production, WithDatabase"?
If so, how can I add a profile based on a property?
Spring, so there are lots of ways to do things!
If you stay all annotations, you can use the #ActiveProfiles annotation so enable the overall set of profiles you want:
#ActiveProfiles(profiles = ProfileDefinitions.MY_ENABLED_PROFILE)
#ContextConfiguration(as usual from here...)
You see the "profiles" allows many profiles to be set. You don't need to store the profiles as constants either, but you may find that helpful:
public class ProfileDefinitions {
public static final String MY_ENABLED_PROFILE = "some-profile-enabled";
// you can even make profiles derived from others:
public static final String ACTIVE_WHEN_MY_IS_NOT = "!" + MY_ENABLED_PROFILE;
}
Using all of the above, you can selectively enable various configurations based upon the dynamic setting of the Profiles:
#Profile(ProfileDefinitions.MY_ENABLED_PROFILE)
#Configuration
#Import({these will only be imported if the profile is active!})
public class DatabaseDataStoreConfiguration {
}
#Profile(ProfileDefinitions.ACTIVE_WHEN_MY_IS_NOT)
#Configuration
#Import({if any are necessary})
public class DataStoreConfiguration {
}
If you are using Spring 4, you could use the new #Conditional annotation functionality (which is actually the backend used to implement #Profile)

How to set Spring Active Profiles Pragmatically from a configuration class?

On a project I'm working on we have some old dependencies that define their own spring beans but need to be initialized from the main application. These beans are all constructed using spring profiles, i.e. "default" for production code and "test" for test code. We want to move away from using spring profiles, instead simply using #import to explicitly wire up our context.
The idea is to encapsulate all these old dependencies so that no other components need to care about spring profiles. Thus, from a test`s point of view, the application context setup can be described as follows:
#ContextConfiguration(classes = {TestContext.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {
//tests
}
TestContext further directs to two classes, one of which encapsulates the old dependencies:
#Configuration
#Import(value = {OldComponents.class, NewComponents.class})
public class TestContext {
//common spring context
}
To encapsulate the old components` need for profiles, the OldComponents.class looks as follows:
#Configuration
#Import(value = {OldContext1.class, OldContext2.class})
public class OldComponents {
static {
System.setProperty("spring.profiles.active", "test");
}
}
The problem here is that the static block does not appear to be executed in time. When running mvn clean install, the test gets an IllegalStateException because the ApplicationContext could not be loaded. I have verified that the static block gets executed, but it would appear that OldContext1 and OldContext2 (which are profile dependent) are already loaded at this time, which means it is too late.
The frustrating thing is that IntelliJ runs the tests just fine this way. Maven, however, does not. Is there a way to force these profiles while keeping it encapsulated? I've tried creating an intermediary context class, but it didn't solve the problem.
If we use the annotation #ActiveProfiles on the test class, it runs just fine but this kind of defeats the purpose. Naturally, we want to achieve the same in production and this means that if we cannot encapsulate the need for profiles, it needs to be configured in the web.xml.
If your configuration classes inherits of AbstractApplicationContext you can call:
getEnvironment().setActiveProfiles("your_profile");
For example:
public class TestContext extends AnnotationConfigWebApplicationContext {
public TestContext () {
getEnvironment().setActiveProfiles("test");
refresh();
}
}
Hope it helps.
It definietly seems that OldContext1 and OldContext2 are being class-loaded and initialized before the static block in OldComponents is executed.
Whilst I can't explain why there is a difference between your IDE and Maven (to do so would require some in-depth knowledge of some, if not all all, of : spring 3.x context initialization, maven surefire plugin, SpringJunit4ClassRunner and the internal IntelliJ test runner), can I recommend to try this?
#Configuration
#Import(value = {UseTestProfile.class, OldContext1.class, OldContext2.class})
public class OldComponents {
// moved the System.setProperty call to UseTestProfile.class
}
and
#Configuration
public class UseTestProfile {
static {
System.setProperty("spring.profiles.active", "test");
}
}
If I am understanding your problem correctly, class UseTestProfile should be loaded first (you might want to investigate a way to guarantee this?) and the other two classes in the import list should have the system setting they need to initialize properly.
Hope this helps...
You need make sure, environment takes effect at first.This is how I do:
#Component
public class ScheduledIni {
#Autowired
private Environment env;
#PostConstruct
public void inilizetion() {
String mechineName = env.getProperty("MACHINE_NAME");
if ("test".equals(mechineName) || "production".equals(mechineName) {
System.setProperty("spring.profiles.default", "Scheduled");
System.setProperty("spring.profiles.active", "Scheduled");
}
}
}
In scheduler add annotation Prodile and DependsOn to make it work.
#DependsOn("scheduledIni")
#Profile(value = { "Scheduled" })
#Component
Use #profile annotation in the class to load the configuration like below
#Configuration
#Profile("test")
public class UseTestProfile {
}
and set the value for the property spring.profiles.active either in property file or as a runtime argument

Guice and general application configuration

For a monitoring software written in Java I consider using Google Guice as DI provider. The project needs to load its configuration from an external resource (file or database). The application is designed to run in standalone mode or in a servlet container.
At the moment the configuration does not contain bindings or parameters for dependency injection, only some global application settings (JDBC connection definitions and associated database management/monitoring objects).
I see two options:
to use another library, for example Apache Commons Configuration, which supports file and JDBC configuration sources (and many other)
or
to use a file based addon for Guice like guice-xml-config to store the application options (this would allow to configure the DI part later if it becomes necessary).
Would you recommend to use Guice for both tasks, or keep the general application configuration separate from the dependency injection? Which advantages and disadvantages would you consider the most important ones?
It's straightforward to slurp a property file in a Guice module:
public class MyModule extends AbstractModule {
#Override
protected void configure() {
try {
Properties properties = new Properties();
properties.load(new FileReader("my.properties"));
Names.bindProperties(binder(), properties);
} catch (IOException ex) {
//...
}
}
}
Later it's easy to switch from Properties to other config sources.
[Edit]
BTW, you can get the injected properties by annotating it with #Named("myKey").
Check the governator library:
https://github.com/Netflix/governator/wiki/Configuration-Mapping
You will get a #Configuration annotation and several configuration providers. In code it helps to see where is You configuration parameters used:
#Configuration("configs.qty.things")
private int numberOfThings = 10;
Also, You will get a nice configuration report on startup:
https://github.com/Netflix/governator/wiki/Configuration-Mapping#configuration-documentation
Try Guice configuration available on maven central, it's support Properties, HOCON and JSON format.
You can inject properties from file application.conf to your service as :
#BindConfig(value = "application")
public class Service {
#InjectConfig
private int port;
#InjectConfig
private String url;
#InjectConfig
private Optional<Integer> timeout;
#InjectConfig("services")
private ServiceConfiguration services;
}
You must install the modules ConfigurationModule as
public class GuiceModule extends AbstractModule {
#Override
protected void configure() {
install(ConfigurationModule.create());
requestInjection(Service.class);
}
}
I ran into the same problem in my own project. We had already chosen Guice as DI-framework and to keep things simple wanted to use it also with configuration.
We ended up reading the configuration from properties file using Apache Commons Configuration and binding them to Guice injector like suggested in Guice FAQ How do I inject configuration parameters?.
#Override public void configure() {
bindConstant().annotatedWith(ConfigurationAnnotation.class)
.to(configuration.getString("configurationValue"));
}
Reloading of configuration supported by Commons Configuration is also quite easy implement into Guice injection.
#Override public void configure() {
bind(String.class).annotatedWith(ConfigurationAnnotation.class)
.toProvider(new Provider<String>() {
public String get() {
return configuration.getString("configurationValue");
}
});
}

Categories

Resources