자바 백엔드 입문 22편 — SpEL 표현식 언어

2026-05-16자바 백엔드 입문

자바 백엔드 입문 22편. Spring 안에 든 작은 표현식 언어 SpEL이 뭐고 @Value·@PreAuthorize 같은 곳에서 어떻게 동작하는지 엑셀 수식 비유로 풀어쓴 Phase 3 시작 글.

📚 자바 백엔드 입문 · 22편 — SpEL 표현식 언어

이 글은 자바 백엔드 입문 시리즈 59편 중 22편이에요. Phase 2 IoC 컨테이너 7편이 끝났고, 이번 22편부터 Phase 3 SpEL·AOP로 들어갑니다. 먼저 SpEL(Spring Expression Language) — Spring 안에 박혀 있는 작은 표현식 언어를 풀어 가요. @Value("#{...}") 같은 표현이 어떻게 동작하는지 한 번에 풀려요.

SpEL이 어렵게 들리는 이유

처음 Spring 코드에서 @Value("#{systemProperties['user.timezone']}") 같은 표현을 보면 "이게 자바인가? 어떤 문법인가?" 가 안 잡혀요. SpEL은 자바 안에 박힌 또 다른 작은 언어예요.

이 글에서는 SpEL을 엑셀 수식 비유로 풀어요. 셀에 =A1+B1 박으면 엑셀이 알아서 계산하듯이, Spring 어노테이션 안에 #{...} 박으면 Spring이 알아서 평가해줘요. 끝까지 따라오시면 SpEL을 "엑셀 수식의 자바 버전" 한 줄로 정리할 수 있어요.

SpEL이란 — Spring 안의 작은 언어

SpEL(Spring Expression Language)"Spring 어노테이션·설정 파일·XML 안에서 평가할 수 있는 표현식 언어" 예요. 2007년 Spring 3.0에서 도입됐고, OGNL·MVEL 같은 기존 EL의 후속이에요.

핵심 발상은 단순. "자바 코드 안에서 동적으로 값을 계산하거나 조회하는 일을 작은 표현식으로 짧게 처리하자". 자바 코드 안에 박는 게 아니라 문자열 안에 박힌 표현식이라 컴파일 후에도 동적으로 평가 가능해요.

@Value("#{2 + 3}")
private int sum;   // Spring이 시작 시 5로 평가해 주입

@Value("#{systemProperties['user.timezone']}")
private String timezone;   // JVM 시스템 프로퍼티에서 timezone 값 가져옴

@Value("#{userService.findActiveCount()}")
private int activeUserCount;   // 다른 Bean의 메서드 호출 결과

#{...} 가 SpEL 평가 마커예요. 안에 든 내용을 Spring이 동적으로 계산해서 필드에 주입.

SpEL이 가장 자주 등장하는 곳 5가지

실무에서 SpEL을 보는 자리는 다음 5개예요.

위치 예시
@Value @Value("#{systemProperties['db.url']}")
@PreAuthorize (Spring Security) @PreAuthorize("hasRole('ADMIN') and #user.id == principal.id")
@Conditional @ConditionalOnExpression("#{environment.acceptsProfiles('prod')}")
@Cacheable @Cacheable(key = "#userId")
@EventListener 조건 @EventListener(condition = "#event.amount > 1000")

#{...} 형식 외에도 #변수 형식이 보여요. 이건 "이 어노테이션이 박힌 메서드의 매개변수 이름" 을 참조하는 SpEL 단축 표기. 어노테이션마다 조금씩 다르게 쓰여요.

SpEL 기본 문법 — 리터럴·프로퍼티·메서드

SpEL이 지원하는 기본 문법은 8가지. 입문에서 가장 자주 보는 게 5개.

1. 리터럴 (Literal)

@Value("#{42}")              // 정수
@Value("#{3.14}")            // 실수
@Value("#{'Hello'}")         // 문자열 (작은따옴표)
@Value("#{true}")            // 불리언
@Value("#{null}")            // null

2. 산술·논리 연산

@Value("#{2 + 3 * 4}")       // 14
@Value("#{10 > 5}")          // true
@Value("#{10 == 10 and 5 < 6}")   // true (and / or 키워드)

3. 프로퍼티 접근

@Value("#{userService.activeUsers.size()}")   // 다른 Bean의 메서드 체이닝
@Value("#{order.items[0].price}")             // 리스트 인덱스
@Value("#{order.metadata['tag']}")            // 맵 키

4. 메서드 호출

@Value("#{userService.findActiveCount()}")
@Value("#{'Hello'.toUpperCase()}")

5. 안전 네비게이션 (?. 연산자)

@Value("#{user?.profile?.name}")   // user가 null이면 평가 자체가 null

자바의 "Optional 체이닝" 과 비슷한 그림. NullPointerException 자동 회피.

@Value 두 가지 표기 — #{...} vs ${...}

여기서 입문자가 자주 헷갈리는 부분. @Value 안에 두 가지 표기가 보여요.

