4.1 평가 by sh

4.1. 평가

이 섹션에서는 SpEL 인터페이스와 표현식 언어의 간단한 사용법을 소개합니다. 완전한 언어 참조는 Language Reference에서 찾을 수 있습니다.

다음 코드는 SpEL API를 도입하여 리터럴 문자열 표현인 Hello World를 평가합니다.

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); 
String message = (String) exp.getValue();

message 변수의 값은 'Hello World'입니다.

설명.

  1. parser 역할을 할 ExpressionParser 클래스의 인스턴스 생성

  2. parseExpression 메서드를 통해 parsing 대상을 넣어줌. -> 현재 'Hello World'라는 내부 값을 가진 String 객체 하나를 대상으로 넣은 것. -> 그 결과 값이 무엇이 되었든 그 상태로 exp라는 객체에 넣어둠.

  3. exp라는 객체에서 getValue 메서드를 통해 결과 값 추출.

Cf. 리터럴 표현식을 제공하는 타입은 문자열(String), 숫자(int, real, hex), Boolean, null이다.

가장 많이 사용하는 SpEL 클래스와 인터페이스는 org.springframework.expression 패키지와 spel.support와 같은 그 하위 패키지에 있습니다.

ExpressionParser인터페이스는 표현식 문자열을 구문 분석(parsing)합니다. 앞의 예에서, 문자열 표현은 작은 따옴표로 묶인 문자열 리터럴입니다. Expression인터페이스는 앞에서 정의된 표현식 문자열을 평가합니다. parser.parseExpressionexp.getValue를 호출할 때 각각 ParseExceptionEvaluationException의 두 가지 예외가 발생할 수 있습니다.

SpEL은 메서드 호출, 속성 접근, 생성자 호출과 같은 다양한 기능을 지원합니다.

다음 메서드 호출 예제에서, 우리는 문자열 리터럴에 대해 ‘concat’ 메서드를 호출합니다.

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); 
String message = (String) exp.getValue();

message 값은 이제 'Hello World!' 입니다.

 SpEL의 메서드 호출 기능 (위 예제: String 리터럴에 대한 메서드 호출)

  • 메서드는 자바 문법을 사용하여 호출할 수 있다.

  • Literal에 대한 메서드 호출도 가능하다.

JavaBean 속성을 호출하는 다음 예제는 String속성 Bytes를 호출합니다.

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); 
byte[] bytes = (byte[]) exp.getValue();

이 행은 리터럴을 바이트 배열로 변환합니다.

SpEL은 표준 도트 표기법(예: prop1.prop2.prop3)과 속성 값 설정을 사용하여 중첩된 속성도 지원합니다. 다음 예제에서는 도트 표기법을 사용하여 리터럴 길이를 가져 오는 방법을 보여줍니다.

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); 
int length = (Integer) exp.getValue();

'Hello World'.bytes.length 는 리터럴의 크기를 나타냅니다.

String의 생성자는 문자열 리터럴을 사용하는 대신, 다음 예제와 같이 호출될 수 있습니다.

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); 
String message = exp.getValue(String.class);

리터럴을 새로운 String 으로 생성하여 대문자로 만듭니다.

일반적인 메서드 사용에 주의하십시오: public <T> T getValue(Class<T> desiredResultType)이 메서드를 사용하면, 표현 식의 값을 원하는 결과 타입으로 캐스팅할 필요가 없습니다. 값을 T타입으로 변환할 수 없거나 등록된 타입 컨버터를 사용하여 변환할 수 없는 경우 EvaluationException이 발생합니다.

SpEL의 보다 일반적인 사용법은 특정 객체 인스턴스(루트 객체라고 함)에 대해 평가되는 표현식 문자열을 제공하는 것입니다. 다음 예제에서는 Inventor클래스의 인스턴스에서 name속성을 검색하거나 Boolean 조건을 만드는 방법을 보여줍니다.

// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); 
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true

Parse name as an expression.

4.1.1. EvaluationContext 이해하기

EvaluationContext인터페이스는 표현식을 평가하여 속성, 메서드 또는 필드를 확인하고 타입 변환을 수행할 때 사용됩니다. 스프링은 두 가지 구현체를 제공합니다.

위의 ExpressionParser는 간단하게 사용 법만 알아봤고, 저렇게 사용할 일 거의 없음. 좀 더 유용하게 사용하려면 EvaluationContext 이용해서 코드를 작성해야 함.

  • SimpleEvaluationContext: SpEL 언어 구문의 전체 범위를 필요로 하지 않고 의미적으로 제한되어야 하는 표현식 범주에 대해 필수적인 SpEL 언어 기능 및 구성 옵션의 하위 집합을 보여줍니다. 예제는 데이터 바인딩 표현식과 속성 기반의 필터가 포함되지만, 이에 국한되지는 않습니다.

  • StandardEvaluationContext: SpEL 언어 기능 및 구성 옵션 전체를 보여줍니다. 이것을 사용하여 기본 루트 객체를 지정하고 사용 가능한 모든 평과 관련 전략을 구성할 수 있습니다.

