본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] SpEL

by 김코더 김주역 2022. 6. 21.
반응형

1. SpEL이란?

- SpEL은 Spring Expression Language의 약자로, EL보다 유연하고 일반 프로그래밍 언어 수준에 가까운 강력한 표현식을 이용하는 표현 언어다.

- SpEL은 Spring 3.0에서 처음 소개되었다.

 

 

 

2. 기본 사용법

- JSP 뷰에서 SpEL을 사용하려면 JSP에 다음과 같이 태그 라이브러리를 추가해야 한다.

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

 

- <spring:eval>를 통해 모델 이름이 포함된 표현식을 작성할 수 있다. 예를 들어, user 오브젝트의 name 필드 값을 출력하려면 다음과 같이 작성하면 된다.

<spring:eval expression="user.name" />

- 다음과 같이 오브젝트 메소드의 호출도 가능하다.

<spring:eval expression="user.toString()" />

※ 스태틱 메소드도 호출이 가능하다. 이 때는 스태틱 메소드 앞에 new 키워드를 붙여야 한다.

 

- <spring:eval>은 다양한 변환 기능과 포맷이 적용된 모델 정보를 화면에 출력할 수 있다.

@NumberFormat(pattern="###,##0")
Integer price;

 

<spring:eval expression="item.price" />

 

 

 

3. 지역화 메시지 출력

- <spring:message> 태그를 통해 지역화 메시지를 출력할 수 있다.

- <spring:message>를 사용하려면 id가 messageSource인 MessageSource bean이 등록되어 있어야 한다. 아래 예시에서는 MessageSource의 구현체인 ResourceBundleMessageSource를 사용했다. 메시지를 담은 기본 프로퍼티 파일의 이름은 basename 속성으로 지정하면 된다.

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="messages" />
</bean>

※ ResourceBundleMessageSource : messages.properties 리소스 번들 파일을 사용하여 메시지를 등록

- <spring:message> 태그를 통해 LocaleResolver가 결정한 지역정보에 따른 메시지를 출력할 수 있다. code 속성은 메시지의 키 값이다.

<spring:message code="greeting" />

※ LocaleResolver의 동작 : 특정 지역용 메시지 프로퍼티 파일을 찾는다. 예를 들어, Locale.KOREAN 지역에 대해서는 messages_ko.properties 파일을 찾고, Locale.ENGLISH 지역에 대해서는 messages_en.properties 파일을 찾는다. 만약 찾지 못했다면 messages.properties에서 메시지를 찾는다.

 

- 메시지 프로퍼티 파일에 다음과 같이 순서 기반 파라미터 치환자를 사용하는 경우가 있다.

greeting=Hello, {0}!

이 때는 파라미터에 들어갈 내용을 arguments 속성을 통해 지정해줄 수 있다. 만약 한 개 이상의 파라미터가 있다면 ,(콤마)로 분리하면 된다. 디폴트 메시지는 text 속성을 통해 지정하면 된다.

<spring:message code="greeting" arguments="${user.name}" text="user" />

 

 

 

4. 폼 관련 태그들

이 부분은 Validator, BindingResult, Errors에 대한 이해가 필요하다. 익숙하지 않다면 아래 포스팅을 먼저 읽고 오는 것을 권장한다.

https://kimcoder.tistory.com/239

 

[Spring] Validator로 form 데이터 검증하기

폼 화면에서 사용자가 실수로 textfield에 아무것도 입력하지 않았거나 제한 글자수를 초과했을 경우에는 사용자에게 다시 입력하게 할 수 있다. 이 과정에서 사용자가 적절한 요청 필드값을 전달

kimcoder.tistory.com

오브젝트의 필드에 @NotNull, @Size와 같은 제약조건 어노테이션을 추가하는 것만으로도 간편하게 검증을 수행해주는 JSR-303 bean 검증 방식도 읽고 오면 더 좋을 것 같다.

https://kimcoder.tistory.com/527

 

[Spring] JSR-303 Bean 검증 방식

LocalValidatorFactoryBean - JSR-303의 검증 기능을 사용할 수 있도록 하는 일종의 어댑터다. - JSR-303의 bean 검증 방식을 통해 오브젝트의 필드에 달린 제약조건 어노테이션을 이용해 검증을 진행할 수 있

