들어가기 앞서
이런 상황이 있다고 합시다. 저는 API를 만들고 있고, request param으로 date값을 받고 싶습니다.
class Controller {
@GetMapping("/hello")
public void hello(
@RequestParam(name = "date") LocalDate date
) {
System.out.println("Hello, " + date);
}
}
이렇게 짜봤습니다.
그런데 여기에 date 값이 오지 않았을 때 기본값으로 오늘 날짜를 넣고 싶을 경우, 어떻게 해야 할까요?
class Controller {
@GetMapping("/hello")
public void hello(
@RequestParam(name = "date", defaultValue = LocalDate.now()) LocalDate date
) {
System.out.println("Hello, " + date);
}
}
이렇게 짤 수 있다면 좋겠지만, 어노테이션의 속성 값으로는 상수밖에 들어갈 수 없어 오류가 나게 됩니다. 이럴 때엔 어떻게 하면 좋을까요?
SpEL
먼저 Spring Expression Language (SpEL)에 대해 알아봅시다. 공식문서가 잘 되어 있는 스프링답게, 여기에서 자세한 내용을 살펴볼 수 있는데요.
https://docs.spring.io/spring-framework/docs/3.0.x/reference/expressions.html
다 읽어보기엔 시간이 없으니 간략하게 알아봅시다.
Spring 표현식 언어 (SpEL)은 런타임에 객체 그래프를 쿼리하고 조작할 수 있도록 지원하는 표현식 언어라고 합니다. 이 언어는 다음과 같은 기능을 제공한다고 해요.
리터럴 표현식
Boolean, 관계 연산자
정규 표현식
클래스 표현식
속성, 배열, 리스트, 맵에 액세스하기
메소드 호출
관계형 연산자
할당
생성자 호출
빈 참조
배열 생성
인라인 리스트
삼항 연산자
변수
유저가 정의한 함수
컬렉션 프로젝션
컬렉션 선택
템플릿 표현식
많죠? 하지만 이 글에서는 이 중 몇 가지만 알아보려고 합니다.
예제와 함께 간단한 SpEL 문법 살펴보기
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
SpEL을 시험해보고 싶은 경우, 다음과 같이 사용 가능합니다. 다음 코드를 실행해보면 message 변수에는 Hello World
가 담기게 됩니다.
만약 실행하려는 SpEL의 문법이 맞지 않을 경우 parseExpression
함수에서는 ParseException
, getValue
함수에서는EvaluationException
이 발생할 수 있다고 하네요.
함수 호출하기
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
앞에서 언급했듯, SpEL은 함수 호출도 가능합니다. 다음을 실행하면 Hello World
리터럴 문자열의 concat 함수를 실행하게 되어 Hello World!
와 같은 결과를 얻어낼 수 있습니다. 그렇다면 다음과 같이 사용할 수 있다는 예제와 함께 쭉쭉 넘어가볼까요.
생성자 호출하기
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class); // HELLO WORLD
위에서 언급했듯이, 생성자를 호출하는 것도 가능합니다.
객체의 속성 검색하기
class Human {
private String name;
private int age;
}
Human human = new Human("Alice", 20);
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = (String) exp.getValue(human); // "Alice"
다음과 같이 특정 객체 인스턴스의 속성을 검색하는 것도 가능합니다.
특정 타입 정의하기
특별한 연산자인 'T' 를 사용하면 특정 클래스의 타입을 지정해줄 수 있어요.
ExpressionParser parser = new SpelExpressionParser();
Class listClass = parser.parseExpression("T(java.util.List)").getValue(Class.class);
System.out.println(listClass); // class java.util.List
ExpressionParser parser = new SpelExpressionParser();
List list = parser.parseExpression("T(java.util.List).of(1, 2)").getValue(List.class);
System.out.println(list); // [1, 2]
이렇게 static 함수를 호출해주는 것도 가능하답니다.
어노테이션과 함께 사용하기
'들어가기 앞서'의 의문증을 해소하는 데에 한 걸음 가까워졌어요.
SpEL을 BeanDefinitions을 정의하는 데에 사용할 수도 있습니다. XML로 하는 방식, 어노테이션으로 하는 방식이 있는데, 저희는 스프링 부트의 시대를 살고 있으므로 어노테이션을 통해 사용하는 법을 알아봅시다.
@Value
@Value
어노테이션을 필드, 메서드 및 메서드/생성자 매개변수에 배치해서 기본값을 지정할 수 있다. 이 때 사용하는 표현식에 #{}
을 붙여주어야 한다!
@Value("#{15 + 57}") // 72
private int sktt1;
@Value("#{2 ^ 10}") // 1024
private int power;
@PreAuthorize, @PostAuthorize
스프링 시큐리티에서 자주 사용하는 이 어노테이션들도 사실은 SpEL로 되어 있어요.
@PreAuthorize("hasRole('ADMIN')") // 이렇게도 사용 가능하지만..
@PreAuthorize("1 == 2") // 이렇게도 사용 가능하다
보통은 hasRole
함수 등으로만 사용해서 잘 몰랐을 수 있지만, 근본은 SpEL이므로 1 == 2
같은 아무 boolean 조건식이나 넣어줄 수 있습니다. 이 경우 true가 나와야지만 인증되므로 1 == 2
는 아무도 실행할 수 없는 api가 되겠네요.
@RequestParam
오늘의 '들어가기 앞서'의 의문증이 나왔어요. RequestParam
의 defaultValue 부분도 SpEL을 사용할 수 있습니다. 따라서 앞의 의문인, 기본값으로 오늘 날짜를 넣고 싶을 경우는 이렇게 코드를 짜주면 되겠습니다.
class Controller {
@GetMapping("/hello")
public void hello(
@RequestParam(defaultValue = "#{T(java.time.LocalDate).now()}") date: LocalDate
) {
System.out.println("Hello, " + date);
}
}
고민 해결!
마무리 하며
SpEL은 나름 다양한 쓰임새가 있습니다. 다른 빈의 변수 값을 가져오는 데에 쓸 수도 있고, 문자열로 다이나믹하게 변수를 파싱해야할 때에 쓰일 수도 있습니다.
알아두면 언젠간 도움이 될 거라 생각이 드네요.