Loading...
Spring Framework Reference Documentation 7.0.2의 Evaluation의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
This section introduces programmatic use of SpEL의 인터페이스와 expression language를 소개합니다. The complete language reference can be found in the Language Reference.
The following code demonstrates how to use the SpEL API to evaluate the literal string
expression, Hello World.
1ExpressionParser parser = new SpelExpressionParser(); 2Expression exp = parser.parseExpression("'Hello World'"); // (1) 3String message = (String) exp.getValue(); 4// Copied!
| 1 | message 변수의 값은 "Hello World"입니다. |
1val parser = SpelExpressionParser() 2val exp = parser.parseExpression("'Hello World'") // (1) 3val message = exp.value as String 4// Copied!
| 1 | message 변수의 값은 "Hello World"입니다. |
The SpEL classes and interfaces you are most likely to use are located in the
org.springframework.expression package and its sub-packages, such as spel.support.
ExpressionParser 인터페이스는 expression 문자열을 파싱하는 역할을 합니다.
앞의 예제에서 expression 문자열은 작은따옴표로 둘러싸인 문자열 리터럴입니다.
Expression 인터페이스는 정의된 expression 문자열을 평가하는 역할을 합니다.
parser.parseExpression(…)과 exp.getValue(…)를 호출할 때 발생할 수 있는
두 가지 유형의 예외는 각각 ParseException과 EvaluationException입니다.
SpEL은 메서드 호출, 프로퍼티 접근, 생성자 호출과 같은 광범위한 기능을 지원합니다.
다음 메서드 호출 예제에서는 문자열 리터럴 Hello World에서 concat 메서드를 호출합니다.
1ExpressionParser parser = new SpelExpressionParser(); 2Expression exp = parser.parseExpression("'Hello World'.concat('!')"); // (1) 3String message = (String) exp.getValue(); 4// Copied!
| 1 | message의 값은 이제 "Hello World!"입니다. |
1val parser = SpelExpressionParser() 2val exp = parser.parseExpression("'Hello World'.concat('!')") // (1) 3val message = exp.value as String 4// Copied!
| 1 | message의 값은 이제 "Hello World!"입니다. |
다음 예제는 문자열 리터럴 Hello World의 Bytes JavaBean 프로퍼티에 접근하는 방법을 보여줍니다.
1ExpressionParser parser = new SpelExpressionParser(); 2 3// invokes 'getBytes()' 4Expression exp = parser.parseExpression("'Hello World'.bytes"); // (1) 5byte[] bytes = (byte[]) exp.getValue(); 6// Copied!
| 1 | 이 줄은 리터럴을 바이트 배열로 변환합니다. |
1val parser = SpelExpressionParser() 2 3// invokes 'getBytes()' 4val exp = parser.parseExpression("'Hello World'.bytes") // (1) 5val bytes = exp.value as ByteArray 6// Copied!
| 1 | 이 줄은 리터럴을 바이트 배열로 변환합니다. |
SpEL은 prop1.prop2.prop3과 같은 표준 dot notation을 사용한 중첩 프로퍼티를
지원하며, 프로퍼티 값의 설정도 지원합니다. public 필드에도 접근할 수 있습니다.
다음 예제는 문자열 리터럴의 길이를 얻기 위해 dot notation을 사용하는 방법을 보여줍니다.
1ExpressionParser parser = new SpelExpressionParser(); 2 3// invokes 'getBytes().length' 4Expression exp = parser.parseExpression("'Hello World'.bytes.length"); // (1) 5int length = (Integer) exp.getValue(); 6// Copied!
| 1 | 'Hello World'.bytes.length는 리터럴의 길이를 제공합니다. |
1val parser = SpelExpressionParser() 2 3// invokes 'getBytes().length' 4val exp = parser.parseExpression("'Hello World'.bytes.length") // (1) 5val length = exp.value as Int 6// Copied!
| 1 | 'Hello World'.bytes.length는 리터럴의 길이를 제공합니다. |
다음 예제에서 보듯이, 문자열 리터럴을 사용하는 대신 String의 생성자를 호출할 수 있습니다.
1ExpressionParser parser = new SpelExpressionParser(); 2Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // (1) 3String message = exp.getValue(String.class); 4// Copied!
| 1 | 리터럴로부터 새로운 String을 생성하고 이를 대문자로 변환합니다. |
1val parser = SpelExpressionParser() 2val exp = parser.parseExpression("new String('hello world').toUpperCase()") // (1) 3val message = exp.getValue(String::class.java) 4// Copied!
| 1 | 리터럴로부터 새로운 String을 생성하고 이를 대문자로 변환합니다. |
제네릭 메서드 public <T> T getValue(Class<T> desiredResultType)의 사용에 주목하십시오.
이 메서드를 사용하면 expression의 값을 원하는 결과 타입으로 캐스팅할 필요가 없습니다.
값을 타입 T로 캐스트할 수 없거나 등록된 타입 컨버터를 사용하여 변환할 수 없는 경우
EvaluationException이 발생합니다.
SpEL의 보다 일반적인 사용법은 특정 객체 인스턴스(루트 객체라고 함)에 대해 평가되는
expression 문자열을 제공하는 것입니다. 다음 예제는 Inventor 클래스의 인스턴스에서
name 프로퍼티를 조회하는 방법과 boolean expression에서 name 프로퍼티를 참조하는
방법을 보여줍니다.
1// Create and set a calendar 2GregorianCalendar c = new GregorianCalendar(); 3c.set(1856, 7, 9); 4 5// The constructor arguments are name, birthday, and nationality. 6Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); 7 8ExpressionParser parser = new SpelExpressionParser(); 9 10Expression exp = parser.parseExpression("name"); // Parse name as an expression 11String name = (String) exp.getValue(tesla); 12// name == "Nikola Tesla" 13 14exp = parser.parseExpression("name == 'Nikola Tesla'"); 15boolean result = exp.getValue(tesla, Boolean.class); 16// result == true 17// Copied!
1// Create and set a calendar 2val c = GregorianCalendar() 3c.set(1856, 7, 9) 4 5// The constructor arguments are name, birthday, and nationality. 6val tesla = Inventor("Nikola Tesla", c.time, "Serbian") 7 8val parser = SpelExpressionParser() 9 10var exp = parser.parseExpression("name") // Parse name as an expression 11val name = exp.getValue(tesla) as String 12// name == "Nikola Tesla" 13 14exp = parser.parseExpression("name == 'Nikola Tesla'") 15val result = exp.getValue(tesla, Boolean::class.java) 16// result == true 17// Copied!
EvaluationContextEvaluationContext API는 expression을 평가할 때 프로퍼티, 메서드, 필드를
해결하고 타입 변환을 수행하는 데 사용됩니다. Spring은 두 가지
구현을 제공합니다.
SimpleEvaluationContext
SpEL language syntax 전체가 필요하지 않고 의미 있게 제한되어야 하는 expression 범주(예: 데이터 바인딩 expression, 프로퍼티 기반 필터 등)에 대해 필수적인 SpEL language 기능과 설정 옵션의 서브셋을 노출합니다.
StandardEvaluationContext
SpEL language 기능과 설정 옵션 전체를 노출합니다. 기본 루트 객체를 지정하고 사용 가능한 모든 평가 관련 전략을 구성하는 데 사용할 수 있습니다.
SimpleEvaluationContext는 SpEL language syntax의 서브셋만 지원하도록 설계되었습니다.
예를 들어, Java 타입 참조, 생성자, 빈 참조를 제외합니다.
또한 expression에서 프로퍼티와 메서드에 대한 지원 수준을 명시적으로 선택해야 합니다.
SimpleEvaluationContext를 생성할 때 SpEL expression에서 데이터 바인딩에 필요한
지원 수준을 선택해야 합니다.
PropertyAccessor(일반적으로 리플렉션 기반이 아님)와
DataBindingPropertyAccessor를 조합한 것편리하게도, SimpleEvaluationContext.forReadOnlyDataBinding()은
DataBindingPropertyAccessor를 통해 프로퍼티에 대한 읽기 전용 접근을 활성화합니다.
마찬가지로, SimpleEvaluationContext.forReadWriteDataBinding()은 프로퍼티에 대한
읽기 및 쓰기 접근을 활성화합니다. 또는
SimpleEvaluationContext.forPropertyAccessors(…)를 통해 custom accessor를 구성하고,
필요한 경우 할당을 비활성화하며, 선택적으로 빌더를 통해 메서드 해결
및/또는 타입 컨버터를 활성화할 수 있습니다.
기본적으로 SpEL은 Spring core(org.springframework.core.convert.ConversionService)에
있는 변환 서비스를 사용합니다. 이 변환 서비스에는 일반적인 변환을 위한
많은 내장 컨버터가 있지만, 타입 간 custom 변환을 추가할 수 있도록
완전히 확장 가능합니다. 추가로, 제네릭 인식입니다. 이는 expression에서 제네릭 타입을
사용할 때 SpEL이 만나는 객체에 대해 타입 정확성을 유지하기 위해 변환을 시도함을
의미합니다.
실제로 이것이 의미하는 바는 무엇일까요? setValue()를 사용한 할당이
List 프로퍼티를 설정하는 데 사용된다고 가정해 봅시다. 프로퍼티의 타입은 실제로
List<Boolean>입니다. SpEL은 리스트의 요소가 리스트에 넣기 전에 Boolean으로
변환되어야 함을 인식합니다. 다음 예제는 이를 수행하는 방법을 보여줍니다.
1class Simple { 2 public List<Boolean> booleanList = new ArrayList<>(); 3} 4 5Simple simple = new Simple(); 6simple.booleanList.add(true); 7 8EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); 9 10// "false" is passed in here as a String. SpEL and the conversion service 11// will recognize that it needs to be a Boolean and convert it accordingly. 12parser.parseExpression("booleanList[0]").setValue(context, simple, "false"); 13 14// b is false 15Boolean b = simple.booleanList.get(0); 16// Copied!
1class Simple { 2 var booleanList: MutableList<Boolean> = ArrayList() 3} 4 5val simple = Simple() 6simple.booleanList.add(true) 7 8val context = SimpleEvaluationContext.forReadOnlyDataBinding().build() 9 10// "false" is passed in here as a String. SpEL and the conversion service 11// will recognize that it needs to be a Boolean and convert it accordingly. 12parser.parseExpression("booleanList[0]").setValue(context, simple, "false") 13 14// b is false 15val b = simple.booleanList[0] 16// Copied!
SpEL expression 파서는 파서 설정 객체
(org.springframework.expression.spel.SpelParserConfiguration)를 사용하여
구성할 수 있습니다. 설정 객체는 일부 expression 구성 요소의 동작을
제어합니다. 예를 들어, 컬렉션에 인덱스를 지정했을 때 지정된 인덱스의 요소가
null이면 SpEL이 자동으로 요소를 생성할 수 있습니다. 이는 프로퍼티 참조
체인으로 구성된 expression을 사용할 때 유용합니다.
마찬가지로, 컬렉션에 인덱스를 지정했을 때 컬렉션의 현재 크기보다 큰 인덱스를 지정하면 SpEL이 해당 인덱스를
수용하도록 컬렉션을 자동으로 확장할 수 있습니다. 지정된 인덱스에 요소를
추가하기 위해 SpEL은 지정된 값을 설정하기 전에 요소 타입의 기본 생성자를
사용하여 요소를 생성하려고 시도합니다. 요소 타입에 기본 생성자가 없으면
null이 컬렉션에 추가됩니다. 값을 설정하는 방법을 아는 내장 컨버터 또는
custom 컨버터가 없으면 지정된 인덱스의 컬렉션에는 null이 그대로 남습니다.
다음 예제는 List를 자동으로 확장하는 방법을 보여줍니다.
1class Demo { 2 public List<String> list; 3} 4 5// Turn on: 6// - auto null reference initialization 7// - auto collection growing 8SpelParserConfiguration config = new SpelParserConfiguration(true, true); 9 10ExpressionParser parser = new SpelExpressionParser(config); 11 12Expression expression = parser.parseExpression("list[3]"); 13 14Demo demo = new Demo(); 15 16Object o = expression.getValue(demo); 17 18// demo.list will now be a real collection of 4 entries 19// Each entry is a new empty String 20// Copied!
1class Demo { 2 var list: List<String>? = null 3} 4 5// Turn on: 6// - auto null reference initialization 7// - auto collection growing 8val config = SpelParserConfiguration(true, true) 9 10val parser = SpelExpressionParser(config) 11 12val expression = parser.parseExpression("list[3]") 13 14val demo = Demo() 15 16val o = expression.getValue(demo) 17 18// demo.list will now be a real collection of 4 entries 19// Each entry is a new empty String 20// Copied!
기본적으로 SpEL expression은 10,000자를 초과할 수 없지만 maxExpressionLength는
구성 가능합니다. SpelExpressionParser를 프로그래밍 방식으로 생성하는 경우
SpelExpressionParser에 제공하는 SpelParserConfiguration을 생성할 때 custom
maxExpressionLength를 지정할 수 있습니다.
SpEL expression이 ApplicationContext 내에서
파싱될 때 사용되는 maxExpressionLength를 설정하려면(예: XML 빈 정의, @Value 등)
JVM 시스템 프로퍼티 또는 Spring 프로퍼티 spring.context.expression.maxLength를
애플리케이션에 필요한 최대 expression 길이로 설정할 수 있습니다(참고:
Supported Spring Properties).
Spring은 SpEL expression을 위한 기본 컴파일러를 제공합니다. expression은 일반적으로 인터프리트되며, 이는 평가 중에 많은 동적 유연성을 제공하지만 최적의 성능을 제공하지는 않습니다. 가끔 expression을 사용하는 경우에는 괜찮지만, Spring Integration과 같은 다른 컴포넌트에서 사용되는 경우 성능이 매우 중요할 수 있으며, 이때는 동적 특성이 실제로 필요하지 않을 수 있습니다.
SpEL 컴파일러는 이러한 요구를 해결하기 위한 것입니다. 평가 중에 컴파일러는 런타임에 expression 동작을 구현하는 Java 클래스를 생성하고 해당 클래스를 사용하여 훨씬 더 빠른 expression 평가를 수행합니다. expression 주변에 타이핑이 부족하기 때문에 컴파일러는 컴파일을 수행할 때 expression의 인터프리트된 평가 중에 수집된 정보를 사용합니다.
예를 들어, 프로퍼티 참조의 타입을 expression만으로는 알 수 없지만, 첫 번째 인터프리트된 평가 동안 그 타입을 알아냅니다. 물론, 이와 같이 도출된 정보에 기반한 컴파일은 나중에 다양한 expression 요소의 타입이 시간이 지나면서 변경되는 경우 문제를 일으킬 수 있습니다. 이러한 이유로, 컴파일은 반복 평가에서 타입 정보가 변경되지 않을 expression에 가장 적합합니다.
다음 기본 expression을 고려해 보십시오.
1someArray[0].someProperty.someOtherProperty < 0.1 2// Copied!
앞의 expression은 배열 접근, 일부 프로퍼티 역참조, 수치 연산을 포함하므로 성능 향상이 매우 눈에 띌 수 있습니다. 예제 마이크로 벤치마크에서 50,000번 반복을 실행한 결과 인터프리터를 사용한 평가에는 75ms가 걸렸고, 컴파일된 버전의 expression을 사용하는 데에는 3ms만 걸렸습니다.
컴파일러는 기본적으로 꺼져 있지만 두 가지 다른 방법 중 하나로 켤 수 있습니다. 파서 설정 프로세스( 앞에서 설명한)를 사용하거나, SpEL 사용이 다른 컴포넌트 내부에 임베딩되어 있을 때 Spring 프로퍼티를 사용할 수 있습니다. 이 섹션에서는 이 두 가지 옵션을 모두 다룹니다.
컴파일러는 org.springframework.expression.spel.SpelCompilerMode enum에
포착된 세 가지 모드 중 하나로 동작할 수 있습니다. 모드는 다음과 같습니다.
OFF
컴파일러가 꺼져 있으며 모든 expression은 인터프리트 모드에서 평가됩니다. 이것이 기본 모드입니다.
IMMEDIATE
immediate 모드에서는 expression이 가능한 한 빨리, 일반적으로 첫 번째 인터프리트된
평가 이후에 컴파일됩니다. 컴파일된 expression의 평가가 실패하면
(예를 들어, 앞에서 설명한 것처럼 타입 변경으로 인해) expression 평가의 호출자는
예외를 받습니다. 시간이 지남에 따라 다양한 expression 요소의 타입이 변경되는 경우
MIXED 모드로 전환하거나 컴파일러를 끄는 것을 고려하십시오.
MIXED
mixed 모드에서는 expression 평가가 시간이 지나면서 _인터프리트_와
컴파일 사이를 조용히 전환합니다. 일정 횟수의 인터프리트된 실행이 성공한 후
expression이 컴파일됩니다. 컴파일된 expression의 평가가 실패하면(예를 들어
타입 변경으로 인해) 그 실패는 내부적으로 포착되고, 시스템은 해당 expression에 대해
인터프리트 모드로 다시 전환합니다. 기본적으로 IMMEDIATE 모드에서 호출자가 받는
예외는 내부적으로 처리됩니다.
이후에 컴파일러가 다시 컴파일된 형태를 생성하고 해당 형태로 전환할 수 있습니다. 인터프리트 모드와 컴파일 모드 사이를 전환하는 이 사이클은 계속되며, 시스템이 더 이상 시도할 의미가 없다고 판단할 때(예: 특정 실패 임계값에 도달했을 때) 해당 expression에 대해 시스템이 영구적으로 인터프리트 모드로 전환합니다.
IMMEDIATE 모드는 MIXED 모드가 부작용이 있는 expression에 문제를 일으킬 수
있기 때문에 존재합니다. 컴파일된 expression이 부분적으로 성공한 후에 실패하면
이미 시스템 상태에 영향을 미치는 작업을 수행했을 수 있습니다. 이런 일이 발생한 경우
호출자는 expression이 인터프리트 모드에서 조용히 다시 실행되는 것을 원하지 않을 수
없습니다. expression의 일부가 두 번 실행될 수 있기 때문입니다.
모드를 선택한 후 SpelParserConfiguration을 사용하여 파서를 구성합니다.
다음 예제는 이를 수행하는 방법을 보여줍니다.
1SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, 2 this.getClass().getClassLoader()); 3 4SpelExpressionParser parser = new SpelExpressionParser(config); 5 6Expression expr = parser.parseExpression("payload"); 7 8MyMessage message = new MyMessage(); 9 10Object payload = expr.getValue(message); 11// Copied!
1val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, 2 this.javaClass.classLoader) 3 4val parser = SpelExpressionParser(config) 5 6val expr = parser.parseExpression("payload") 7 8val message = MyMessage() 9 10val payload = expr.getValue(message) 11// Copied!
컴파일러 모드를 지정할 때 ClassLoader를 지정할 수도 있습니다(null 전달 허용).
컴파일된 expression은 제공된 ClassLoader 아래에 생성된 child ClassLoader에
정의됩니다. ClassLoader를 지정하는 경우, 해당 ClassLoader가 expression 평가
프로세스에 관련된 모든 타입을 볼 수 있도록 하는 것이 중요합니다. ClassLoader를
지정하지 않으면 기본 ClassLoader가 사용되며, 일반적으로 expression 평가가
실행되는 스레드의 컨텍스트 ClassLoader입니다.
컴파일러를 구성하는 두 번째 방법은 SpEL이 다른 컴포넌트 내부에 임베딩되어 있고
설정 객체를 통해 구성하는 것이 불가능할 수 있는 경우에 사용됩니다.
이러한 경우 JVM 시스템 프로퍼티(또는
SpringProperties 메커니즘)를 통해
spring.expression.compiler.mode 프로퍼티를 SpelCompilerMode enum 값(off,
immediate, mixed) 중 하나로 설정할 수 있습니다.
Spring은 모든 종류의 expression 컴파일을 지원하지는 않습니다. 주요 초점은 성능이 중요한 컨텍스트에서 사용될 가능성이 높은 일반적인 expression에 맞춰져 있습니다. 다음과 같은 종류의 expression은 컴파일할 수 없습니다.
추가적인 종류의 expression 컴파일은 향후 지원될 수 있습니다.
Spring Expression Language (SpEL)
Expressions in Bean Definitions