kimcoder.tistory.com

- Spring은 등록 폼과 수정 폼을 구분하지 않고 모두 처음에 폼을 띄울 때부터 모델을 폼에 출력하는 방식을 사용한다. 지금부터 소개할 스프링 태그들을 보면서 어떻게 단일 폼 방식을 사용할 수 있는지 이해할 수 있을 것이다.

 

1) <spring:bind>

- 컨트롤러에서 바인딩 오류가 발생했을 때 폼에 에러 메시지를 표시하거나, 잘못 입력한 값을 다시 출력해줄 수 있도록 해주는 태그다. 모델과 바인딩 결과 정보를 최대한 활용해서 적절한 처리가 가능하다.

- <spring:bind> 태그는 내부적으로 BindStatus 타입의 오브젝트를 status라는 이름의 변수로 등록해준다. 이 BindStatus는 path 속성으로 지정한 오브젝트 필드에 관련된 정보를 제공해준다.

<spring:bind path="user.name">
    <label for="name" <c:if test="${status.errorMessage!=''}">class="errorMessage"</c:if>>Name</label>&nbsp;
    <input type="text" id="${status.expression}" name="${status.expression}" size="50" value="${status.value}" />
    <span class="errorMessage">
        <c:forEach var="errorMessage" items=${status.errorMessages}">
            ${errorMessage}
        </c:forEach>
    </span>
</spring:bind>
  • errorMessage 프로퍼티 : 에러가 있다면 첫 번째 에러 메시지를, 에러가 하나도 없다면 빈 문자열을 갖고 있음
  • expression 프로퍼티 : <input> 태그의 name을 갖고 있음
  • value 프로퍼티 : 바인딩 오류가 없다면 바인딩된 값을 갖고 있고, 바인딩 오류가 있다면 이전에 입력했던 값을 갖고 있음
  • errorMessages 프로퍼티 : 해당 필드에 대한 모든 에러 메시지를 갖고 있음
  • errors 프로퍼티 : Errors 타입 오브젝트를 갖고 있음

※ BindingStauts 문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/support/BindStatus.html

 

BindStatus (Spring Framework 5.3.23 API)

Simple adapter to expose the bind status of a field or object. Set as a variable both by the JSP bind tag and FreeMarker macros. Obviously, object status representations (i.e. errors at the object level rather than the field level) do not have an expressio

docs.spring.io

 

 

2) form 전용 태그

- Spring의 form 전용 태그를 사용하려면 JSP에 다음과 같이 태그 라이브러리를 추가해야 한다.

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

- form 태그 라이브러리를 이용하면 1)에서 살펴봤던 코드를 아래와 같이 3줄로 줄일 수 있다.

<form:label path="name" cssErrorClass="errorMessage">Name</form:label>&nbsp;
<form:input path="name" size="50"/>
<form:errors path="name" cssClass="errorMessage"/>

이제 form 태그의 종류들을 살펴 보면서 어떻게 위와 같이 코드를 줄일 수 있었는지 이해해보도록 하자. form 태그를 공식 문서로 제대로 공부하고 싶다면 https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/spring-form-tld.html로 들어가면 된다.

 

(1) <form:form>

- Html의 <form> 태그를 만들어주는 태그다.

- commandName, modelAttribute 속성으로 모델의 키를 지정하면 이 태그 안에 있는 폼 태그의 path 속성에는 해당 모델 오브젝트의 필드만 지정하면 된다. 기본값은 command다. 그리고 <form> 태그의 id 값으로도 사용된다.

- method 속성으로 HTTP 메소드를 지정할 수 있다. 기본값은 post다. 만약 put이나 delete로 지정한다면 다음처럼 히든 필드가 자동으로 추가되는데, Html의 폼 태그는 get과 post만 지원하기 때문이다.

<input type="hidden" name="_method" value="put"/>

이 경우에는 다음과 같이 HiddenHttpMethodFilter bean을 등록하고 컨트롤러에서 put 또는 delete 요청을 받을 수 있도록 설정하면 된다.

@Configuration
public class AppConfig {
	...
    
