1

Consider the following class:

public class ExpressionTokenClassifier {

  private static final String       PROMQL_KEYWORDS = "promql-keywords.json";
  private static final List<String> KEYWORDS;

  static {
    List<PromQLKeyword> words = KeywordsExtractor.extractKeywords(PROMQL_KEYWORDS);
    KEYWORDS = words.stream().map(PromQLKeyword::getName).collect(Collectors.toList());
  }

  public static void classifyTokens(List<ExpressionToken> tokens) {
    ... // KEYWORDS is used in here
  }

}

Notice the hardwired file name (PROMQL_KEYWORDS) specified and used in the static initialization block. This works, but I would like to remove the hardcoded value and instead use dependency injection (Spring) to set the value from application.yaml. This class is intended to only be used statically.

My research indicates that the Spring framework does not allow setting a static field with the @Value annotation. I have seen some suggestions of a workaround, but do not understand how they would work. Here is an example where I attempted to do this:

@Component
public class ExpressionTokenClassifier2 {

  private static final List<String> KEYWORDS;

  private static String             KEYWORDS_FILE;

  @Value("${app.keywords-file}")
  public void setKeywordsFileName(String name) {
    KEYWORDS_FILE = name;
  }

  static {
    List<PromQLKeyword> words = KeywordsExtractor.extractKeywords(KEYWORDS_FILE);
    KEYWORDS = words.stream().map(PromQLKeyword::getName).collect(Collectors.toList());
  }

  public static void classifyTokens(List<ExpressionToken> tokens) {
    ... // KEYWORDS is used in here
  }

}

When I tried it, the application fails to start because of NPE (KEYWORDS_FILE is null). My immediate question is: Where and when would the setKeywordsFileName() method be called? Nowhere, as far as I can tell - and the results agree. Am I missing some logic?

Does anyone know of a way to accomplish this? I could change this class so that it's instantiated instead of statically accessed, but the class that uses this class is also a statically accessed class. I really don't want to have to change a bunch of code to fix this.

UPDATE:

Upon further investigation, it appears that this is essentially not doable, at least not without a lot of extra work that probably defeats the purpose. This link, to me, describes the situation quite well.

How to assign a value from application.properties to a static variable?

Looks like I'm going to have to think about this for awhile.

Joseph Gagnon
  • 1,731
  • 3
  • 30
  • 63
  • The static block will be executed before the constructor, which results in the NPE, so could move the static code to the constructor. But since it's now a Component, it probably should not have any static methods. But if it really does need to have static methods, then perhaps create an @Configuration class that receives the @Value, and sets the value in the `ExpressionTokenClassifier2` via a static setter. – Andrew S Apr 06 '23 at 16:32

1 Answers1

2

I would just use a @Component and @PostConstruct method for init task:

Spring conponent is singleton, so no need for static.

@Component // singleton
public class ExpressionTokenClassifier {
    
    @Value("${app.keywords-file}")
    private String file;

    private List<String> KEYWORDS;

    @PostConstruct
    public void init() {
       [...]
    }

}

if static access is essential, you can init your static class as soon as the application is started:

public class ExpressionTokenClassifier {
    
    private List<String> KEYWORDS;

    static void init(String file) {
        List<PromQLKeyword> words = KeywordsExtractor.extractKeywords(file);
        KEYWORDS = words.stream().map(PromQLKeyword::getName).collect(Collectors.toList());
    }
    
     public static void classifyTokens(List<ExpressionToken> tokens) {
        // KEYWORDS is used in here
    }

}

@Component
public class ExpressionTokenClassifierInitializer {
    
    @Value("${app.keywords-file}")
    private String file;

    @EventListener(ContextRefreshedEvent.class)
    public void onApplicationStartedEvent() {
       ExpressionTokenClassifier.init(file);
    }

}
alex
  • 8,904
  • 6
  • 49
  • 75