오늘은 Spring에서 제공해주는 Expression Language에 대해서 살펴보도록 하자. Spring Expression Language은 런타임시에 객체 그래프를 조회하고 조작하는 표현언어로 매우 강력하다. 이 표현언어는 아주 많은 기능을 지원하는데
문서를 참고하면 되겠다.
어떻게 사용하는지 한번 보고 어디에 유용하게 사용할 것인가를 살펴보도록 하자.
처음에는 문서에도 있다시피 아주 간단한 표현식을 살펴보자.
ExpressionParser
@Test
public void simple() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("Hello World");
String value = (String) exp.getValue();
assertThat(value).isEqualTo("Hello World");
}
SpelExpressionParser
클래스를 사용해서 해당하는 표현식을 파싱할 수 있다. 아주 간단하다. 물론 운영에서는 저렇게 사용하라는 것은 아니다. 예제이니 한번씩만 살펴보자.
@Test
public void stringLength() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("Hello World.length()");
Integer length = exp.getValue(Integer.class);
assertThat(length).isEqualTo(11);
}
위와 같이 String의 메서드를 호출할 수 도 있다. 뭐 별거 아닐 수도 있지만 어떻게 보면 조금 신기하다. 어쨋든 getValue() 메서드에는 아주 다양한 메서드가 존재한다. 첫 번째코드에서 봤던 getValue() 메서드는 아주 파라미터가 없는 메서드이다. 그래서 해당하는 타입에 맞게 형변환을 해주어야 한다. 이 보다는 두번째 코드에는 파라미터로 Class
타입을 받아 좀 더 깔끔하고 안전한 코드가 되었다. 필자의 경우에는 특별한 경우가 아니라면 두번째 코드를 사용하는 편이다.
EvaluationContext
위의 ExpressionParser
는 아주 간단하게 사용법만 알아봤다. 아마도 저렇게는 쓸일이 거의 없을 듯하다. 좀 더 유용하게 사용하려면 EvaluationContext
를 이용해서 코드를 작성해야 한다. 일단 코드로 보자.
class Foo {
private String name;
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Test
public void context() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name.length() < 10");
EvaluationContext context = new StandardEvaluationContext(new Foo("wonwoo"));
Boolean result = exp.getValue(context, Boolean.class);
assertThat(result).isTrue();
}
우의 코드는 Foo
클래의 속성중 name을 가져와 그 길이가 10보다 작으면 true를 던지고 그렇지 않으면 false를 던지는 그런 코드를 작성하였다. 위와 같이 어떠한 조건의 결과 값도 Boolean 형태의 값으로 리턴받을 수 있다. 이러한 코드는 어디서 많이 봤다. Spring에서 제공해주는 Cache를 사용해봤다면 아주 익숙한 코드이다.
@Cacheable(value = "test", key = "#id", condition = "#id.length() < 10")
public String fooBar(String id) {
// ...
}
이것 또한 Spring의 Expression Language
를 사용하여 Cacheable의 속성들을 파싱한다. 조금 유용하게 사용할 수 있을 것만 같다. 하지만 실체는..
위와 같이 EvaluationContext 를 사용해도 되지만 위와 같이 간단한 코드라면 EvaluationContext를 사용하지 않고 바로 getValue에 Object를 넣을 수 있다. 다음과 같이 말이다.
@Test
public void root() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name.length() < 10");
Boolean result = exp.getValue(new Foo("wonwoo"), Boolean.class);
assertThat(result).isTrue();
}
class Foo {
//
}
Array
Spring의 Expression Language은 배열도 접근 가능하게 해준다. 예제로 한번 살펴보도록 하자.
class Foo {
public List<String> names = new ArrayList<>();
@Override
public String toString() {
return "Foo{" +
"names=" + names +
};
}
}
@Test
public void array() {
ExpressionParser parser = new SpelExpressionParser();
Foo foo = new Foo();
foo.names = Arrays.asList("wonwoo", "kevin");
StandardEvaluationContext context = new StandardEvaluationContext(foo);
Expression expression = parser.parseExpression("names[0]");
String value = expression.getValue(context, String.class);
assertThat(value).isEqualTo("wonwoo");
}
우리가 배열의 원소를 가져올때 처럼 마찬가지로 [i]
를 이용해서 동일하게 가져오면 된다. 보기엔 그렇게 어렵지 않다. 뿐만 아니라 배열의 속성도 변경가능하다. 물론 배열만 되는 것은 아니고 아까 봤던 예제도 마찬가지로 조작가능하다.
@Test
public void arrayValue() {
ExpressionParser parser = new SpelExpressionParser();
Foo foo = new Foo();
foo.names = Arrays.asList("wonwoo", "kevin");
StandardEvaluationContext context = new StandardEvaluationContext(foo);
parser.parseExpression("names[0]").setValue(context, "test");
assertThat(foo.names.get(0)).isEqualTo("test");
}
첫 번째 배열인 wonwoo
를 test
로 변경하는 그런 코드이다. 그리 어려운 코드가 아니기에 각자 이것저것 한번씩 해보면 좋을 듯 싶다.
Message
필자가 그냥 지은 제목이다. 어떠한 message format을 지정한뒤 그에 맞게 값을 넣어주면 원하는 값을 받을 수 있는 그런 기능이다. 이게 가장 유용하게 쓰일듯 싶다. 물론 필자 생각이다. 예제를 보자.
@Test
public void message() {
String message = "my foo is #{name}, i bar #{age}";
SpelExpressionParser parser = new SpelExpressionParser();
Foo foo = new Foo("wonwoo", 33);
StandardEvaluationContext context = new StandardEvaluationContext(foo);
Expression expression = parser.parseExpression(message, ParserContext.TEMPLATE_EXPRESSION);
String value = expression.getValue(context, String.class);
assertThat(value).isEqualTo("my foo is wonwoo, i bar 33");
}
message 포맷은 "my foo is #{name}, i bar #{age}"
라는 String 문자열을 파싱하는 코드이다. Foo라는 클래스에 이름과 나이를 넣어주면 자동으로 프로퍼티에 맞게 메시지를 만들어 준다. 여기서 중요한건 ParserContext.TEMPLATE_EXPRESSION
라는 인스턴스이다. Spring에서 이미 만들어 놓은 기본 템플릿이다. 이 코드는 다음과 같다.
public static final ParserContext TEMPLATE_EXPRESSION = new ParserContext() {
@Override
public String getExpressionPrefix() {
return "#{";
}
@Override
public String getExpressionSuffix() {
return "}";
}
@Override
public boolean isTemplate() {
return true;
}
};
prefix는 #{
로 시작하고 suffix는 }
로 끝나는 템플릿을 자동으로 파싱해준다. 만약 위와 같이 #{
이 아니라 ${
로 시작하고 싶다면 다음과 같이 만들어서 사용하면 된다.
@Test
public void templateMessage() {
String message = "my foo is ${name}, i bar ${age}";
SpelExpressionParser parser = new SpelExpressionParser();
Foo foo = new Foo("wonwoo", 33);
StandardEvaluationContext context = new StandardEvaluationContext(foo);
Expression expression = parser.parseExpression(message, new ParserContext() {
@Override
public boolean isTemplate() {
return true;
}
@Override
public String getExpressionPrefix() {
return "${";
}
@Override
public String getExpressionSuffix() {
return "}";
}
});
String value = expression.getValue(context, String.class);
assertThat(value).isEqualTo("my foo is wonwoo, i bar 33");
}
따로 클래스를 만드는 것이 더 좋아보이지만 여기서는 예제이므로 익명클래스를 만들어 사용했다.
만약 객체말고 Map으로 하고 싶다면 어떻게 할까? 이것 또한 간단하다. StandardEvaluationContext
클래스에는 property를 무엇으로 접근할지 셋팅하는 부분이 있다. 이걸 이용해서 객체가아닌 Map으로 사용할 수 있다. 예제를 보자.
@Test
public void message() {
String message = "my foo is #{name}, i bar #{age}";
SpelExpressionParser parser = new SpelExpressionParser();
Map<String,String> map = new HashMap<>();
map.put("name", "wonwoo");
map.put("age", "33");
StandardEvaluationContext context = new StandardEvaluationContext(map);
context.addPropertyAccessor(new MapAccessor());
Expression expression = parser.parseExpression(message, ParserContext.TEMPLATE_EXPRESSION);
String value = expression.getValue(context, String.class);
assertThat(value).isEqualTo("my foo is wonwoo, i bar 33");
}
Spring에서 이미 만들어 놓은 MapAccessor
클래스를 사용하면 된다. 그럼 객체가 아닌 Map으로 해당 메세지를 파싱할 수 있다. 아마 기본은 ReflectivePropertyAccessor
를 사용하고 있는 것으로 보인다.
이렇게 오늘은 유용하면 유용하지만 잘 사용하지 않는다면 잘 모르는 Spring의 Expression Language를 살펴봤다. Spring의 Expression Language는 더욱 많은 기능을 제공해준다. 하지만 필자는 기본적인 예제와 자주 사용할만한 것으로 살펴봤다. 좀 더 관심이 있는 개발자라면 Spring의 문서를 참고하여 각자가 좀 더 많은 기능을 살펴보는 것을 좋을 듯 싶다.
오늘 이 코드들은 여기에 있으니 관심있다면 한번씩 돌려보거나 살펴보는 것도 나쁘지 않다.