
해당 포스트는 Spring.io의 공식 문서를 포함한 레퍼런스와 코드를 통해 Spring Framework의 구조 / 기술에 대해 확인해보고자 하는 포스트입니다.
SpEL (Spring Expression Language)는 스프링 프레임워크에서 사용되는 표현 언어로, 다양한 스프링 구성 요소에서 값을 조회하고 조작하는데에 사용되며, 런타임에 객체 그래프를 쿼리하고 조작하는 것을 지원합니다.
SpEL 은 XML 및 어노테이션 기반 구성에서 사용할 수 있으며 뿐만 아니라 스프링 프레임워크에 직접 연결되지 않고 독립적으로 사용될 수도 있습니다.
위에서 설명하였듯, SpEL은 다양한 구성 요소에 있는 값을 런타임 시점에서 표현식을 해석하고 처리할 수 있습니다. 뿐만 아니라 메서드 호출과 기본 문자열 템플릿 기능 역시 제공합니다.
이러한 특성을 살려 SpEL은 주로 Spring 구성을 정의하고 관리하는데 사용되며 그 밖에도 다양한 분야에 활용되는데 주요 예시는 다음과 같습니다.
프로퍼티 및 빈 접근
스프링 빈의 프로퍼티 값을 읽고 설정할 수 있음 (@Value)
빈 참조 / 빈 설정
스프링 빈을 참조하거나 빈의 메서드를 호출할 수 있으며 의존성 관리 시 SpEL을 사용할 수 있음
조건식 및 논리 연산
조건식과 논리 연산을 지원하여 표현식을 조건부로 실행하거나 다양한 논리 연산을 수행할 수 있음
리스트 및 맵 조작
리스트와 맵을 조작하고 필요한 데이터를 추출할 수 있음
배열 및 컬렉션 조작
배열 및 컬렉션을 조작하여 원소를 필터링하거나 변환할 수 있음
메서드 호출
메서드를 호출하고 그 결과를 반환할 수 있음
데이터 액세스
데이터베이스 쿼리나 데이터 조작 로직에서 사용하여 동적으로 데이터를 추출하거나 조작할 수 있음
시큐리티 설정
스프링 시큐리티와 함께 사용하여 보안 규칙을 정의하고 인증 및 권한 부여를 위한 조건을 지정할 수 있음
템플릿 엔진
템플릿 엔진(Thymeleaf 등)에서 동적 컨텐츠를 생성하거나 템플릿을 조작할 수 있음
로깅
로깅 설정에서 로깅 레벨을 동적으로 설정하거나 특정 이벤트에 대한 조건을 정의할 때 활용
SpEL은 스프링 프레임워크에서 다양한 상황에서 코드를 더욱 유지보수하기 쉽게 하고 가독성 있게 만드는데 도움을 줍니다.
공식 문서에 소개 된 주요 표현식 레퍼런스는 다음과 같습니다.
리터럴 표현식
ExpressionParser parser = new SpelExpressionParser();
// evaluates to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
// evaluates to "Tony's Pizza"
String pizzaParlor = (String) parser.parseExpression("'Tony''s Pizza'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evaluates to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
설정, 배열, 리스트, 맵, 인덱서
// evaluates to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Inventions Array
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
// Members List
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
context, ieee, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
context, ieee, String.class);
// Officer's Dictionary
Inventor pupin = parser.parseExpression("officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
societyContext, "Croatia");
인라인 리스트 / 맵
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
배열 생성
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array -> 현재는 다차원 배열 생성 시 이니셜라이저를 사용할 수 없음
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
메서드
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
연산자
관계 연산자 (==, >, <), 정규식, 기호 연산자(lt, gt, le, ge, eq, ne, div, mod, not) 논리 연산자 (&&, ||, !), 수학 연산자, 할당 연산자 (=) 지원
// 관계 연산자
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
// uses CustomValue:::compareTo
boolean trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean.class);
// 정규식 기반
// evaluates to false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// 논리 연산자
// -- AND --
// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// evaluates to 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);
// 수학 연산자
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
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
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
// 할당 연산자
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");
// alternatively
String aleks = parser.parseExpression(
"name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
타입
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);
생성자
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
변수
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
함수
EvaluationContext 를 통해 사용자 정의 메서드를 등록하여 사용 가능
// 사용자 정의 메서드 등록
Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
// 유틸리티 메서드 예시
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
// 사용
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
빈 참조
@ 를 사용하여 빈 조회 가능 (팩토리 빈 자체 접근 시에는 &)
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
삼항 연산자 (If-Then-Else)
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"
엘비스 연산자
엘비스 연산자는 삼항 연산자 구문을 단축한 것으로 Groovy 언어에서 사용
// 엘비스 연산자 미사용 시
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
// 엘비스 연산자 사용 시
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'
엘비스 연산자 사용 시 표현식에 기본값을 적용할 수 있음
@Value("#{systemProperties['pop3.port'] ?: 25}")
안전 탐색 연산자(Safe Navigation Operator)
NPE를 피하기 위해 사용되며 객체 참조 시 해당 객체의 속성에 액세스하기 전에 null 여부를 체크, null인 경우 예외 대신 null을 반환
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
컬렉션 셀렉션 / 프로젝션
// 컬렉션 셀렉션
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext);
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
// 컬렉션 프로젝션
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
표현식 템플릿
표현식 템플릿 사용 시 리터럴 텍스트를 하나 이상의 평가 블록과 혼합 가능
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
SpEL은 스프링 프레임워크에서 사용되는 표현 언어로 런타임에 객체 그래프를 쿼리하고 조회하는 기능을 지원합니다.
주로 스프링 구성 요소를 정의하고 관리, 구성 요소에서 값을 조회하거나 조작할 때 사용하며 다양한 상황에서 코드를 가독성 있게 하고 유지보수하기 쉽게 만들어줍니다.
https://docs.spring.io/spring-framework/reference/core/expressions.html
ChatGPT 3.5