• Feed
  • Explore
  • Ranking
/
/
    🖥 백엔드

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

    Spring Expression Language (SpEL)을 알아보자
    SpringBackend
    O
    Octoping
    2024.07.03
    ·
    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은 나름 다양한 쓰임새가 있습니다. 다른 빈의 변수 값을 가져오는 데에 쓸 수도 있고, 문자열로 다이나믹하게 변수를 파싱해야할 때에 쓰일 수도 있습니다.

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







    - 컬렉션 아티클