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
오브젝트의 필드에 @NotNull, @Size와 같은 제약조건 어노테이션을 추가하는 것만으로도 간편하게 검증을 수행해주는 JSR-303 bean 검증 방식도 읽고 오면 더 좋을 것 같다.
https://kimcoder.tistory.com/527
- 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>
<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
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>
<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
- 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
'Spring Series > Spring Framework' 카테고리의 다른 글
[Spring] Prototype Bean (0) | 2022.06.23 |
---|---|
[Spring] RestTemplate 한글 깨짐 해결방법 (0) | 2022.06.22 |
[Spring] Collection 타입을 XML로 작성하기 (0) | 2022.06.21 |
[Spring] @Qualifier 이란? (0) | 2022.06.17 |
[Spring] @Autowired의 4가지 사용 방법 (0) | 2022.06.17 |
댓글