이전 글에 언급했듯 이번에는 @Value에 대해서 알아보도록 하겠다.
그때는 속성값들을 Spring에서 관리하는 bean의 field에 주입하는 용도로 사용되었었는데 실제로 그 용도로 주로 사용된다. 다만 단순 field뿐만 아니라 생성자나 메서드의 parameter에 주입하는 것도 가능하다.
관련 예제들을 실제로 사용해보려면 준비해야하는 파일들이 있다. 바로 property들을 정의한 파일, 즉 property file.
이에 대해서는 이전 글에서 자세히 다뤘다. 순수 Spring이면 @PropertySource annotation에 property 설정 파일을 적어서 @Configuration class에다가 설정해줘야 하고, Spring Boot라면 그냥 application.properties에 집어넣으면 된다. 다른 방법들은 앞에 링크한 글 참고. 이 글의 경우 밑과 같은 property file을 사용했다.
value.from.file=Value got from the file
priority=high
listOfValues=A,B,C
@Value로는 먼저 단순 String value를 직접 작성해가지고 field에 주입하는 것이 가능하다. 밑은 stringValue에 "string value"를 주입하는 코드다.@Value("string value")
private String stringValue;
Environment에 property들을 등록했으면 밑과 같이 ${...} 플레이스홀더를 사용해서 property 값을 주입하는 것이 가능하다. 밑은 property file의 value.from.file값, 즉 "Value got from the file"을 stringValue에 주입하는 코드다.@Value("${value.from.file}")
private String valueFromFile;
${...}에 있는 property 값이 Environment상에 정의되지 않았을 때 사용할 기본값도 설정이 가능하다. :를 사용하면 된다. 밑의 경우 unknown.param이 정의되지 않은 property라 some default라는 값이 someDefault라는 field에 주입된다.@Value("${unknown.param:some default}")
private String someDefault;
또 JVM 구동시 자동으로 설정되는 System Property라는 것이 있는데, 이 property들도 Environment에 등록되기에 @Value를 사용해서 접근하는 것이 가능하다. 유의할건, 만약 system property와 property file에서 둘 다 정의한 property가 있으면 해당 property 주입 시 system property의 것이 사용된다는 것이다.
마지막으로 배열에 주입하는 경우를 생각해보자. 이 경우 여러개의 값들을 한번에 주입하는 방법이 있으면 좋을 것이고... 실제로 이게 가능하다. 앞의 property file에서 listOfValues를 보면 쉼표로 구별된 값들을 볼 수 있는데, 이 property를 배열에 넣으면 자동으로 쉼표로 구별된 element 개수만큼의 배열이 만들어지게 된다. 즉 밑의 경우 ["A", "B", "C"]라는 값이 valuesArray에 주입된다.
@Value("${listOfValues}")
private String[] valuesArray;
SpEL은 Spring Expression Language의 줄임말이다. 관련 documentation 여기선 이에 대해 자세히 다루지는 않고 @Value에서 이를 어떻게 활용하는지 몇가지 예제만 보도록 하겠다.
밑과 같은 SpEl을 사용하면 priority라는 system property가 존재시 해당 값을 spelValue에 주입한다.
@Value("#{systemProperties['priority']}")
private String spelValue;
unknown이라는 system property가 존재시 해당 값을, 아닌 경우 'some default'를 spelSomeDefault에 주입한다.@Value("#{systemProperties['unknown'] ?: 'some default'}")
private String spelSomeDefault;
someBean이라는 bean의 someValue라는 값을 someBeanValue에 주입하는 것이 가능하다.@Value("#{someBean.someValue}")
private Integer someBeanValue;
List를 형성하는 것도 가능하다.@Value("#{'${listOfValues}'.split(',')}")
private List<String> valuesList;
@Value With Maps@Value로 Map에 해당하는 field를 집어넣거나, Map안의 특정 원소들을 집어넣는 것도 가능하다. 먼저 property file에 다음과 같은 property가 정의되어 있다고 해보자. 유의할건, Map안의 value들을 설정할 때 작은따옴표를 써야 한다는 것이다. 큰따옴표나 아무 따옴표 없이 value를 표현하면 안된다.valuesMap={key1: '1', key2: '2', key3: '3'}
Map type의 field에 주입하는 방법은 다음과 같다. SpEL을 활용한다.@Value("#{${valuesMap}}")
private Map<String, Integer> valuesMap;
Map 안의 특정 key에 대응되는 value를 주입하는 방법은 다음과 같다. 밑은 key1에 해당하는 값을 주입한다.@Value("#{${valuesMap}.key1}")
private Integer valuesMapKey1;
Map 안에 특정 key가 존재하는지 확실하지 않을 경우 존재하지 않으면 exception을 반환하는게 아닌, 해당 값을 null로 설정하도록 하는 SpEL expression이 존재한다. 밑은 unknownKey가 존재하지 않을 경우 unknownMapKey에 null값을 저장한다.@Value("#{${valuesMap}['unknownKey']}")
private Integer unknownMapKey;
Map을 주입하는 경우든, Map의 특정 key에 대응되는 value를 주입하는 경우든 값이 존재하지 않을 경우 집어 넣을 기본값을 설정하는 것이 SpEL에서 가능하다.@Value("#{${unknownMap : {key1: '1', key2: '2'}}}")
private Map<String, Integer> unknownMap;
@Value("#{${valuesMap}['unknownKey'] ?: 5}")
private Integer unknownMapKeyWithDefaultValue;
Map을 주입하는 경우, Map 안에서 특정 조건 만족을 하는 key-value pair만 집어넣는 것이 가능하다. 여기선 조건 비교를 key-value의 value로만 했지만 key로 하는 것도 가능하다. 이 경우 key를 사용.@Value("#{${valuesMap}.?[value>'1']}")
private Map<String, Integer> valuesMapFiltered;
@Value("#{systemProperties}")
private Map<String, String> systemPropertiesMap;
@Value Wtih Constructor InjectionPriorityProvider이라는 bean 형성 때 사용되는 생성자의 priority parameter에 priority라는 성분을 주입, 만약 해당 성분이 values.properties에 없는 경우 normal이라는 String을 주입하라는 코드다. 이러면 PriorityProvider bean은 priority 성분이 존재시 해당 값을 priority field에 저장하게 되고, 존재하지 않으면 normal이라는 값을 priority field에 저장하게 된다.@Component
@PropertySource("classpath:values.properties")
public class PriorityProvider {
private String priority;
@Autowired
public PriorityProvider(@Value("${priority:normal}") String priority) {
this.priority = priority;
}
// standard getter
}
@Value With Setter Injectionvalues.properties에 있는 listOfValues의 값들을 List 형태로 values에 주입한다. 구분자는 ,. 만약 해당 값이 없을 경우 오류가 나온다.@Component
@PropertySource("classpath:values.properties")
public class CollectionProvider {
private List<String> values = new ArrayList<>();
@Autowired
public void setValues(@Value("#{'${listOfValues}'.split(',')}") List<String> values) {
this.values.addAll(values);
}
// standard getter
}
@Value With RecordsJava 14에서는 record라는 keyword가 등장하게 되었다. class의 일종인데, 정의를 얼핏 보면 kotlin의 Data class랑 매우 유사하고, 실제로 많은 부분이 비슷하지만 차이점이 좀 있다. 이에 관해서는 다음 글 참고. 좀 더 제약사항이 많은 Data class로 보면 된다. 롬복의 @Data를 어느정도 대체하려고 나온 class이며, 주된 용도는 프로그래밍하다 보면 자주 사용되는, synchronization 관리 없이 유효한 data들을 보관하기 위한 class들을 간단하게 정의하는데 사용하기 위해서다.
여튼 이 record 형성 도중에 property file 내 값을 주입하는 것도 Spring에서 지원해준다. 정확히는 Spring 6.0.6부터 말이다. 밑은 PriorityRecord이라는 record bean을 형성할 때 필요로 하는 priority값을 values property file의 priority값으로 설정, 없는 경우 normal로 설정하라고 명시하는 코드다.
@Component
@PropertySource("classpath:values.properties")
public record PriorityRecord(@Value("${priority:normal}") String priority) {}