Spring @Autowired and Singletons
For anyone stumbling on this post trying to make some kind of static property storing mechanism using Spring, I have created a nice singleton class you can use which allows for centralizing properties pulled either from the environment (text file properties in my case) and/or from a database using a service. The refreshParameters() method can be called statically from anywhere in your code in order to pull all the properties from their respective sources again (this could be broken down into different methods to allow pulling only certain types of properties for example). The neat thing is that access to the underlying instance is completely hidden by relying on the getter methods to query the instance field.
The drawback is that we are injecting a static context via setter injection which is generally not recommended but it seems to be required to allow pulling the instance bean from the context statically (please let me know if there is another way!). Also, I'm sure its thread safety could be improved but this works perfectly for my needs :
package com.somepackage.utilities;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import com.somepackage.service.GlobalParamService;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
@Log4j2
public class ParameterUtils
{
private static ParameterUtils instance = null;
//Core
private boolean headless = false;
//Logging
private String logLevel = "";
private static ApplicationContext applicationContext;
private final Environment environment;
private final GlobalParamService globalParamService;
@PostConstruct
//Runs on bean creation to initialize parameters from environment and DB, is also
//called by the refreshParameters() method
private void init()
{
//Core
headless = !environment.getProperty("serviceGUI").equals("ON");
//Logging
logLevel = globalParamService.findByName("logLevel").getValue();
}
//creates instance if null, getting the ParameterUtils bean from the static
//context autowired via setter-injection
private synchronized static ParameterUtils getInstance()
{
if (instance == null)
{
instance = applicationContext.getBean(ParameterUtils.class);
}
return instance;
}
//Refreshes all parameters by querying the environment and DB again
public static void refreshParameters()
{
getInstance().init();
}
//Core
public static boolean headless()
{
return getInstance().headless;
}
//Logging
public static String logLevel()
{
return getInstance().logLevel;
}
@Autowired
//Autowires static context to allow creating the fully autowired
//instance variable with a getBean() call;
public void setApplicationContext(ApplicationContext applicationContext)
{
ParameterUtils.applicationContext = applicationContext;
}
}
With this, from anywhere in my code and statically, I can then just do ParameterUtils.headless() to see if I'm running in headless mode. This class eliminated hundreds of lines of code in my program.
******EDIT******
You could get fancy and replace the individual getter methods with a single one with variable return type using reflection, as I ended up doing :
@SuppressWarnings("unchecked")
public static <T> T getParameter(String name, Class<T> returnType)
{
try
{
return (T) Stream.of(getInstance().getClass().getDeclaredFields())
.filter((field) -> field.getName().equals(name))
.findAny().get().get(instance);
}
catch (Exception ex)
{
instance.logEntryService.logError(LogEntrySource.SERVICE, LogEntryType.CORE, "Error retrieving " + name
+ " parameter : " + ex.getMessage(), log);
}
return null;
}
In this case calls to ParameterUtils would look like this :
boolean headless = ParameterUtils.getParameter("headless", boolean.class);
Note that you will need @SuppressWarnings("unused")
on top of your class to avoid warnings about fields not being used. They are very much being used, reflectively.
Feel free to steal this or to suggest improvements!
Cheers!
When you constructor calls load() the Autowired dependencies are still unwired. The wiring takes place after the constructor has finished. Either make configService final and use constructor autowiring or remove load() from your constructor but annotate load() with @PostConstruct.