이전 글에 언급했듯 이번에는 @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) {}