    @Bean
    public HiddenHttpMethodFilter httpMethodFilter() {
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return hiddenHttpMethodFilter;
    }
}

- action 속성으로 URL을 지정할 수 있다. 폼을 띄우는 URL과 폼을 제출하는 URL이 동일하다면(GET과 POST로만 구분한다면) 생략해도 되는 속성이다.

 

(2) <form:input>

- Html의 <input type="text"> 태그를 만들어주는 태그다. path 속성은 필수다.

- size, maxlength, readonly 같은 표준 HTML 속성과 onclick과 onkeydown과 같은 이벤트 속성을 지원한다.

- 기본적으로 path 속성은 <input> 태그의 id, name에 지정된다.

- cssClass, cssErrorClass은 각각 바인딩 오류가 없을 때와 있을 때 지정할 class 속성값이다.

- value 속성값은 path 속성에 지정한 오브젝트 필드의 값으로 자동 세팅된다. 단, 프로퍼티 에디터나 컨버전 서비스가 등록되어 있다면 이를 통해 문자열로 변환된 값이 세팅된다.

PropertyEditor 참고 / ConversionService 참고

 

(3) <form:label>

- Html의 <label> 태그를 만들어주는 태그다. path 속성은 필수다.

※ <label> 태그는 for 속성을 통해 다른 요소와 결합할 수 있다.

- cssClass, cssErrorClass 속성을 지원한다.

 

(4) <form:errors>

- 바인딩 에러 메시지를 출력할 때 사용되는 태그로, 기본적으로 <span> 태그로 에러 메시지를 감싼다. 필수 속성은 없다.

- path 설정에 따라서 에러 메시지의 종류를 선택할 수 있다.

  • path="[필드]" : Errors.rejectValue() 메소드로 등록된 필드 레벨의 에러 메시지를 출력한다.
  • path 속성 생략 : Errors.reject() 메소드로 등록된 오브젝트 레벨의 에러 메시지를 출력한다.
  • path="*" : 오브젝트 레벨의 에러 메시지와 필드 레벨의 에러 메시지를 함께 출력한다.

- delimiter 속성을 통해 각 에러 메시지를 구분해주는 구분자를 지정할 수 있다. 기본값은 줄바꿈 태그인 <br/>이다.

- cssClass 속성을 통해 바인딩 오류가 있을 때 적용할 css 클래스를 지정할 수 있다. <form:errors> 태그는 어차피 에러 메시지를 출력하는 태그이기 때문에 cssErrorClass 속성을 따로 두지 않는다는 점에 유의하자.

 

(5) <form:hidden>

- Html의 <input type="hidden"> 태그를 만들어주는 태그다.

- 기본적으로 path 속성은 <input> 태그의 id, name에 지정된다.

- value 속성값은 path 속성에 지정한 오브젝트 필드의 값으로 자동 세팅된다.

 

(6) <form:password>, <form:textarea>

- 각각 html의 <input type="password">와 <textarea> 태그를 생성해주는 태그다. path 속성은 필수다.

- 사용 방법은 <form:input>과 동일하며, 각 태그에 대응되는 표준 HTML 속성을 이용할 수 있다.

 

(7) <form:checkbox>, <form:checkboxes>

- Html의 <input type="checkbox"> 태그를 만들어주는 태그로, 자동으로 필드마커가 붙은 히든 필드를 등록해준다. 필드마커의 개념과 사용 이유를 모른다면, 아래 포스팅의 [3. fieldMarkerPrefix]를 참고하자.

https://kimcoder.tistory.com/525

 

[Spring] 자주 사용되는 WebDataBinder 설정 항목

이전 포스팅에서는 WebDataBinder에 커스텀 프로퍼티 에디터와 컨버전 서비스를 등록하는 방법을 소개했다. https://kimcoder.tistory.com/521 [Spring] 프로퍼티 바인딩 방식(1) - PropertyEditor 프로퍼티 바인..

kimcoder.tistory.com

- path 속성은 필수다.

- 프로퍼티 타입이 boolean이 아니라면 value에 다른 값을 지정해주는 것이 좋다.

- <form:checkboxes>는 한 번에 여러 개의 체크박스를 만들 때 사용할 수 있다. 체크박스 목록을 맵이나 컬렉션으로 만들어서 모델에 제공해주고 items 속성에서 해당 오브젝트를 받도록 하면 된다.

