7.5 언어 레퍼런스
7.5.1 리터럴 표현식
지 원하는 리터럴 표현식의 타입은 문자열, 날짜, 숫자값(int, real, hex), 불리언, null이다. 문자열은 따옴표로 구분된다. 문자열내에 따옴표를 사용하려면 2개의 따옴표를 사용해라. 다음 예제에서 리터럴의 사용방법을 간단히 보여준다. 보통은 이 예제처럼 단독적으로 쓰이지 않고 더 복잡한 표현식의 일부로써 사용한다. 예를 들면 논리적인 비교연산의 한쪽부분에 리터럴을 사용한다.
ExpressionParser parser = new SpelExpressionParser();
// "Hello World"로 평가된다
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// 2147483647로 평가된다
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
숫자는 음수기호, 지수표시, 소수점을 지원한다. 기본적으로 실제 숫자는 Double.parseDouble()로 파싱한다.
7.5.2 프로퍼티, 배열, 리스트, 맵, 인덱서
프 로퍼티 참조를 탐색하는 것은 쉬운데 그냥 중첩된 프로퍼티 값을 가리키는 마침표를 사용해라. Inventor 클래스의 pupin과 tesla 인스턴스에는 예제에 사용한 클래스들 섹션에 나온 데이터들이 있다. 다음의 표현식을 사용해서 Tesla가 태어난 해솨 Pupin이 태어난 도시를 탐색한다.
// 1856으로 평가된다.
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
프로퍼티명의 첫글자는 대소문자를 구별하지 않는다. 배열과 리스트의 내용은 대괄호를 사용해서 획득한다.
ExpressionParser parser = new SpelExpressionParser();
// 발명품 배열
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
// "Induction motor"로 평가된다.
String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class);
// 회원 리스트
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
// "Nikola Tesla"로 평가된다.
String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class);
// 리스트와 배열 탐색
// "Wireless communication"로 평가된다.
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext, String.class);
대괄호로 키값 리터럴을 지정해서 맵의 내용을 획득한다. 아래 예제의 경우 Officers 맵의 키가 문자열이므로 문자열 리터럴을 지정할 수 있다.
// Officer의 딕션어리
Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, Inventor.class);
// "Idvor"로 평가된다
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, String.class);
// 값을 설정한다
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");
7.5.3 인라인 리스트
리스트는 {} 표시법을 사용한 표현식으로 직접 나타낼 수 있다.
// 4개의 숫자를 담고 있는 자바 리스트로 평가된다
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
단독으로 {}를 사용하면 비어있는 리스트를 의미한다. 성능때문에 리스트 자체가 고정된 리터럴로 구성되어 있다면 각 평가마다 새로운 리스트를 만드는 것이 아니라 표현식을 나타내는 변하지 않는 리스트를 생성한다.
7.5.4 배열 생성
배열은 자바문법과 유사하게 만들 수 있고 선택적으로 생성시에 존재해야 하는 배열을 갖는 initializer를 제공할 수 있다.
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// initializer가진 배열
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// 다차원 배열
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
현재는 다차원 배열을 생성할 때 initializer를 제공할 수 없다.
7.5.5 메서드
메서드는 전형적인 자바 프로그래밍 문법을 사용해서 호출한다. 리터럴에서 메서드를 호출할 수도 있다. 가변인자(Varargs)도 지원한다.
// 문자열 리터럴, "bc"로 평가된다
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
// true로 평가된다.
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, Boolean.class);
7.5.6 연산자(Operators)
7.5.6.1 관계 연산자
표준 연산자 표기법을 사용해서 같다, 같지 않다, 작다, 작거나 같다, 크다, 크거나 같다 등의 관계 연산자를 지원한다.
// true로 평가된다
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// false로 평가된다
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// true로 평가된다
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
표준 관계 연산자뿐만 아니라 SpEL은 'matches' 연산자에 기반한 정규표현식과 'instanceof'를 지원한다.
// false로 평가된다
boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);
// true로 평가된다
boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// false로 평가된다
boolean falseValue = parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
각 심볼릭 연산자는 순수하게 연문자로 지정할 수도 있다. 표현식을 내장하는 문서형식에서(예를 들면 XML 문서) 특별한 의미를 가지는 기호를 사용할 때 발생할 수 있는 문제를 피하기 위해서 사용한다. 문자표현은 다음과 같다. lt ('<'), gt ('>'), le ('<='), ge ('>='), eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!'). 대소문자는 구별하지 않는다.
7.5.6.2 논리 연산자
and, or, not 같은 논리 연산자를 지원한다. 사용방법은 다음 예제에 나온다.
// -- AND --
// false로 평가된다
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// true로 평가된다
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// true로 평가된다
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// true로 평가된다
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstien')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// false로 평가된다
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
7.5.6.3 수식 연산자
더 하기 연산자를 숫자, 문자열 날짜에서 사용할 수 있다. 빼기는 숫자와 날짜에서 사용할 수 있다. 곱하기와 나누기는 숫자에서만 사용할 수 있다. 다른 수식 연산자로 계수(%)와 지수(^)를 지원한다. 표준 연산자를 우선적으로 처리한다. 이러한 연산자는 다음 예제에서 보여준다.
// 더하기
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// 빼기
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// 곱하기
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// 나누기
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// 계수(Modulus)
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// 연산자 우선순위
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
7.5.7 할당
할당 연산자를 사용해서 프로퍼티를 설정한다. 이는 보통 setValue 호출내에서 이뤄지지만 getValue호출내에서도 이뤄질 수 있다.
Inventor inventor = new Inventor();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
// 대신에
String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
7.5.8 타입
특 수한 'T' 연산자를 java.lang.Class ('type')의 인스턴스를 지정하는 데 사용할 수 있다. 정적 메서드도 이 연산자를 사용해서 호출한다. StandardEvaluationContext는 타입을 찾으려고 TypeLocator를 사용하고 StandardTypeLocator(교체할 수 있다)는 java.lang 패키지로 만들어진다. 즉, java.lang 내에서 타입을 참조하는 T()는 정규화될 필요는 없지만 다른 모든 타입참조는 정규화되어야 한다.
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean
trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING
< T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);
7.5.9 생성자
생성자는 새로운 연산자를 사용해서 호출할 수 있다. 프리미티브 타입과 String 외에는(int, float등이 사용될 수 있는) 모두 정규화된 클래스명을 사용해야 한다.
Inventor einstein = p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
//리스트의 add 메서드내에서 새로운 inventor 인스턴스를 생성한다
p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
.getValue(societyContext);
7.5.10 변수
#변수명 문법을 사용해서 표현식내에서 변수를 참조할 수 있다. StandardEvaluationContext에서 setVariable 메서드를 사용해서 변수를 설정한다.
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context);
System.out.println(tesla.getName()) // "Mike Tesla"
7.5.10.1 #this와 #root 변수
#this 변수는 항상 정의되어 있고 현재 평가객체를(정규화되지 않은 참조를 처리하는 것에 대비해서) 참조한다. #root변수도 항상 정의되어 있고 루트 컨텍스트 객체를 참조한다. #this가 평가되는 표현식 컴포넌트에 따라 다양하지만 #root는 항상 루트를 참조한다.
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// 파서를 생성하고 'primes' 변수를 정수 배열로 설정한다
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);
// 리스트에서 10보다 큰 모든 소수(?{...} 선택을 사용)
// [11, 13, 17]로 평가된다
List<Integer>
primesGreaterThanTen = (List<Integer>)
parser.parseExpression("#primes.?[#this>10]").getValue(context);
7.5.11 함수
표현식 문자열내에서 호출할 수 있는 사용자 정의 함수를 등록해서 SpEL을 확장할 수 있다. 사용자 정의 함수는 메서드를 사용해서 StandardEvaluationContext에 등록한다.
public void registerFunction(String name, Method m)
자바 메서드에 대한 참조는 함수의 구현체를 제공한다. 예를 들어 다음에서 문자열을 뒤집는 유틸리티 메서드를 보여준다.
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder();
for (int i = 0; i < input.length(); i++)
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
이 메서드를 평가 컨텍스트에 등록하고 표현식 문자열내에서 사용할 수 있다.
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("reverseString",
StringUtils.class.getDeclaredMethod("reverseString",
new Class[] { String.class }));
String helloWorldReversed =
parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
7.5.12 빈(Bean) 참조
평가 컨텍스트가 빈 리졸버로 설정되었다면 (@) 기호를 사용해서 표현식에서 빈을 검색하는 것이 가능하다.
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// 평가하는 동안 MyBeanResolver에서 resolve(context,"foo")를 호출할 것이다
Object bean = parser.parseExpression("@foo").getValue(context);
7.5.13 3항 연산자 (If-Then-Else)
표현식에서 if-then-else 조건을 위해 3항 연산자를 사용할 수 있다. 다음은 간단한 예제다.
String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);
이 경우에 false 불리언값으로 'falseExp' 문자열값을 리턴한다. 다음은 좀 더 실제적인 예제다.
parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString =
parser.parseExpression(expression).getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
3항 연산자의 더 간단한 문법은 다은 섹션의 엘비스 연산자를 봐라.
7.5.14 엘비스(Elvis) 연산자
엘비스 연산자는 3항 연산자 문법의 단축형으로 Groovy 언어에서 사용된다. 보통 3항 연산자에서는 변수를 두번 반복해야 한다. 예를 들면 다음과 같다.
String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";
대신에 엘비스의 헤어스타일과 유사한 엘비스 연산자를 사용할 수 있다.
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("null?:'Unknown'").getValue(String.class);
System.out.println(name); // 'Unknown'
좀 더 복잡한 예제를 보자.
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
System.out.println(name); // Mike Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
System.out.println(name); // Elvis Presley
7.5.15 안전한 탐색(Navigation) 연산자
안 전한 탐색 연산자는 NullPointerException를 피하기 위해 사용하고 Groovy 언어에서 가져왔다. 보통 객체를 참조할 때 메서드에 접근하거나 객체의 프로퍼티에 접근할 때 null이 아닌지 확인해야 한다. 이 작업을 하지 않기 위해 안전한 탐색 연산자는 예외를 던지는 대신에 null을 반환한다.
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // null - NullPointerException을 던지지 않는다!!!
Note
엘비스 연산자를 표현식의 기본값을 설정하는데 사용할 수 있다. 예를 들어 @Value 표현식에서 다음과 같이 사용한다.
이는 시스템 프로퍼티 pop3.port가 정의되어 있다면 pop3.port를 주입하고 정의되어 있지 않다면 25를 주입한다.
엘비스 연산자를 표현식의 기본값을 설정하는데 사용할 수 있다. 예를 들어 @Value 표현식에서 다음과 같이 사용한다.
@Value("#{systemProperties['pop3.port'] ?: 25}")
이는 시스템 프로퍼티 pop3.port가 정의되어 있다면 pop3.port를 주입하고 정의되어 있지 않다면 25를 주입한다.
7.5.16 컬렉션 선택기능(Selection)
선택기능(Selection)은 소스 컨텍션에서 언트리를 선택해서 다른 컬렉션으로 변환하는 강력한 표현식 언어의 기능이다.
선 택기능은 ?[selectionExpression]의 문법을 사용한다. 선택기능은 컬렉션을 필터링해서 원래 요소의 서브셋을 가진 새로운 컬렉션을 반환한다. 예를 들어 선택기능으로 세르비아(Serbian) 발명가의 리스트를 쉽게 얻을 수 있다.
List<Inventor> list = (List<Inventor>)
parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);
선 택기능은 리스트와 맵에서 도무 사용할 수 있다. 리스트의 경우 선택 크리테리아는 개별 리스트요소에 대해 평가되고 맵에서 선택 크리테리아는 맵의 각 엔트리에 대해 평가된다. (Map.Entry 자바 타입의 객체들) 맵 엔트리는 선택기능에서 사용하기 위해 프로퍼티로 접근할 수 있는 키와 값을 가진다.
다음 표현식은 원래의 맵에서 27보다 작은 값을 가진 엔트리로 이루어진 새로운 맵을 반환한다.
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
게다가 선택된 모든 요소에서 첫번째나 마지막 값을 획득하는 것도 가능한다. 선택기능의 ^[...] 문법을 사용해서 매칭된 첫 엔트리를 획득하고 $[...]로 마지막 엔티르를 획득한다.
7.5.17 컬렉션 투영(Projection)
투 영기능은 컬렉션에 하위 표현식을 평가해서 새로운 컬렉션을 반환한다. 투영 문법은 ![projectionExpression]이다. 예제로 이해하는 것이 가장 쉬운데 발명가들의 리스트를 가지고 있지만 발명가들이 테어난 도시의 리스트를 원한다고 가정해보자. 발명가 리스트에서 모든 인트리에 대해 'placeOfBirth.city'를 효율적으로 평가하기를 원한다. 다음과 같이 투영을 사용한다.
// [ 'Smiljan', 'Idvor' ]를 반환한다.
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
맵에서도 투영기능을 사용할 수 있고 맵의 경우 투영 표현식은 맵의 각 엔트리마다(자바 Map.Entry로 표현되는) 평가된다. 맵의 투영결과는 맵의 각 엔트리에 대한 투영 표현식의 평가결과로 이루어진 리스트이다.
7.5.18 표현식 템플릿
표현식 템플릿으로 하나 이상의 평가 블럭을 가진 리터럴 문자를 섞을 수 있다. 각 평가 블럭은 사용자가 정의할 수 있는 접두사와 접미사로 구분되고 일반적으로는 구분자로 #{ }를 사용한다. 다음 예제를 보자.
String randomPhrase =
parser.parseExpression("random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// "random number is 0.7038186818312008"로 평가된다
#{ } 구분자내의 표현식을 평가한 결과(이 예제에서는 random() 메서드를 호출한 결과이다.)와 'random number is ' 리터럴 문자를 연결해서 문자열을 평가한다. parseExpression() 메서드의 두번째 아규먼트는 ParserContext의 타입이다. ParserContext 인터페이스는 표현식 템플릿 기능을 지원하려고 표현식을 어떻게 파싱하는 지에 영향을 주려고 사용한다. TemplateParserContext의 정의는 다음에 나와있다.
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
7.6 예제에 사용한 클래스들
Inventor.java
package org.spring.samples.spel.inventor;
import java.util.Date;
import java.util.GregorianCalendar;
public class Inventor {
private String name;
private String nationality;
private String[] inventions;
private Date birthdate;
private PlaceOfBirth placeOfBirth;
public Inventor(String name, String nationality)
{
GregorianCalendar c= new GregorianCalendar();
this.name = name;
this.nationality = nationality;
this.birthdate = c.getTime();
}
public Inventor(String name, Date birthdate, String nationality) {
this.name = name;
this.nationality = nationality;
this.birthdate = birthdate;
}
public Inventor() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNationality() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality = nationality;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public PlaceOfBirth getPlaceOfBirth() {
return placeOfBirth;
}
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
this.placeOfBirth = placeOfBirth;
}
public void setInventions(String[] inventions) {
this.inventions = inventions;
}
public String[] getInventions() {
return inventions;
}
}
PlaceOfBirth.java
package org.spring.samples.spel.inventor;
public class PlaceOfBirth {
private String city;
private String country;
public PlaceOfBirth(String city) {
this.city=city;
}
public PlaceOfBirth(String city, String country)
{
this(city);
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String s) {
this.city = s;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
Society.java
package org.spring.samples.spel.inventor;
import java.util.*;
public class Society {
private String name;
public static String Advisors = "advisors";
public static String President = "president";
private List<Inventor> members = new ArrayList<Inventor>();
private Map officers = new HashMap();
public List getMembers() {
return members;
}
public Map getOfficers() {
return officers;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMember(String name)
{
boolean found = false;
for (Inventor inventor : members) {
if (inventor.getName().equals(name))
{
found = true;
break;
}
}
return found;
}
}
Comments