SimpleEvaluationContext는 SpEL 언어 구문의 하위 집합만 지원하도록 설계되었습니다. 자바 타입 참조, 생성자 및 빈 참조를 제외합니다. 또한, 표현식에서 속성 및 메서드에 대한 지원 레벨을 명시적으로 선택해야 합니다. 기본적으로, create()static factory 메서드는 속성에 대한 읽기 접근만 허용합니다. 또한, 다음 중 하나 또는 그 조합을 대상으로 필요한 지원 레벨을 구성하는 Builder를 얻을 수 있습니다.

  • 사용자 정의 PropertyAccessor만 (Reflection 없음)

  • 읽기 전용 액세스를 위한 데이터 바인딩 속성

  • 읽기 및 쓰기 용 데이터 바인딩 속성

Foo 클래스 속성 중 name 을 가져와 그 길이가 10보다 작으면 true를 던지는 예제.

Ex.

  1. standardEvaluationContext는 name 프로퍼티가 평가될 객체를 지정하는 클래스이다. (위의 예제는 루트 객체, 즉 tesla 객체를 고정하여 사용하는 예제)

  2. parser.parseExpression("name"); 라인이 실행되면, tesla 객체의 name 프로퍼티가 파싱된다.

  3. name 변수에는 Nikola Tesla 문자열이 리턴된다.

타입 변환