@Value("#{expression}")       // SpEL — 동적 평가
@Value("${property.key}")     // 프로퍼티 플레이스홀더 — application.properties 값

#{...} 는 SpEL. 동적으로 계산하거나 다른 Bean 참조. ${...}"application.properties 의 키 값을 가져와 그대로 박는다" 의미.

# application.properties 에 박힌 설정
server.port=8080
app.welcome.message=Hello Spring

# 자바 코드
@Value("${server.port}")              // 8080
@Value("${app.welcome.message}")      // "Hello Spring"
@Value("${app.welcome.message:기본값}")  // 키 없으면 기본값 사용

두 표기를 섞을 수도 있어요.

@Value("#{'${app.greeting}'.toUpperCase()}")
// application.properties의 app.greeting 값을 가져와 SpEL에서 대문자로 변환

SpEL을 직접 평가하기 — ExpressionParser

Spring 어노테이션 외에 SpEL을 자바 코드 안에서 직접 쓸 수도 있어요. 가끔 동적 규칙 처리에 활용해요.

ExpressionParser parser = new SpelExpressionParser();

Expression exp1 = parser.parseExpression("2 + 3");
int result1 = exp1.getValue(Integer.class);   // 5

Expression exp2 = parser.parseExpression("'Hello, ' + name");
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("name", "Spring");
String result2 = exp2.getValue(ctx, String.class);   // "Hello, Spring"

실무에서는 직접 쓰는 일이 드물지만, "동적 룰 엔진" 같은 시나리오에서 만나요. 예: 결제 정책을 데이터베이스에 SpEL 표현식으로 저장 후 평가.

⚠️ 보안 함정 — 사용자 입력 SpEL 평가

사용자 입력을 SpEL로 평가하지 마세요. SpEL은 메서드 호출·시스템 프로퍼티 접근까지 가능해서 — 사용자가 T(java.lang.Runtime).getRuntime().exec(...) 같은 표현식을 박으면 서버 명령 실행. 실제 CVE도 자주 발생. 사용자 입력 평가는 별도의 안전한 표현식 라이브러리(Janino·Drools)를 써요.

실무에서 SpEL을 거의 안 쓰는 이유

입문자에게 한 가지 솔직한 말 — 일반적인 백엔드 개발에서 SpEL을 직접 박을 일은 정말 드물어요. ${...} 프로퍼티 플레이스홀더는 매일 봐도, #{...} SpEL은 한 달에 한 번 볼까말까.

다만 다음 시나리오에서 만나면 "아, 이게 SpEL이구나" 가 잡혀요. - Spring Security @PreAuthorize 규칙 - Spring Cache @Cacheable 키 생성 - Spring Boot 자동 설정의 @ConditionalOnExpression - Spring Integration·Batch의 동적 라우팅

이 자리에서 SpEL 문법이 자연스럽게 등장하니까, "#{...} 가 보이면 그 안은 작은 자바 표현식" 한 줄만 머리에 박아두면 충분합니다.

한 줄 정리 — SpEL = Spring 안에 박힌 작은 표현식 언어. #{...} 마커 안에서 리터럴·프로퍼티·메서드 호출 가능. ${...} 프로퍼티 플레이스홀더와 헷갈리지 말 것.

시험 직전 한 번 더 — SpEL 입문자가 매번 헷갈리는 것

  • SpEL = Spring Expression Language. Spring 3.0(2007) 도입
  • "Spring 안에 박힌 작은 표현식 언어" — 엑셀 수식 비유
  • 마커 = #{...} (SpEL 평가)
  • ${...} 와 헷갈리지 말 것 — 이건 프로퍼티 플레이스홀더
  • #{...} = 동적 평가 (계산·메서드 호출), ${...} = application.properties
  • 둘 다 섞기 가능 — #{'${app.name}'.toUpperCase()}
  • 가장 자주 보는 자리 = @Value·@PreAuthorize·@Conditional·@Cacheable·@EventListener
  • 기본 문법 = 리터럴 / 산술·논리 / 프로퍼티 / 메서드 / 안전 네비게이션
  • and·or·not 키워드 (&&·||·! 도 가능)
  • 리스트 인덱스 = [0], 맵 키 = ['tag']
  • 안전 네비게이션 = ?. — NPE 회피
  • 다른 Bean 참조 = @beanName 또는 메서드 체이닝
  • ExpressionParser + SpelExpressionParser 로 자바 코드 안 직접 평가 가능
  • StandardEvaluationContext = 변수·함수 등록하는 평가 컨텍스트
  • 컨텍스트 변수 = #변수이름 (어노테이션 안에서 메서드 매개변수 참조)
  • SimpleEvaluationContext = 메서드 호출·타입 참조 차단된 안전 컨텍스트
  • 사용자 입력 SpEL 평가 = 보안 위험. 절대 금지
  • T(java.lang.Runtime) 같은 타입 참조로 서버 명령 실행 가능
  • 사용자 입력 평가는 별도 안전 라이브러리(Janino·Drools) 사용
  • 실무 = ${...} 매일, #{...} 한 달에 한 번 정도

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!