Spring: 将一个值注入到一个静态访问的类中

huangapple go评论64阅读模式
英文:

Spring: Inject a value into a statically accessed class

问题

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.

https://stackoverflow.com/questions/45192373/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.

英文:

Consider the following class:

public class ExpressionTokenClassifier {

  private static final String       PROMQL_KEYWORDS = &quot;promql-keywords.json&quot;;
  private static final List&lt;String&gt; KEYWORDS;

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

  public static void classifyTokens(List&lt;ExpressionToken&gt; 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&lt;String&gt; KEYWORDS;

  private static String             KEYWORDS_FILE;

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

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

  public static void classifyTokens(List&lt;ExpressionToken&gt; 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.

https://stackoverflow.com/questions/45192373/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.

答案1

得分: 2

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

Spring conponent is singleton, so no need for static.

@Component // 单例
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);
    }

}
英文:

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(&quot;${app.keywords-file}&quot;)
    private String file;

    private List&lt;String&gt; 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&lt;String&gt; KEYWORDS;

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

}

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

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

}

huangapple
  • 本文由 发表于 2023年4月7日 00:13:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/75951620.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定