Options for dynamic properties in Spring Boot - java

I have an application with some externalized configuration in the form of properties. I would like the application to react to a change of such properties without a restart or full context refresh.
I am not clear what my options are.
Artificial example: the application implements a service that receives requests and decides whether to queue or reject them. The maximum size of the queue is a property.
final int queueMaxSize = queueProperties.getMaxSize();
if (queue.size() >= queueMaxSize) { //reject }
Where QueueProperties is a class annotated with #ConfigurationProperties.
#ConfigurationProperties(prefix = "myapp.limits.queue")
#Getter
#Setter
public class QueueProperties {
public int maxSize = 10;
}
This works as far as allowing me to control behavior via system properties, profiles, etc.
However, I would like to be able to change this value without releasing/deploying/restarting/refreshing the application.
My application already uses Archaius.
If I push a new value for this property using our internal infrastructure, i can see the application Spring Environment does receive the new value.
(e.g., /admin/env reflects the value and changes dynamically).
The part I'm not clear on is: how to make my service react to the change of value in the environment?
I found two ways, but they seem hairy, I wonder if there are better options. I expected this to be a common problem with a first class solution in the Spring ecosystem.
Hacky solution #1:
#Bean
#Scope("prototype")
#ConfigurationProperties(prefix = "myapp.limits.queue")
QueueProperties queueProperties() {
return new QueueProperties();
}
And inject this into the service using the properties as Provider<QueueProperties> and use it as queuePropertiesProvider.get().getMaxSize().
This works but has a few side-effects I'm not a fan of:
ConfigurationProperties annotation moved from the class to the bean definition
A new QueueProperties object is created and bound to values for every request coming in
Provider might throw on get()
Invalid values are not detected until the first request comes in
Hacky solution #2:
Don't annotate my properties class with ConfigurationProperties, inject the Spring environment at construction time. Implement the getters as such:
int getMaxSize() {
return environment.getProperty("myapp.limits.queue", 10);
}
This also works ok in terms of behavior. However
- This is not annotated as property (unlike the rest of the properties classes in this large project, makes it harder to find)
- This class does not show up in /admin/configprops
Hacky solution #3:
Schedule a recurring task that uses Environment to update my singleton QueueProperties bean.
Any further ideas/suggestions/pointers?
Is there a canonical/recommended way to do this that does not have the shortcoming of my solutions above?

Related

When to set proxyBeanMethods to false in Springs #Configuration?

When looking at springs autoconfigure source code it seems that every auto-configuration class sets proxyBeanMethods = false.
#Configuration(proxyBeanMethods=false)
public class SomeAutoConfiguration {
...
}
The javadoc gives a detailed explanation for this particular field:
Specify whether {#code #Bean} methods should get proxied in order to enforce
bean lifecycle behavior, e.g. to return shared singleton bean instances even
in case of direct {#code #Bean} method calls in user code. (...)
If this is not needed since each of this particular configuration's {#code #Bean}
methods is self-contained and designed as a plain factory method for container use,
switch this flag to {#code false} in order to avoid CGLIB subclass processing.(...)
After reading this I'm still confused when it is better setting it to false.
Here are my questions:
Can someone give a concrete example when this field should be true and explain why?
Why is this field set to false on the auto-configuration classes?
Update:
Found two issues on github that give some explanation why it is false on most auto-configutation classes:
https://github.com/spring-projects/spring-boot/issues/9068
https://github.com/spring-projects/spring-framework/issues/22461
Something like this:
#Configuration(proxyBeanMethods=true)
public class SomeConfiguration {
#Bean
ServiceA serviceA(){
return new ServiceA(sharedService());
}
#Bean
ServiceB serviceB(){
return new ServiceB(sharedService());
}
#Bean
ServiceC sharedService(){
return new ServiceC();
}
}
Here, the proxyBeanMethods will assure that the 'sharedService' method will be intercepted and its result re-used. If you would follow the normal java logic, when calling serviceA() and serviceB(), there would be two different instances of ServiceC, and when calling sharedService() directly, a third instance would be created. Then proxy interceptor will make sure the actual method is only called once, so only one instance of the shared ServiceC is created, and both ServiceA and ServiceB will get the shared instance.
However proxyBeanMethods=true has a performance cost during startup, especially for libraries with a lot of # Configuration classes, like spring-boot's internal libraries. See e.g. https://github.com/spring-projects/spring-boot/issues/9068#issuecomment-461520814 for the impact on Spring WebFlux.
They couldn't change it to false by default, since that would break backwards compatibility. See links in the original question.
You can use different configuration patterns to avoid this, which is probably what auto-configuration classes do.
One way to do this is auto-wiring the service via method parameters instead of nested method calls. It makes less sence in normal Java, but it works in Spring configurations:
#Configuration(proxyBeanMethods=false)
public class SomeSmarterConfiguration {
#Bean
ServiceC sharedService(){
return new ServiceC();
}
#Bean
ServiceA serviceA(ServiceC sharedService){
return new ServiceA(sharedService);
}
#Bean
ServiceB serviceB(ServiceC sharedService){
return new ServiceB(sharedService);
}
}
Spring introduces proxyBeanMethods in version 5.2. The default value of proxyBeansMethods is true. That implies proxy objects will be generated for classes configured with #Configuration by default. In Springboot version 2 and onwards, all AutoConfiguration classes are configured with #Configuration(proxyBeanMethods = false). This is because the automatic Configuration feature is widely used in SpringBoot. Generating proxy objects will increase the startup time of spring and increase the object memory of the proxy part. The best practice is to annotate configuration classes with #Configuration(proxyBeanMethods = false)

How can I define multiple jobs dynamically in Spring Batch?

I have an application that uses Spring Batch to define a preset number of jobs, which are currently all defined in the XML.
We add more jobs over time which requires updating the XML, however these jobs are always based on the same parent and can easily be predetermined using a simple SQL query.
So I've been trying to switch to use some combination of XML configuration and Java-based configuration but am quickly getting confused.
Even though we have many jobs, each job definition falls into essentially one of two categories. All of the jobs inherit from one or the other parent job and are effectively identical, besides having different names. The job name is used in the process to select different data from the database.
I've come up with some code much like the following but have run into problems getting it to work.
Full disclaimer that I'm also not entirely sure I'm going about this in the right way. More on that in a second; first, the code:
#Configuration
#EnableBatchProcessing
public class DynamicJobConfigurer extends DefaultBatchConfigurer implements InitializingBean {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private JobRegistry jobRegistry;
#Autowired
private DataSource dataSource;
#Autowired
private CustomJobDefinitionService customJobDefinitionService;
private Flow injectedFlow1;
private Flow injectedFlow2;
public void setupJobs() throws DuplicateJobException {
List<JobDefinition> jobDefinitions = customJobDefinitionService.getAllJobDefinitions();
for (JobDefinition jobDefinition : jobDefinitions) {
Job job = null;
if (jobDefinition.getType() == 1) {
job = jobBuilderFactory.get(jobDefinition.getName())
.start(injectedFlow1).build()
.build();
} else if (jobDefinition.getType() == 2) {
job = jobBuilderFactory.get(jobDefinition.getName())
.start(injectedFlow2).build()
.build();
}
if (job != null) {
jobRegistry.register(new ReferenceJobFactory(job));
}
}
}
#Override
public void afterPropertiesSet() throws Exception {
setupJobs();
}
public void setInjectedFlow1(Flow injectedFlow1) {
this.injectedFlow1 = injectedFlow1;
}
public void setInjectedFlow2(Flow injectedFlow2) {
this.injectedFlow2 = injectedFlow2;
}
}
I have the flows that get injected defined in the XML, much like this:
<batch:flow id="injectedFlow1">
<batch:step id="InjectedFlow1.Step1" next="InjectedFlow1.Step2">
<batch:flow parent="InjectedFlow.Step1" />
</batch:step>
<batch:step id="InjectedFlow1.Step2">
<batch:flow parent="InjectedFlow.Step2" />
</batch:step>
</batch:flow>
So as you can see, I'm effectively kicking off the setupJobs() method (which is intended to dynamically create these job definitions) from the afterPropertiesSet() method of InitializingBean. I'm not sure that's right. It is running, but I'm not sure if there's a different entry point that's better intended for this purpose. Also I'm not sure what the point of the #Configuration annotation is to be honest.
The problem I'm currently running into is as soon as I call register() from JobRegistry, it throws the following IllegalStateException:
To use the default BatchConfigurer the context must contain no more than one DataSource, found 2.
Note: my project actually has two data sources defined. The first is the default dataSource bean which connects to the database that Spring Batch uses. The second data source is an external database, and this second one contains all the information I need to define my list of jobs. But the main one does use the default name "dataSource" so I'm not quite sure how else I can tell it to use that one.
First of all - I don't recommend using a combination of XML as well as Java Configuration. Use only one, preferably Java one as its not much of an effort to convert XML config to Java config. (Unless you have some very good reasons to do it - that you haven't explained)
I haven't used Spring Batch alone as I have always used it with Spring Boot and I have a project where I have defined multiple jobs and it always worked well for similar code that you have shown.
For your issue, there are some answers on SO like this OR this which are basically trying to say that you need to write your own BatchConfigurer and not rely on default one.
Now coming to solution using Spring Boot
With Spring Boot, You should try segregate job definitions and job executions.
You should first try to just define jobs and initialize Spring context without enabling jobs (spring.batch.job.enabled=false)
In your Spring Boot main method, when you start app with something like - SpringApplication.run(Application.class, args); ...you will get ApplicationContext ctx
Now you can get your relevant beans from this context & launch specif jobs by getting names from property or command line etc & using JobLauncher.run(...) method.
You can refer my this answer if willing to order job executions. You can also write job schedulers using Java.
Point being, you separate your job building / bean configurations & job execution concerns.
Challenge
Keeping multiple jobs in a single project can be challenging when you try to have different settings for each job as application.properties file is environment specific and not job specific i.e. spring boot properties will apply to all jobs.
In my particular case, the solution was to actually eliminate the #Configuration
and #EnableBatchProcessing annotations from my class above. Something about these caused it to try and use the DefaultBatchConfigurer which fails when you have more than one data source defined (even if you've identified them clearly with "dataSource" as the primary and some other name for the secondary).
The #Configuration class in particular wasn't necessary because all it really does is lets your class get auto-instantiated without having to define it as a bean in the app context. But since I was doing that anyway this one was superfluous.
One of the downsides of removing #EnableBatchProcessing was that I could no longer auto-wire the JobBuilderFactory bean. So instead I just had to do to create it:
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.afterPropertiesSet();
jobRepository = factory.getObject();
jobBuilderFactory = new JobBuilderFactory(jobRepository);
Then it seems I was on the right track already by using jobRegistry.register(...) to define my jobs. So essentially once I removed those annotations above everything started working. I'm going to mark Sabir's answer as the correct one however because it helped me out.

Can a Spring FactoryBean get access to all of the properties in the context?

I'm trying to write a Spring FactoryBean that will produce a list of Request objects. The number of requests and the values that go into them are configurable at runtime, so I want to use properties for these.
Each request comprises of a pair of ID values, so I need some way of providing the Factory Bean with a configurable list of these ID pairs (Call them A and B for now).
What I've got so far is to use a property that looks something like:
requests=1/2,3/4,5/6
which then defines three requests, one with A=1 and B=2, one with A=3 and B=4, and one with A=5 and B=6.
This is obviously a bit nasty to configure, and rather prone to errors. What would be much nicer would be to do something with the values split out over many properties, so the above could be something like:
requests.1.A=1
requests.1.B=2
requests.2.A=3
requests.2.B=4
requests.3.A=5
requests.3.B=6
Which just makes it a bit more obvious what is going on.
However, I can't find any way of having my FactoryBean configured to access all of the available properties, instead of just the specifically named property that is passed in from the context.
Am I missing something here? Or - even better - is there a better way of doing this kind of config that is easier supported and maintained?
You can inject an Environment bean into your FactoryBean instance, it is provided by the context and you do not have to configure it. I am not sure how you are configuring your beans, but I always favor Java config. So this example will use Java config.
#Configuration
class FactoryBeanConfig {
#Bean
public FactoryBean(final Environment env) {
return new MyFactoryBean(env);
}
}
The Environment instance will give you access to all the properties, because it is a PropertyResolver You can programmatically loop over the properties
int x = 1;
while(true) {
env.getRequiredProperty("requests." + x + ".A")
env.getRequiredProperty("requests." + x + ".B")
}
If you want to use that to create instances of a specific bean I would suggest using a PropertiesBeanDefinitionReader. Specify 2 property files to load (one well known to configure the defaults) and one to add beans.
core-request.properties
request.(class)=com.company.pkg.Request
requests.properties
requests1.A=1
requests1.B=2
requests2.A=3
requests2.B=4
requests3.A=5
requests3.B=6
Now you can use your FactoryBean to construct a BeanFactory and obtain all the beans and expose them as a list.
public class YourFactoryBean implements FactoryBean<List<Request>> {
#Autowired
private ResourceLoader rl;
public Object getObject() {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory;
PropertiesBeanDefinitionLoader loader = new PropertiesBeanDefinitionLoader(beanRegistry);
Resource core = rl.getResource(core-location);
Resource requests = rl.getResource(requests-location);
loader.loadBeanDefinitions(core);
loader.setDefaultParentBean("request");
loader.loadBeanDefinitions(requests);
return beanRegistry.getBeansOfType(Request.class).values();
}
}
Something like that.

Is it allowed to have instance variables in Spring Services?

I have a spring service that provides configuration data. When the service is invoked by the GUI, it loads the configuration data from the database. It turns out that this happens quite often during the rendering of a single request. I want to optimize this by caching the configuration data. However, I am not sure if this is a good programming style or if it is "allowed" to have an instance variable in a service.
Here is some example code of what I am thinking of doing:
#Serivce("MyConfigService")
public class MyConfigServiceImpl implements MyConfigService {
private Config cachedConfig;
#Override
public Config loadConfig() {
if (cachedConfig != null) {
// load config
cachedConfig = loadedConfig;
}
return cachedConfig;
}
#Override
public saveConfig(Config config) {
cachedConfig = null;
// save the configuration
}
}
Having a instance variable (not managed by spring) introduces the possibility of the service becoming thread unsafe. So I try to avoid them, or make sure they are thread safe.
You may want to look at #configurable and #postconstuct annotations to achieve your goals.
Are instance variables allowed in Spring service layer classes? Sure.
Is it a good idea to use one to save a reference to the Config object here?
Maybe, maybe not.
You're not showing how Config normally gets loaded... Does the same Config instance get returned to all users? i.e. - When User1 calls saveConfig then User2 calls loadConfig User2 gets the Config object User1 saved.
If so, you should be able to cache the value with no problems.
Also, instead of implementing it yourself, you could use Spring's annotation-based caching.
Instance variables is what Spring IoC container is all about; what is dubious in your design is that you have your own lazy-loading logic in loadConfig—that's the concern you must leave to Spring via lazy-init=true (or similar, I don't remember exactly). The design will also probably involve lookup methods and posibly request-scoped beans.

Conditionally creating beans in spring

Right now I'm exposing the service layer of my application using spring remoting's RMI/SOAP/JMS/Hessian/Burlap/HttpInvoker exporters. What I'd like is to allow the user to somehow define which of these remoting mechanisms they'd like enabled (rather than enabling all of them), then only create those exporter beans.
I was hoping that spring's application context xml's had support for putting in conditional blocks around portions of the xml. However, from what I've seen so far there's nothing in the standard spring distribution that allows you to do something like this.
Are there any other ways to achieve what I'm trying to do?
I am going to assume that you are looking to configure your application based on your environment, as in... for production I want to use this beans, in dev these other ...
As Ralph is saying, since Spring 3.1 you have profiles... But the key, is that you understand that you should put your environment based beans in different configuration files... so you could have something like dev-beans.xml, prod-beans.xml... Then in your main spring file, then you just invoke the appropriate one based on the environment that you are using... So profiles are only technique to do so... But you can also use other techniques, like have a system environmental variable, or pass a parameter in your build to decide which beans you want to use
You could realize this by using a Spring #Configuration bean, so you can construct your beans in java code. (see http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-java)
#Configuration
public class AppConfig {
#Bean
public MyService myService() {
if ( userSettingIshessian ) {
return new HessianExporter();
}else {
return new BurlapExporter();
}
}
}
Of course you need to get the user setting from somewhere, a system parameter would be easy, or config file, or something else.
Spring 3.1 has the concept of Profiles. My you can use them.

Categories