기본적으로, SpEL은 스프링의 core(org.springframework.core.convert.ConversionService에서 사용 가능한 변환 서비스를 사용합니다. 이 변환 서비스에는 일반 변환을 위해 내장된 컨버터가 많이 포함되어 있지만, 완전히 확장 가능하므로 타입 간에 사용자 지정 변환을 추가할 수 있습니다. 추가적으로, 제네릭을 인식합니다. 즉, 표현식에서 일반 타입을 사용하여 작업할 때 SpEL은 발생하는 모든 객체의 타입 정확성을 유지하기 위해 변환을 시도합니다.

실제로 이것은 무엇을 의미합니까? setValue()를 사용하는 대입이 List속성을 설정하는 데 사용되고 있다고 가정합니다. 그 속성의 타입은 실제로 List<Boolean>입니다. SpEL은 리스트의 요소들이 배치되기 전에 Boolean로 변환되어야 한다는 것을 인식합니다. 다음 예제에서는 이를 수행하는 방법을 보여줍니다.

class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b is false
Boolean b = simple.booleanList.get(0);

4.1.2. Parser 구성

Parser 구성 객체(org.springframework.expression.spel.SpelParserConfiguration)를 사용하여SpEL 표현식 Parser를 구성할 수 있습니다. 구성 객체는 일부 표현식 구성 요소의 동작을 제어합니다. 예를 들어, 배열이나 컬렉션에 인덱스를 만들고 지정된 인덱스의 요소가 null인 경우에 그 요소를 자동으로 만들 수 있습니다. 이는 일련의 속성 참조로 구성된 표현식을 사용할 때 유용합니다. 배열 또는 리스트의 현재 크기 끝을 초과하는 인덱스를 지정하면, 배열 또는 리스트를 자동으로 확장하여 해당 인덱스를 수용할 수 있습니다. 다음 예제에서는 리스트를 자동으로 확장하는 방법을 보여줍니다.

class Demo {
    public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String

4.1.3. SpEL 컴파일

스프링 프레임워크 4.1에는 기본 표현식 컴파일러가 포함되어 있습니다. 표현식은 일반적으로 평가되는 동안 동적인 유연성을 많이 제공하지만 최적의 성능을 제공하지는 않습니다. 가끔씩 표현식을 사용하는 경우에는 괜찮습니다. 하지만, 스프링 통합과 같은 다른 구성 요소에서 사용하는 경우 성능이 매우 중요하며 동적 효과가 실제로 필요하지 않습니다.

SpEL 컴파일러는 이러한 필요를 해결하기 위한 것입니다. 평가 중에 컴파일러는 표현 동작을 구현하는 실제 자바 클래스를 생성하고 이를 사용하여 훨씬 빠른 표현 식 평가를 수행합니다. 표현 식에 대한 타이핑이 부족하기 때문에 컴파일러는 컴파일을 수행할 때 표현식의 해석된 평가 중에 수집된 정보를 사용합니다. 예를 들어, 표현 식으로부터 속성 참조의 타입을 순전히 알지 못하지만, 첫 번째 해석된 평가에서 그것이 무엇인 지 찾아냅니다. 물론, 이 정보를 기반으로 컴파일을 하면 다양한 표현 요소의 타입이 시간이 지남에 따라 변경되는 경우, 나중에 문제가 발생할 수 있습니다. 이러한 이유로 컴파일은 반복되는 평가에서 변경되지 않는 식에 가장 적합합니다.

다음 기본 표현 식을 고려하십시오.

someArray[0].someProperty.someOtherProperty < 0.1

앞의 표현식은 배열 액세스, 일부 속성 참조 해제 및 숫자 연산을 포함하기 때문에 성능이 크게 향상될 수 있습니다. 50000 반복의 예제 마이크로 벤치 마크 실행에서 인터프리터를 사용하여 평가하는 데 75 밀리 초가 걸렸으며 컴파일 된 버전의 표현을 사용하여 3 밀리 초 밖에 걸리지 않았습니다.

컴파일러 구성

컴파일러는 기본적으로 켜져 있지 않지만, 두 가지 방법으로 컴파일러를 켤 수 있습니다. 앞에서 설명한 Parser 구성 프로세스를 사용하거나(discussed earlier) SpEL 사용이 다른 구성 요소에 포함되어 있는 경우 시스템 속성을 사용하여 끌 수 있습니다. 이 섹션에서는 이 두 가지 옵션에 대해 설명합니다.

컴파일러는 org.springframework.expression.spel.SpelCompilerMode enum에 캡처된 세 가지 모드 중 하나에서 작동할 수 있습니다. 모드는 다음과 같습니다.

  • OFF (default): 컴파일러가 꺼져 있습니다.

  • IMMEDIATE: 즉시 모드에서는 표현 식이 가능한 한 빨리 컴파일 됩니다. 일반적으로 첫 번째 해석된 평가 이후입니다. 컴파일 된 표현식이 실패하면(일반적으로 앞에서 설명한대로 타입이 변경되어), 표현식 평가 호출자가 예외를 수신합니다.

  • MIXED: 혼합 모드에서는 표현 식이 해석 모드와 컴파일 모드 사이를 조용히 전환합니다. 해석된 실행이 몇 번 수행된 후 컴파일 된 양식으로 전환되고 컴파일 된 양식(예: 앞에서 설명한 대로 타입이 변경되는 경우)에 문제가 발생하면 자동으로 다시 해석된 양식으로 전환됩니다. 언젠가 나중에 다른 컴파일 된 양식을 생성하고 전환할 수 있습니다. 기본적으로 사용자가 IMMEDIATE모드에 있다는 예외는 내부적으로 처리됩니다.

MIXED모드는 부작용이 있는 표현식에 문제를 일으킬 수 있기 때문에 IMMEDIATE모드가 존재합니다. 컴파일된 표현식이 부분적으로 성공한 후에 불면, 시스템 상태에 영향을 미친 무언가를 이미 수행했을 수도 있습니다. 이 경우에는 호출자가 표현식의 일부가 두 번 실행될 수 있으므로 호출자가 자동으로 해석 모드로 다시 실행하지 못하게 할 수 있습니다.

모드를 선택한 후, SpelParserConfiguration을 사용하여 Parser를 구성하십시오. 다음 예제에서는 이를 수행하는 방법을 보여줍니다.

SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
    this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);

컴파일러 모드를 지정할 때 클래스 로더를 지정할 수도 있습니다(null 전달도 가능함). 컴파일 된 표현식은 제공된 클래스에서 작성된 하위 클래스 로더에 정의됩니다. 클래스 로더가 지정되면 표현식 평가 프로세스에 관련된 모든 타입을 볼 수 있도록 하는 것이 중요합니다. 클래스 로더를 지정하지 않으면 기본 클래스 로더(일반적으로 표현식 평가 중에 실행되는 스레드의 컨텍스트 클래스 로더)가 사용됩니다.

컴파일러는 구성하는 두 번째 방법은 SpEL이 다른 구성 요소 안에 포함되어 있고 구성 객체를 통해 구성할 수 없는 경우에 사용하는 것입니다. 이러한 경우 시스템 속성을 사용할 수 있습니다. pring.expression.compiler.mode속성을 SpelCompilerMode열거 형 값 중 하나(off, immediate, or mixed)로 설정할 수 있습니다.

컴파일러 제한 사항

스프링 프레임워크 4.1부터 기본적인 프레임 워크가 마련되어 있습니다. 그러나, 프레임 워크는 모든 종류의 표현식을 컴파일하는 것을 아직 지원하지 않습니다. 초기의 초점은 성능이 중요한 상황에서 사용되는 공통 표현 식에 있었습니다. 현재 다음 종류의 표현 식을 컴파일 할 수 없습니다.

  • 할당과 관련된 표현식

  • 변환 서비스를 사용하려는 표현식

  • 사용자 정의 Resolver 또는 접근 자를 사용하는 표현식

  • 선택 또는 투영을 사용하는 표현식

더 많은 표현식의 타입이 미래에 컴파일 될 수 있습니다.

Last updated