Reputation: 6116
I have a spring boot project and I am trying to set some static constant variables from spring beans. Here are the relevant files
application.properties
app.constants.name=FooTester
app.constants.version=v1
app.constants.port=123
AppConfig.java
@Configuration
public class AppConfig {
@Value("${app.constants.name}")
private String appName;
@Bean
public MethodInvokingBean initAppConstants() {
MethodInvokingBean miBean = new MethodInvokingBean();
miBean.setStaticMethod("app.constants.AppConstants.setAppName");
miBean.setArguments(new String[]{appName});
try {
miBean.prepare();
miBean.invoke();
} catch (Exception e) {
e.printStackTrace();
}
return miBean;
}
public void setAppName(String appName) {
this.appName = appName;
}
}
AppConstants.java
public final class AppConstants {
public static String APP_NAME;
public static String APP_VERSION;
public static String APP_PORT;
private AppConstants(){}
public static void setAppName(Properties p) {
APP_NAME = p.toString();
}
}
This gets the value of name fine but when it gets to the setAppName
method, the properties value is a hashmap with the key as FooTester
and value as ""
. If I added in multiple values with the setArguments
method like so:
miBean.setArguments(new String[]{"test", "test2", "test3"});
The properties object would only have 1 entry in the hashmap and the key would just be test, test2, test3
and the value as ""
.
Additionally, I would like the constant values to be final
rather than just public static
. So I'm not sure that this is the right way to do it. How do I accomplish what I want?
Upvotes: 2
Views: 4724
Reputation: 2672
In AppConstants
change setAppName()
to receive a String
(why Properties
?)
public final class AppConstants {
// ...
public static void setAppName(String appName) {
APP_NAME = appName;
}
}
Why use reflection? you can simply invoke the static method.
Your entire AppConfig
class can be as simple as this:
@Configuration
public class AppConfig {
@Value("${app.constants.name}")
public void setAppName(String appName) {
AppConstants.setAppName(appName);
}
}
P.S. Settings static
fields from Spring is not considered a best practice.
Edit: Answering "I would like to know what would be best practice"
A: First, why is injecting to static
fields isn't recommended:
It makes testing a bit harder:
static
fields, it's pretty easy to forget to set one and screw up tests.Theoretically, you can have 2 Spring contexts loaded, each with a different config, then when the second context loads it overrides values set by the first context (so you can't be using static fields in that situation and you'll need to replace all usages).
In practice this could happen, for instance, when you have two webapps (with different configs) sharing a jar containing AppConstants
.
(This is no so common, but - somewhere down the line you might want to change your config scope (e.g. from Singleton to Prototype or Session scope). This will be harder with static
fields.)
What is the best practice?
The best practice is simple (but might be hard to move to) - just use injection all the way:
Where you have AppConstants.APP_NAME
replace it with:
@Inject AppConstants appConstants;
// ...
appConstants.getAppName()
But only change it if you feel what I said makes sense and worth the effort in your case.
If you do consider changing it, I recommend the following:
Consider renaming AppConstants
to AppProperties
. By convention such beans, containing injected properties, are usually named XxxProperties.
Consider using @ConfigurationProperties
like so:
(Removes some boilerplate).
@EnableConfigurationProperties
public class AppConfig {
}
@ConfigurationProperties("app.constants")
public class AppProperties {
private String name; // = ${app.constants.name}
private String version; // = ${app.constants.version}
private int port; // = ${app.constants.port}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
Upvotes: 3