devoong2
devoong2
devoong2
전체 방문자
오늘
어제

인기 글

최근 글

  • Category (35)
    • Java (4)
    • Spring (13)
    • JPA (4)
    • DesignPattern (1)
    • 동시성 (Concurrency) (4)
    • 회고 (1)
    • Redis (1)
    • Network (3)
    • Kafka (2)
    • Spring Batch (2)

최근 댓글

반응형
hELLO · Designed By 정상우.
devoong2

devoong2

SpEL(Spring Expression Langauge) 사용법 + 어노테이션에 SpEL로 값 전달하기
Spring

SpEL(Spring Expression Langauge) 사용법 + 어노테이션에 SpEL로 값 전달하기

2023. 5. 6. 23:22
반응형
예제 및 테스트 코드는 github 에서 확인 가능합니다.

Spring Expresion Language(SpEL) 이란?

스프링 공식 문서에서는 SpEL을 다음과 같이 설명합니다.

SpEL은 런타임 시 객체 그래프 쿼리 및 조작을 지원하는 강력한 표현언어이다.

출처: https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/expressions.html
Type Operators
Arithmetic +, -, *, /, %, ^, div, mod
Relational <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
Logical and, or, not, &&,
Conditional and, or, not, &&,
Regex matches

SpEL 사용법

SpEL 표현식은 다음과 같은 규칙이 있습니다.

  • # 기호로 시작하고 중괄호로 묶는다. ex) #{expression}
  • 속성은 $ 기호로 시작하고 중괄호로 감싼다. ex) ${property.name}

스프링에서 SpEL이 자주 사용되는 어노테이션은 대략 다음과 같은 어노테이션들이 있습니다.

  • @Value
  • @PreAuthorize
  • @PostAuthorize
  • @PreFilter
  • @PostFilter

표현식은 다음과 같이 사용할 수 있습니다.

@Value("#{19 + 1}") // 20
private double add; 

@Value("#{'String1 ' + 'string2'}") // "String1 string2"
private String addString; 

@Value("#{1 == 1}") // true
private boolean equal;

@Value("#{250 > 200 && 200 < 4000}") // true
private boolean and; 

@Value("#{2 > 1 ? 'a' : 'b'}") // "a"
private String ternary;

@Value("#{'100' matches '\\d+' }") // true
private boolean validNumericStringResult;
...

SpEL 파싱

ExpressionParser.class

ExpressionParser 는 문자열 표현을 파싱하는 책임을 가지고 Expression 는 문자열 표현의 평가를 책임집니다.

ExpressionParser, Expression 클래스를 이용하면 다음과 같이 SpEL을 파싱할 수 있습니다.

ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue(); // "Any string"

Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue(); // 10

EvaluationContext.class

EvaluationContext 는 프로퍼티, 메서드, 필드를 확인하고 타입 변환을 수행하는 표현식을 평가할 때 사용됩니다.

EvaluationContext 는 다음 두가지 구현체를 제공합니다.

  • SimpleEvaluationContext
    • SpEL의 전체 범위가 필요하지 않으며 의미 있게 제한되어야 할 때 사용
    • 자바 타입 참조, 생성자 및 bean 참조를 지원하지 않음
  • StandardEvaluationContext
    • 리플렉션을 기반으로 SpEL의 모든 기능 제공
Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");

ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("model");

EvaluationContext context = new StandardEvaluationContext(car);
String result = (String) expression.getValue(context);  // "Model 3"

어노테이션에서 SpEL 활용하기

이번엔 SpEL을 이용해 커스텀 어노테이션에 변수를 넘겨서 사용하는 방법에 대해 알아보겠습니다.

코드를 작성하다보면 공통 처리를 위해 annotation + aop 조합을 사용하는 경우가 있습니다. 이런 경우에 활용할 수 있습니다.

CustomAnnotation.java

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
    String value() default "";
}

SpELApplication.java

@Component
@Slf4j
public class SpELApplication {

    @CustomAnnotation(value = "#value")
    public void call(String value) {
    }
}

call 메소드를 호출하면 value변수를 어노테이션에 전달하고 AOP에서는
해당 value변수를 실제 참조를 통해 읽어와야 합니다.

CustomAnnotationAop.java

@Aspect
@Component
@Slf4j
public class CustomAnnotationAop {

    @Around("@annotation(com.example.spelexample.aop.CustomAnnotation)")
    public Object aopCall(final ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        CustomAnnotation customAnnotation = method.getAnnotation(CustomAnnotation.class);

        String value = (String) CustomSpELParser.getDynamicValue(signature.getParameterNames(), joinPoint.getArgs(), customAnnotation.value());
        log.info("aop value ##### {}", value);

        return joinPoint.proceed();
    }
}
  • AOP의 MethodSignature 를 이용해 메소드에 전달받은 파라미터 네임과 arguments를 CustomSpELParser로 전달합니다.
    그리고 CustomAnnotation 의 value도 같이 전달합니다.

CustomSpELParser.java

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class CustomSpELParser {
    public static Object getDynamicValue(String[] parameterNames, Object[] args, String name) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new StandardEvaluationContext();

        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }

        return parser.parseExpression(name).getValue(context);
    }
}

EvaluationContext에 전달받은 파라미터 네임과 arguments들을 모두 매핑합니다. 이후 가져올 값을 표현식으로 꺼내 리턴합니다.


spel-image-1


name에 #value 라는 값이 들어있는 것을 확인할 수 있습니다.
CustomSpELParser 클래스에서는 context 세팅을 생성자나 setRootObject가 아닌 setVariable를 이용하여 context에 값을 세팅했습니다.


spel-image-2

그리고 name을 #value 라는 값으로 전달하여 value로 파싱해 context에서 value에 해당하는 값을 꺼내옵니다.

InternalSpelExpressionParser.class 내부를 살펴보면 #value의 맨 앞인 #(hash) 를 자르고
#value 문자열에서 value 만 가져올수있게 position을 1, 6으로 설정하는 것을 확인 할 수 있습니다.


InternalSpelExpressionParser.class

spel-image-3


SpELTest.java

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpELTest {
    @Autowired
    private SpELApplication spELApplication;

    @Test
    void spELCallTest() {
        spELApplication.spELCall("test annotation");
    }
}

그리고 해당 메소드를 호출하면 전달한 파라미터 값인 test annotation 가 로그에 찍힌것을 확인할 수 있습니다.


spel-image-4


reference

https://www.baeldung.com/spring-expression-language
https://blog.outsider.ne.kr/835
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/expression

반응형

'Spring' 카테고리의 다른 글

[Spring] Transactional REQUIRES_NEW 옵션에서 예외 및 Rollback  (2) 2024.01.10
[Spring] 서로 다른 테스트 클래스에서 테스트 데이터를 공유하는 방법  (0) 2023.11.05
스프링 Redis 테스트 환경 구축하기 (Embedded Redis, TestContainer)  (0) 2022.09.17
Spring @Valid Annotation을 이용한 유효성 검증과 예외처리  (1) 2022.08.17
MapStruct를 이용해 객체를 변환하는 방법  (0) 2022.08.17
    'Spring' 카테고리의 다른 글
    • [Spring] Transactional REQUIRES_NEW 옵션에서 예외 및 Rollback
    • [Spring] 서로 다른 테스트 클래스에서 테스트 데이터를 공유하는 방법
    • 스프링 Redis 테스트 환경 구축하기 (Embedded Redis, TestContainer)
    • Spring @Valid Annotation을 이용한 유효성 검증과 예외처리
    devoong2
    devoong2
    github 주소: https://github.com/limwoobin

    티스토리툴바