items가 맵인 경우에는 맵의 키는 체크박스의 value로 지정되고 맵의 값은 체크박스의 레이블에 출력된다.

<form:checkboxes path="fields" items="${fields}" />

items가 오브젝트 리스트인 경우에는 itemLabel과 itemValue 속성을 추가해서 레이블과 value로 사용할 오브젝트 필드를 각각 지정해주면 된다. 

여러 개를 체크한다면 value 값들이 콤마로 연결되어 전달된다.

 

(8) <form:radiobutton>, <form:radiobuttons>

- Html의 <input type="radio"> 태그를 생성해주는 태그다. 사용 방법은 <form:checkbox>, <form:checkboxes>와 비슷하지만, 단일 선택 요소라는 차이점이 있다.

 

(9) <form:select>, <form:option>, <form:options>

- Html의 <select>와 <option> 태그를 생성해주는 태그다. 사용 방법은 <form:radiobutton>, <form:radiobuttons>와 비슷하다.

- path 속성은 <form:select>에 지정한다.

- 다음과 같이 <form:option>과 <form:options>를 같이 사용할 수도 있다.

<form:select path="year">
    <form:option value=" " label="--연도를 선택해주세요--" />
    <form:options items="${years}" />
</form:select>

 

 

 

5. Bean 참조

- 아래 예시에서는 참조할 bean의 id와 property를 표현식에 작성한다.

<bean id="beanA" ...>
    <property name="str" value="Spring" />
</bean>

<bean id="beanB" ...>
    <property name="beanAstr" value="#{beanA.str}" />
</bean>

- 다른 bean의 property 외에도 메소드 호출도 가능하고 생성자를 호출해서 오브젝트를 만들 수도 있다.

 

 

 

6. properties 파일 참조

- properties 파일의 내용을 읽어오는 Properties 타입의 bean을 등록하면 된다.

<util:properties id="dbprops" location="classpath:db.properties" />

util 네임스페이스를 사용하기 위해서는 <beans>에 아래와 같이 스키마를 추가해야 한다.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.0.xsd">

- Properties은 Map 인터페이스를 구현한 클래스로, SpEL에서 Map의 get 메소드를 호출해주는 표현식을 만들 수 있다. 

<bean id="dataSource" class="..." >
    <property name="driverClass" value="#{dbprops['db.driverclass']}" />
    <property name="url" value="#{dbprops['db.url']}" />
    <property name="username" value="#{dbprops['db.username']}" />
    <property name="password" value="#{dbprops['db.password']}" />
</bean>

- @Value 어노테이션에도 SpEL 표현식을 사용할 수 있다.

@Value("#{beanA.str}")
private string beanAstr;

 

 

 

7. ExpressionParser과 Expression

1) ExpressionParser

- 표현식을 파싱하는 기능이 정의되어 있는 인터페이스다.

- 구현체로는 주로 SpelExpressionParser을 사용하며, 이 클래스는 싱글톤 bean으로 등록해두고 사용해도 안전하다.

 

 

2) Expression

- 파서에 의해 해석된 표현식에 대한 정보를 가지는 인터페이스다.

- ExpressionParser.parseExpression() 메소드에 의해 Expression 타입의 오브젝트가 반환된다. 

- Expression.getValue()에 오브젝트를 파라미터로 전달하면 해당 오브젝트에 표현식을 적용한다.

※ 오브젝트를 바로 전달하는 대신에 EvaluationContext에 담아 전달하는 방법도 있다. 이 방법을 사용하면 변수, 메소드, 컨버터 등을 추가로 제공할 수 있다.

 

예시 - 모든 User 오브젝트의 name 프로퍼티 값만 추출해서 저장

ExpressionParser parser = new SpelExpressionParser();
Expression nameExp = parser.parseExpression("name");

List<String> nameList = new ArrayList<>();
for(User user : userList) nameList.add(nameExp.getValue(user);

 

※ ExpressionParser 문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/expression/ExpressionParser.html

※ Expression 문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/expression/Expression.html

 

 

● 참고 자료 : 토비의 스프링 3.1 Vol.2

반응형

댓글