avatar
Octoping Blog

스프링 표현식 언어 (SpEL) 알아보기

Spring Expression Language (SpEL)을 알아보자
SpringBackend
6 months ago
·
8 min read

들어가기 앞서

이런 상황이 있다고 합시다. 저는 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은 나름 다양한 쓰임새가 있습니다. 다른 빈의 변수 값을 가져오는 데에 쓸 수도 있고, 문자열로 다이나믹하게 변수를 파싱해야할 때에 쓰일 수도 있습니다.

알아두면 언젠간 도움이 될 거라 생각이 드네요.


- 컬렉션 아티클






반갑습니다 😄