[java]Spel(Spring Expression Language) 관련

1. Overview

  • SpEL(Spring Expression Language)은 expression language 이고 런타임에 동작한다.
  • XML이나 Spring configurations기반의 annotation 으로 사용할수 있다.

  • 언어로 사용할수 있는 몇가지 operators이 아래와 같이 있다.
Type Operators
Arithmetic +, -, *, /, %, ^, div, mod
Relational <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
Logical and, or, not, &&, ||, !
Conditional ?:
Regex matches

2. Operators

  • 아래 예는 annotation기반의 설정을 기반으로 한다 XML 설정은 다루지 않을 예정이다. For these examples, we will use annotation-based configuration. More details about XML configuration can be found in later sections of this article.
  • SpEL 표현은 # 기호고 시작하고 중괄호로 묶고(#{expression), 속성은 $ 기호로 시작하여 중괄호로 묶는 방식을 택하고있다. (${property.name}.). 속성 자리 표시자는 SpEL 표현식을 포함할수없지만 표현식에는 속성 참조가 포함될수있다.
  • 간단하게 아래 예제를 보자
    • 이 예제에서 someProperty 값이 2라고 가정하면 결과 표현식은 2 +2가 되어 4로 평가된다.
      #{${someProperty} + 2}
      

2.1. 산술 Operators

  • 모든 기본 산술 연산자가 지원된다.
@Value("#{19 + 1}") // 20
private double add; 

@Value("#{'String1 ' + 'string2'}") // "String1 string2"
private String addString; 

@Value("#{20 - 1}") // 19
private double subtract;

@Value("#{10 * 2}") // 20
private double multiply;

@Value("#{36 / 2}") // 19
private double divide;

@Value("#{36 div 2}") // 18, the same as for / operator
private double divideAlphabetic; 

@Value("#{37 % 10}") // 7
private double modulo;

@Value("#{37 mod 10}") // 7, the same as for % operator
private double moduloAlphabetic; 

@Value("#{2 ^ 9}") // 512
private double powerOf;

@Value("#{(2 + 2) * 2 + 9}") // 17
private double brackets;
  • 나누기는 /, 모듈로는 % 명령어로도 표현할수있고, + 명령어는 는 문자열 합치는 용도로도 사용 가능하다.

2.2. Relational and Logical Operators

@Value("#{1 == 1}") // true
private boolean equal;

@Value("#{1 eq 1}") // true
private boolean equalAlphabetic;

@Value("#{1 != 1}") // false
private boolean notEqual;

@Value("#{1 ne 1}") // false
private boolean notEqualAlphabetic;

@Value("#{1 < 1}") // false
private boolean lessThan;

@Value("#{1 lt 1}") // false
private boolean lessThanAlphabetic;

@Value("#{1 <= 1}") // true
private boolean lessThanOrEqual;

@Value("#{1 le 1}") // true
private boolean lessThanOrEqualAlphabetic;

@Value("#{1 > 1}") // false
private boolean greaterThan;

@Value("#{1 gt 1}") // false
private boolean greaterThanAlphabetic;

@Value("#{1 >= 1}") // true
private boolean greaterThanOrEqual;

@Value("#{1 ge 1}") // true
private boolean greaterThanOrEqualAlphabetic;
  • 모든 관계 연산자에도 알파벳으로 사용할수있는데 XML 구반 구성에는 몇가지것을을 사용할수없다(<, <=, >, >=)
  • 대신 lt (보다 작음), le (보다 작거나 같음l), gt (보다 큼), or ge (크거나 같음) 을 사용할 수있다.

2.3. Logical Operators

@Value("#{250 > 200 && 200 < 4000}") // true
private boolean and; 

@Value("#{250 > 200 and 200 < 4000}") // true
private boolean andAlphabetic;

@Value("#{400 > 300 || 150 < 100}") // true
private boolean or;

@Value("#{400 > 300 or 150 < 100}") // true
private boolean orAlphabetic;

@Value("#{!true}") // false
private boolean not;

@Value("#{not true}") // false
private boolean notAlphabetic;

2.4. Conditional Operators

  • 조건 연산자는 일부 조건에 따라 다른 값을 주입하는데 사용된다.
@Value("#{2 > 1 ? 'a' : 'b'}") // "a"
private String ternary;
  • 삼항 연산자는 if-then-else 조건부 논리를 수행할때 사용되고, 일부 변수가 null인지 아닌지 확인한 다음 변수 값이나 기본값을 반환하는데 사용된다.
@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
private String ternary;
  • Elvis 연산자는 Groovy 언어에서 사용되는 경우에 삼항연산자 구문을 단축하는 방법이다. SpEL에서도 사용할수 있다.
  • 아래의 예제는 위의 예제와 동일하다
    @Value("#{someBean.someProperty ?: 'default'}") // Will inject provided string if someProperty is null
    private String elvis;
    

2.5. Using Regex in SpEL

  • matches operator는 정규식을 통해 스트링 매칭을 확인할수 있다
@Value("#{'100' matches '\\d+' }") // true
private boolean validNumericStringResult;

@Value("#{'100fghdjf' matches '\\d+' }") // false
private boolean invalidNumericStringResult;

@Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }") // true
private boolean validAlphabeticStringResult;

@Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }") // false
private boolean invalidAlphabeticStringResult;

@Value("#{someBean.someValue matches '\d+'}") // true if someValue contains only digits
private boolean validNumericValue;

2.6. Accessing List and Map Objects

  • SpEL를 이용해 Map이나 List에 SpEL을 사용해 접근가능하다. workersHolder 란 새로운 빈을 생성한 아래 예제를 살펴보자.
@Component("workersHolder")
public class WorkersHolder {
    private List<String> workers = new LinkedList<>();
    private Map<String, Integer> salaryByWorkers = new HashMap<>();

    public WorkersHolder() {
        workers.add("John");
        workers.add("Susie");
        workers.add("Alex");
        workers.add("George");

        salaryByWorkers.put("John", 35000);
        salaryByWorkers.put("Susie", 47000);
        salaryByWorkers.put("Alex", 12000);
        salaryByWorkers.put("George", 14000);
    }

    //Getters and setters
}

이제 아래처럼 접근이 가능하다.

@Value("#{workersHolder.salaryByWorkers['John']}") // 35000
private Integer johnSalary;

@Value("#{workersHolder.salaryByWorkers['George']}") // 14000
private Integer georgeSalary;

@Value("#{workersHolder.salaryByWorkers['Susie']}") // 47000
private Integer susieSalary;

@Value("#{workersHolder.workers[0]}") // John
private String firstWorker;

@Value("#{workersHolder.workers[3]}") // George
private String lastWorker;

@Value("#{workersHolder.workers.size()}") // 4
private Integer numberOfWorkers;

3. 활용예시

  • 아래 예제와 같이 SpEL을 사용하여 defaultValue 를 설정할 수 있음
  • 입력값이 없는 경우 현재 날짜보다 3개월 전을 default 로 세팅함

 @GetMapping(value = "/path/...")
public String test(
    @RequestParam(name = "begin", required = false, defaultValue = "#{T(java.time.OffsetDateTime).now().minusMonths(3)}") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime begin, 
    @RequestParam(name = "end", required = false, defaultValue = "#{T(java.time.OffsetDateTime).now()}") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime end) {
    .
    .   
}

참고

https://www.baeldung.com/spring-expression-language