Search code examples
javaspringspring-mvcjavabeansspring-java-config

Java Setting Static field with spring beans


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?


Solution

    1. In AppConstants change setAppName() to receive a String (why Properties?)

      public final class AppConstants {
          // ...
      
          public static void setAppName(String appName) {
              APP_NAME = appName;
          }
      }
      
    2. 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:

    1. It makes testing a bit harder:

      • When using configurable static fields, it's pretty easy to forget to set one and screw up tests.
      • It makes running tests (with different configs) in parallel much harder.
    2. 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.

    3. (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;
          }
      }