이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.
17. 뷰 기술
17.1 소개
스프링이 잘하는 부분 중 하나는 MVC 프레임워크의 남은 부분에서 뷰 기술을 분리하는 것이다. 예를 들어 JSP에 Velocity나 XSLT를 사용하는 것은 주로 설정의 문제이다. 이번 장에서는 스프링에서 사용가능한 주요 뷰 기술을 다루고 새로운 뷰 기술을 추가하는 방법도 살펴본다. 이번 장에서는 보통 뷰와 MVC 프레임워크가 어떻게 연동되는지에 대한 기본적인 내용을 다룬 Section 16.5, “뷰 처리”를 이미 알고 있다고 간주한다.
17.2 JSP & JSTL
스프링은 JSP와 JSTL 뷰에 대한 다수의 해결책을 제공한다. WebApplicationContext에 정의한 일반적인 뷰 리졸버로 JSP나 JSTL을 사용한다. 물론 실제 뷰를 렌터링할 JSP를 작성해야 한다.
Note
어플리케이션에서 JSTL을 사용하도록 설정하는 것이 일반적으로 오류의 원인이 되곤 하는데 주로는 다양한 서블릿 스펙을 혼동해서 발생한다. 즉, JSP와 JSTL 버전들이 무엇을 의미하고 taglib을 제대로 설정하는 방법에 대한 것이다. 웹 어플리케이션에서 JSTL을 참조하고 사용하는 방법이라는 글에 일반적인 문제점들과 이 문제점을 피하는 방법에 대한 유용한 가이드가 나와있다. 스프링 3.0부터 지원하는 서블릿 버전의 최소 버전은 혼동하는 범위를 약간 줄여주는 2.4(JSP 2.0, JSTL 1.1)이다.
어플리케이션에서 JSTL을 사용하도록 설정하는 것이 일반적으로 오류의 원인이 되곤 하는데 주로는 다양한 서블릿 스펙을 혼동해서 발생한다. 즉, JSP와 JSTL 버전들이 무엇을 의미하고 taglib을 제대로 설정하는 방법에 대한 것이다. 웹 어플리케이션에서 JSTL을 참조하고 사용하는 방법이라는 글에 일반적인 문제점들과 이 문제점을 피하는 방법에 대한 유용한 가이드가 나와있다. 스프링 3.0부터 지원하는 서블릿 버전의 최소 버전은 혼동하는 범위를 약간 줄여주는 2.4(JSP 2.0, JSTL 1.1)이다.
17.2.1 뷰 리졸버
스프링에 통합하는 다른 뷰 기술과 마찬가지로 JSP에서도 뷰를 처리할 뷰 리졸버가 필요하다. JSP로 개발할 때 가장 많이 사용하는 뷰 리졸버는 InternalResourceViewResolver와 ResourceBundleViewResolver이다. 두 리졸버 모두 WebApplicationContext에 선언되어 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Xml <!-- ResourceBundleViewResolver --> <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean> # 그리고 예제 프로퍼티파일을 사용한다 (WEB-INF/classes의 views.properties): welcome.(class)=org.springframework.web.servlet.view.JstlView welcome.url=/WEB-INF/jsp/welcome.jsp productList.(class)=org.springframework.web.servlet.view.JstlView productList.url=/WEB-INF/jsp/productlist.jsp |
여기서 보듯이 ResourceBundleViewResolver는 1) 클래스와 2) URL에 매핑된 뷰 이름을 정의하는 프로퍼티 파일이 필요하다. ResourceBundleViewResolver에서는 딱 하나의 리졸버를 사용해서 다른 종류의 뷰를 섞을 수 있다.
1 2 3 4 5 6 7 | Xml <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> |
위에서 설명했듯이 JSP를 사용하는데 InternalResourceBundleViewResolver를 설정할 수 있다. 베스트 프렉티스로 'WEB-INF' 디렉토리 하위에 있는 디렉토리에 JSP 파일을 두기를 강력히 권장한다. 이렇게 하면 크랄이언트가 직접 접근할 수 없다.
17.2.2 '평범한(Plain-old)' JSP 대 JSTL
자바 표준 태그라이브러리를 사용하는 경우 I18N 같은 기능이 동작하기 전에 JSTL에 어떤 준비과정이 필요하므로 전용 뷰 클래스인 JstlView를 사용해야 한다.
17.2.3 개발에 편리한 부가적인 태그
이전 장에서 설명했듯이 스프링은 요청 파라미터를 커맨드 객체로의 데이터바인딩을 지원한다. 이러한 데이터 바인딩 기능을 사용하는 JSP 페이지의 개발을 더 쉽게할 수 있도록 스프링이 몇가지 태그를 제공한다. 모든 스프링 태그는 문자를 이스케이프하거나 이스케이프하지 않도록 하는 HTML 이스케이핑 기능을 가진다.
태그 라이브러리 디스크립터(TLD, tag library descriptor)는 spring-webmvc.jar에 있다. 각 태그에 대한 자세한 내용은 Appendix F, spring.tld 부록에 나와있다.
17.2.4 스프링의 폼태그 라이브러리 사용
버전 2.0부터 스프링은 JSP와 스프링 웹 MVC를 사용하는 경우 폼 요소를 다룰수 있도록 데이터바인딩과 관련된 광범위한 태그 세트를 제공한다. 각 태그는 대응되는 HTML 태그의 속성들을 지원해서 태그에 익숙해지게 하고 직관적으로 사용할 수 있게 한다. 태그가 생성한 HTML은 HTML 4.01/XHTML 1.0를 따른다.
다른 폼/인풋 태그 라이브러리와는 달리 스프링의 폼 태그 라이브러리는 스프링 웹 MVC와 통합되어 있어서 태그가 커맨드객체에 접근하고 컨트롤러가 다루는 데이터를 참조할 수 있다. 다음 예제에서 볼 수 있듯이 폼 태그는 JSP를 더 쉽게 개발하고 읽고 유지할 수 있게 한다.
폼 태그의 각 태그를 어떻게 사용하는지 예제를 보자. 해당 태그가 추가적인 설명이 필요하다면 생성된 HTML 코드도 포함시켰다.
17.2.4.1 구성
폼 태그 라이브러리는 spring-webmvc.jar에 있다. 라이브러리 디스크립터는 spring-form.tld이다.
이 라이브러리의 태그를 사용하려면 다음 디렉티브를 JSP 페이지 상단에 추가해야 한다.
Java
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form" %>
form은 이 라이브러리에서 사용하려는 태그의 태그명 접두사이다.
17.2.4.2 form 태그
이 태그는 HTML 'form' 태그를 생성하고 바인딩일 위해 내부 태그에 바인딩경로를 노출한다. 이 태그가 PageContext에 커맨드객체를 연결하므로 내부태그가 커맨드 객체에 접근할 수 있다. 이 라이브러리의 다른 모든 태그는 form 태그의 중첩된 태그이다.
User라는 도메인 객체를 가지고 있다고 가정해 보자. User는 firstName과 lastName같은 프로퍼티를 가진 JavaBean이다. form.jsp를 반환하는 폼 컨트롤러의 폼에 기반한 객체(form backing object)로 이 객체를 사용할 것이다. form.jsp는 다음 예제와 같을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Html <form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> |
firstName와 lastName 값은 페이지 컨트롤러가 PageContext에 바인딩한 커맨드객체에서 가져온다. 내부 태그를 form 태그와 함께 사용하는 방법에 대한 더 복잡한 예제를 보려면 계속 읽어봐라.
생성된 HTML은 표준 폼처럼 보일 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Html <form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value="Harry"/></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value="Potter"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form> |
앞의 JSP는 폼에 기반한 객체(form backing object)의 변수 이름이 'command'라고 가정한다. 폼에 기반한 객체를 다름 이름으로 모델에 넣었다면(이는 확실하게 좋은 사용방법이다) 다음과 같이 변수명에 폼을 바인딩할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Html <form:form commandName="user"> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> |
17.2.4.3 input 태그
이 태그는 바인딩된 값과 기본적으로 type='text'를 사용해서 'input' HTML 태그를 생성한다. 이 태그의 예제는 Section 17.2.4.2, “form 태그”를 봐라. 스프링 3.1에서는 'email', 'tel', 'date' 등의 HTML5에 특화된 다른 타입을 사용할 수 있다.
17.2.4.4 checkbox 태그
이 태그는 'checkbox' 타입의 HTML 'input' 태그를 생성한다. 뉴스레터 구복과 취미 목록같은 선호도(preference)를 가진 User를 가정하자. 다음은 Preferences 클래스의 예제이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Java public class Preferences { private boolean receiveNewsletter; private String[] interests; private String favouriteWord; public boolean isReceiveNewsletter() { return receiveNewsletter; } public void setReceiveNewsletter(boolean receiveNewsletter) { this.receiveNewsletter = receiveNewsletter; } public String[] getInterests() { return interests; } public void setInterests(String[] interests) { this.interests = interests; } public String getFavouriteWord() { return favouriteWord; } public void setFavouriteWord(String favouriteWord) { this.favouriteWord = favouriteWord; } } |
form.jsp는 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | Html <form:form> <table> <tr> <td>Subscribe to newsletter?:</td> <%-- 접근방법 1: 프로퍼티가 java.lang.Boolean 타입이다 --%> <td><form:checkbox path="preferences.receiveNewsletter"/></td> </tr> <tr> <td>Interests:</td> <td> <%-- 접근방법 2: 프로퍼티가 java.util.Collection 타입이거나 배열이다 --%> Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/> Herbology: <form:checkbox path="preferences.interests" value="Herbology"/> Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/> </td> </tr> <tr> <td>Favourite Word:</td> <td> <%-- 접근방법 3: 프로퍼티가 java.lang.Object타입이다 --%> Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/> </td> </tr> </table> </form:form> |
체크박스에서 필요한 모든 것을 만족시킬 checkbox 태그의 3가지 접근방법이 있다.
- 접근방법 1 - 바인딩된 값이 java.lang.Boolean 타입인 경우 바인딩된 값이 true이면 input(checkbox)는 'checked'가 된다. value 속성은 setValue(Object) 값 프로퍼티의 처리된 값에 대응된다.
- 접근방법 2 - 바인딩된 값이 array이나 java.util.Collection인 경우 설정된 setValue(Object) 값이 바인딩된 Collection에 있으면 input(checkbox)는 'checked'가 된다.
- 접근방법 3 - 바인딩된 값이 그외 다른 타입인 경우 설정한 setValue(Object)가 바인딩된 값과 같으면 input(checkbox)는 'checked'가 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Html <tr> <td>Interests:</td> <td> Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/> <input type="hidden" value="1" name="_preferences.interests"/> Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/> <input type="hidden" value="1" name="_preferences.interests"/> Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/> <input type="hidden" value="1" name="_preferences.interests"/> </td> </tr> |
여기서 의외인 부분은 각 체크박스 뒤에 있는 부가적인 히든필드이다. HTML 페이지의 체크박스가 checked가 아닌 경우 체크박스의 값은 폼이 제출될때 HTTP 요청 파라미터의 일부로 서버로 보내지지 않으므로 스프링 폼 데이터 바인딩이 동작하도록 HTML에서 이 문제를 우회해야 한다. checkbox 태그는 각 체크박스에 언더스코어("_") 접두사가 붙는 히든 파라미터를 포함하는 기존의 스프링 관례를 따른다. 이렇게 함으로써 “ 체크박스가 폼에 보이고 객체에 체크박스의 상태에 관계없이 체크박스의 상태를 나타내는 폼 데이터가 바인딩될 것이다. ”라고 스프링에게 효과적으로 지시한다.
17.2.4.5 checkboxes 태그
이 태그는 'checkbox' 타입의 HTML 'input' 태그를 다수 생성한다. 앞의 checkbox 태그 부분의 예제를 이어서 보자. 때로는 JSP 페이지에서 가능한 모든 취미를 보여주길 원치 않을 수 있다. 사용가능한 옵션의 목록을 런타임에서 제공하고 이 목록을 태그에 전달한다. 이것이 checkboxes 태그의 목적이다. "items" 프로퍼티에 사용가능한 옵션을 담고 있는 Array, List, Map을 전달한다. 보통 바인딩된 프로퍼티가 컬렉션이므로 사용자가 선택한 여러가지 값을 담고 있을 수 있다. 다음은 이 태그를 사용한 JSP 예제이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Html <form:form> <table> <tr> <td>Interests:</td> <td> <%-- 프로퍼티는 배열이거나 java.util.Collection타입이다 --%> <form:checkboxes path="preferences.interests" items="${interestList}"/> </td> </tr> </table> </form:form> |
이 예제는 "interestList"가 모델 속성으로 사용할 수 있는 List로 선택된 값의 문자열을 담고 있다고 가정한다. Map을 사용하는 경우에 맵 엔트리의 키를 값으로 사용할 것이고 맵 엔트리의 값은 표시할 라벨로 사용할 것이다. "itemValue"를 사용한 값과 "itemLabel"를 사용한 라벨에 프로퍼티 명을 제공할 수 있는 커스텀 객체를 사용할 수도 있다.
17.2.4.6 radiobutton 태그
이 태그는 'radio' 타입의 HTML 'input' 태그를 생성한다. 일반적인 사용 패턴은 프로퍼티는 같지만 다른 값이 바인딩된 여러 태그 인스턴스를 포함하는 것이다.
1 2 3 4 5 6 7 | Html <tr> <td>Sex:</td> <td>Male: <form:radiobutton path="sex" value="M"/> <br/> Female: <form:radiobutton path="sex" value="F"/> </td> </tr> |
17.2.4.7 radiobuttons 태그
이 태그는 'radio' 타입의 HTML 'input' 태그를 다수 생성한다. 앞의 checkboxes 태그와 마찬가지로 런타임변수로 사용할 수 있는 옵션을 전달하기를 원할 수 있다. 이러한 경우에 radiobuttons 태그를 사용한다. "items" 프로퍼티에 사용가능한 옵션을 담고 있는 Array, List, Map을 전달한다. Map을 사용하는 경우 맵 엔트리의 키를 값으로 사용하고 맵 엔트리의 값을 표시할 라벨로 사용할 것이다. "itemValue"를 사용한 값과 "itemLabel"를 사용한 라벨에 대한 프로퍼티명을 제공할 수 있는 커스텀 객체를 사용할 수도 있다.
1 2 3 4 5 6 | Html <tr> <td>Sex:</td> <td><form:radiobuttons path="sex" items="${sexOptions}"/></td> </tr> |
17.2.4.8 password 태그
이 태그는 바인딩된 값을 사용해서 'password'타입의 HTML 'input' 태그를 생성한다.
1 2 3 4 5 6 7 8 | Html <tr> <td>Password:</td> <td> <form:password path="password" /> </td> </tr> |
기본적으로 패스워드 값은 보이지 않는다는 점을 명심해라. 패스워드 값을 보이게 하고 싶다면 다음과 같이 'showPassword' 값을 true로 설정해라.
1 2 3 4 5 6 7 8 | Html <tr> <td>Password:</td> <td> <form:password path="password" value="^76525bvHGq" showPassword="true" /> </td> </tr> |
17.2.4.9 select 태그
이 태그는 HTML 'select' 요소를 생성한다. 이 태그는 중첩된 option과 options 태그와 마찬가지로 선택한 option의 데이터바인딩을 지원한다. User과 기술 목록을 가진다고 가정하자.
1 2 3 4 5 6 | Html <tr> <td>Skills:</td> <td><form:select path="skills" items="${skills}"/></td> </tr> |
User의 skill이 Herbology이라면 'Skills' 열의 HTML 소스는 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 10 | Html <tr> <td>Skills:</td> <td><select name="skills" multiple="true"> <option value="Potions">Potions</option> <option value="Herbology" selected="selected">Herbology</option> <option value="Quidditch">Quidditch</option></select> </td> </tr> |
17.2.4.10 option 태그
이 태그는 HTML 'option'를 생성한다. 이 태그는 바인딩된 값에 기반해서 적절하게 'selected'를 설정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Html <tr> <td>House:</td> <td> <form:select path="house"> <form:option value="Gryffindor"/> <form:option value="Hufflepuff"/> <form:option value="Ravenclaw"/> <form:option value="Slytherin"/> </form:select> </td> </tr> |
User의 house가 Gryffindor이라면 'House'열의 HTML 소스는 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Html <tr> <td>House:</td> <td> <select name="house"> <option value="Gryffindor" selected="selected">Gryffindor</option> <option value="Hufflepuff">Hufflepuff</option> <option value="Ravenclaw">Ravenclaw</option> <option value="Slytherin">Slytherin</option> </select> </td> </tr> |
17.2.4.11 options 태그
이 태그는 HTML 'option' 태그의 리스트를 생성한다. 이 태그는 바인딩된 값에 기반해서 적적하게 'selected' 속성을 설정한다.
1 2 3 4 5 6 7 8 9 10 11 | Html <tr> <td>Country:</td> <td> <form:select path="country"> <form:option value="-" label="--Please Select"/> <form:options items="${countryList}" itemValue="code" itemLabel="name"/> </form:select> </td> </tr> |
User가 UK에 살고 있다면 'Country' 열의 HTML 소스는 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Html <tr> <td>Country:</td> <td> <select name="country"> <option value="-">--Please Select</option> <option value="AT">Austria</option> <option value="UK" selected="selected">United Kingdom</option> <option value="US">United States</option> </select> </td> </tr> |
예제에서 보듯이 option 태그와 options 태그와 함께 사용하면 같은 표준 HTML을 생성하지만 JSP에서 예제의 기본 문자열인 "-- Please Select"같은 보여주기 용도의 값을 명시적으로 지정할 수 있다.
items 속성은 보통 아이템 객체의 배열이나 컬렉션에 있다. itemValue와 itemLabel는 단순히 이러한 아이템 객체(지정했다면)의 빈 프로퍼티를 참조한다. 그렇지 않으면 아이템 객체 자체를 문자열화 한다. 아니면 맵의 키를 옵션값으로 맵의 값을 옵션 라벨로 해석하는 경우에 아이템의 Map을 지정할 수 있다. itemValue와 itemLabel을 지정했다면 아이템 값 프로퍼티는 맵의 키에 적용될 것이고 아이템 라벨 프로퍼티는 맵의 값에 적용될 것이다.
17.2.4.12 textarea 태그
이 태그는 HTML 'textarea'를 생성한다.
1 2 3 4 5 6 7 | Html <tr> <td>Notes:</td> <td><form:textarea path="notes" rows="3" cols="20" /></td> <td><form:errors path="notes" /></td> </tr> |
17.2.4.13 hidden 태그
이 태그는 바인딩된 값을 사용해서 'hidden' 타입의 HTML 'input' 태그를 생성한다. 바인딩되지 않은 히든 값을 제출(submit)하려면 'hidden'타입의 HTML input 태그를 사용해라.
Html
<form:hidden path="house" />
'house' 값을 히든으로 제출한다면 HTML은 다음과 같을 것이다.
Html
<input name="house" type="hidden" value="Gryffindor"/>
17.2.4.14 errors 태그
이 태그는 HTML 'span' 태그에 필드 오류를 렌더링한다. 이 태그는 컨트롤러나 컨트롤러와 연결된 밸리데이터가 생성한 오류에 접근한다.
폼을 제출하면 firstName과 lastName 필드에 대한 모든 오류 메시지를 노출하기를 원한다고 가정해보자. User 클래스의 인스턴스에 대한 밸리데이터로 UserValidator가 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Java public class UserValidator implements Validator { public boolean supports(Class candidate) { return User.class.isAssignableFrom(candidate); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required."); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required."); } } |
form.jsp는 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Html <form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <%-- firstName 필드에 대한 오류를 보여준다 --%> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <%-- lastName 필드에 대한 오류를 보여준다 --%> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> |
firstName과 lastName 필드가 빈 값인 폼을 제출한다면 HTML이 다음과 같이 될 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Html <form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <%-- firstName 필드와 연결된 오류를 노출한다 --%> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <%-- lastName 필드와 연결된 오류를 노출한다 --%> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form> |
해당 페이지의 전체 오류 목록을 보여주기를 원한다면? 다음 예제에서 errors 태그도 기본적인 와일드카드 기능을 제공한다는 것을 보여준다.
- path="*" - 모든 오류를 노출한다
- path="lastName" - lastName 필드와 연관된 모든 오류를 노출한다.
- path를 생략하면 - 객체 오류만 노출한다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Html <form:form> <form:errors path="*" cssClass="errorBox" /> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> |
HTML은 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Html <form method="POST"> <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </form> |
17.2.4.15 HTTP 메서드 변환
REST의 핵심 원리는 통일된 인터페이스(Uniform Interface)의 사용이다. 즉 모든 리소스(URL)은 네가지 HTTP 메서드인 GET, PUT, POST, DELETE로 조작할 수 있다. 각 메서드는 HTTP 명세서에서 정확한 의미를 정의하고 있다. 예를 들어 GET은 항상 안전한 작업(즉, 부가작용(side effect)이 없다)을 해야하고 PUT이나 DELETE는 멱등이 되어야 한다.(즉, 같은 작업을 계속해서 반복하더라도 같은 결과가 되어야 한다.) HTTP가 이러한 네가지 메서드를 정의하고 있지만 HTML은 두가지만 지원한다. GET과 POST만 지원한다. 다행이도 사용할 수 있는 두가지 우회방법이 있다. PUT이나 DELETE에는 자바스크립트를 사용하거나 그냥 POST를 사용하면서 실제 메서는 추가적인 파라미터(HTML 폼에서 히든 인풋 필드로 설계된)를 사용할 수 있다. 후자의 방법이 스프링의 HiddenHttpMethodFilter가 하는 것이다. 이 필터는 평범한 서블릿 필터이므로 어떤 웹 프레임워크와도 함께(스프링 MVC만이 아니라) 사용할 수 있다. 그냥 이 필터를 web.xml에 추가하면 히든 _method 파라미터를 가진 POST는 대응하는 HTTP 메서드 요청으로 변환될 것이다.
HTTP 메서드 변환을 지원하려면 스프링 MVC 폼 태그를 설정된 HTTP 메서드를 지원하도록 갱신해야 한다. 예를 들어 다음 코드는 갱신된 Petclinic 예제에서 가져온 것이다.
1 2 3 4 5 | Html <form:form method="delete"> <p class="submit"><input type="submit" value="Delete Pet"/></p> </form:form> |
이는 실제로 '진짜' DELETE 메서드가 요청파라미터로 숨겨져있는 HTTP POST를 수행할 것이고 이 요청 파라미터를 web.xml에 정의된 HiddenHttpMethodFilter가 골라낸다.
1 2 3 4 5 6 7 8 9 10 11 | Xml <filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <servlet-name>petclinic</servlet-name> </filter-mapping> |
대응하는 @Controller 메서드는 아래에 나와있다.
1 2 3 4 5 6 7 | Java @RequestMapping(method = RequestMethod.DELETE) public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { this.clinic.deletePet(petId); return "redirect:/owners/" + ownerId; } |
17.2.4.16 HTML5 태그
스프링 3부터는 스프링 폼 태그 라이브러리가 동적 속성에 진입할 수 있게 한다. 즉, HTML5에 특화된 속성에 진입할 수 있다.
스프링 3.1에서 폼 인풋 태그는 'text'가 아닌 타입 속성에 대한 진입을 지원한다. 이는 'email', 'date', 'range' 등과 같은 HTML5의 새로운 인풋 타입을 렌더링하기 위해서 추가된 것이다. 'text'가 기본타입이므로 type='text'에 진입하는 것은 필요하지 않다.
17.3 타일즈(Tiles)
다음 뷰 기술과 마찬가지로 스프링을 사용하는 웹 어플리케이션에서 타일즈(Tiles)를 통합할 수 있다. 통합하는 방법을 광범위한 방법으로 이어서 설명한다.
NOTE: 이 섹션은 타일즈 2 (자바 5+가 필요한 타일즈의 독립적인 버전)에 대한 스프링의 지원에 초점을 맞추고 있고 이 지원은 org.springframework.web.servlet.view.tiles2 패키지에 있다. 스프링은 계속해서 타일즈 1.x (스트럿츠(Struts) 1.1+에 포함되어 있으므로 "스트럿츠 타일즈"라고도 부르고 자바 1.4와 호환성이 있다)도 원래의 org.springframework.web.servlet.view.tiles 패키지에서 지원한다.
17.3.1 의존성
타일즈를 사용할 수 있게 하려면 프로젝트에 몇가지 의존성을 추가해야 한다. 다음은 필요한 의존성 목록이다.
- Tiles 버전 2.1.2이상
- Commons BeanUtils
- Commons Digester
- Commons Logging
17.3.2 타일즈를 통합하는 방법
타일즈를 사용할 수 있게 하려면 정의를 포함한 파일을 사용해서 타일즈를 구성해야 한다. (정의에 대한 기본적인 정보와 타일즈의 다른 컨셉이 궁금하다면 http://tiles.apache.org를 참고해라.) 스프링에서는 TilesConfigurer를 사용해서 구성한다. 다음의 ApplicationContext 설정 예시를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Xml <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/general.xml</value> <value>/WEB-INF/defs/widgets.xml</value> <value>/WEB-INF/defs/administrator.xml</value> <value>/WEB-INF/defs/customer.xml</value> <value>/WEB-INF/defs/templates.xml</value> </list> </property> </bean> |
보는 것처럼 정의를 가진 다섯개의 파일이 있고 모두 'WEB-INF/defs' 디렉토리에 위치하고 있다. WebApplicationContext 초기화시 이 파일들은 로드될 것이고 정의 팩토리가 초기화 될 것이다. 이 초기화가 완료된 후 정의 파일에 포함된 타일즈를 스프링 어플리케이션내에서 뷰로 사용할 수 있다. 이 뷰를 사용할 수 있게 하려면 스프링과 함께 사용하는 다름 뷰 기술과 마찬가지로 ViewResolver를 가져야 한다. 아래에서 사용할 수 있는 UrlBasedViewResolver와 ResourceBundleViewResolver를 볼 수 있다.
17.3.2.1 UrlBasedViewResolver
UrlBasedViewResolver는 처리해야할 각 뷰에 주어진 viewClass를 인스턴스화 한다.
1 2 3 4 5 | Xml <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/> </bean> |
17.3.2.2 ResourceBundleViewResolver
ResourceBundleViewResolver는 리졸버가 사용할 수 있는 뷰이름과 뷰클래스를 담고 있는 프로퍼티를 함께 제공해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Xml <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean> C-like ... welcomeView.(class)=org.springframework.web.servlet.view.tiles2.TilesView welcomeView.url=welcome (타일즈 정의의 이름이다) vetsView.(class)=org.springframework.web.servlet.view.tiles2.TilesView vetsView.url=vetsView (다시 말하지만 타일즈 정의의 이름이다) findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp ... |
보는 것처럼 ResourceBundleViewResolver를 사용하는 경우에는 쉽게 다른 뷰 기술들과 섞을 수 있다. 타일즈 2에 대한 TilesView 클래스는 JSTL(JSP Standard Tag Library)을 지원하지만 타일즈 1.x 지원에서는 별도로 분리된 TilesJstlView 하위 클래스가 존재한다.
17.3.2.3 SimpleSpringPreparerFactory와 SpringBeanPreparerFactory
고급 기능으로 스프링은 특수한 타일즈 2 PreparerFactory 구현체 두가지도 지원한다. 타일즈 정의 파일에서 ViewPreparer 참조를 사용하는 자세한 방법은 타일즈 문서를 확인해 봐라.
SimpleSpringPreparerFactory를 지정된 준비자 (preparer) 클래스에 기반해서 자동연결(atotowire) ViewPreparer 인스턴스로 지정해서 설정한 스프링 BeanPostProcessor를 적용하는 것과 마찬가지로 스프링의 컨테이너 콜백을 적용한다. 스프의 컨텍스트 범위 어노테이션 설정이 활성화되었다면 ViewPreparer 클래스의 어노테이션을 자동으로 탐지해서 적용할 것이다. 이는 기본 PreparerFactory가 하는 것과 마찬가지로 타일즈 정의 파일에 준비자(preparer) 클래스를 기대한다.
SpringBeanPreparerFactory를 지정된 준비자(preparer)의 클래스 대신 이름에서 동작하도록 지정해서 DispatcherServlet의 어플리케이션 컨텍스트에서 대응하는 스프링 빈을 획득한다. 이 경우에 전체 빈(bean) 생성과정이 스프링 어플리케이션 컨텍스트의 제어하에 있을 것이므로 명시적인 의존성주입 설정, 범위를 가진 빈 등을 사용할 수 있다. 준비자(preparer) 이름마다 하나의 스프링 빈 정의를 정의해야 한다. (타일즈 정의에서 사용한 것처럼)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Xml <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/general.xml</value> <value>/WEB-INF/defs/widgets.xml</value> <value>/WEB-INF/defs/administrator.xml</value> <value>/WEB-INF/defs/customer.xml</value> <value>/WEB-INF/defs/templates.xml</value> </list> </property> <!-- 스프링 빈 정의 이름처럼 준비자(preparer) 이름을 처리한다 --> <property name="preparerFactoryClass" value="org.springframework.web.servlet.view.tiles2.SpringBeanPreparerFactory"/> </bean> |
17.4 벨로시티(Velocity) & 프리마커(FreeMarker)
Velocity와 FreeMarker는 스프링 MVC 어플리케이션내에서 뷰 기술로 사용할 수 있는 템플릿 언어이다. 두 언어는 아주 비슷하고 유사한 요구사항을 제공하므로 이 섹션에서 함께 다룬다. 두 언어간에 의미적/문법적 차이점은 FreeMarker 웹사이트를 참고해라.
17.4.1 의존성
벨로시티나 프리마커가 각각 동작하도록 웹 어플리케이션에 velocity-1.x.x.jar나 freemarker-2.x.jar를 포함시켜야 할 것이고 벨로시티에는 commons-collections.jar가 필요하다. 보통은 Java EE 서버가 찾아내서 어플리케이션 클래스패스에 추가하는 것을 보장하는 WEB-INF/lib 폴더에 추가한다. 물론 'WEB-INF/lib' 디렉토리에 spring-webmvc.jar가 이미 있다고 가정한다! 벨로시티 뷰에서 스프링의 'dateToolAttribute'나 'numberToolAttribute'를 사용한다면 velocity-tools-generic-1.x.jar도 포함시켜야 할 것이다.
17.4.2 컨텍스트 구성
다음과 같이 '*-servlet.xml'에 관련된 구성자(configurer) 빈 정의를 추가함으로써 적합한 설정을 초기화한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | Xml <!-- 이 빈은 템플릿 루트경로에 기반해서 벨로시티 환경을 설정한다. 선택적으로 벨로시티 환경을 더 제어할 수 있도록 프로퍼티 파일을 지정할 수 있지만 파일에 기반한 템플릿 로딩에 기본값도 상당히 괜찮다. --> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/velocity/"/> </bean> <!-- 뷰 리졸버도 ResourceBundle이나 XML 파일로 설정할 수 있다. 로케일에 기반해서 뷰를 다르게 처리해야 한다면 리소스 번들 리졸버를 사용해야 한다. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".vm"/> </bean> Xml <!-- 프리마커 설정 --> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> </bean> <!-- 뷰 리졸버도 ResourceBundle이나 XML 파일로 설정할 수 있다. 로케일에 기반해서 뷰를 다르게 처리해야 한다면 리소스 번들 리졸버를 사용해야 한다. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".ftl"/> </bean> |
Note
웹앱이 아니면 어플리케이션 컨텍스트 정의 파일에 VelocityConfigurationFactoryBean나 FreeMarkerConfigurationFactoryBean를 추가해라.
웹앱이 아니면 어플리케이션 컨텍스트 정의 파일에 VelocityConfigurationFactoryBean나 FreeMarkerConfigurationFactoryBean를 추가해라.
17.4.3 템플릿 생성
앞에서 나왔듯이 *Configurer 빈으로 지정한 디렉토리에 템플릿을 저장해야 한다. 이 문서에서는 이 두 언어로 템플릿을 생성하는 자세한 내용은 다루지 않는다. 자세한 내용을 각 웹사이트를 참고해라. 앞에 나온 뷰 리졸버를 사용한다면 JSP의 InternalResourceViewResolver와 유사한 방법으로 논리적인 뷰 이름을 템플릿 파일명에 연결한다. 그래서 "welcome"이라는 뷰 이름을 가진 ModelAndView 객체를 컨트롤러가 반환하면 리졸버는 적절하게 /WEB-INF/freemarker/welcome.ftl나 /WEB-INF/velocity/welcome.vm를 찾을 것이다.
17.4.4 고급 설정
앞에 나온 기본 설정은 대부분의 어플리케이션 요구사항에 적합할 것이지만 일반적이지 않거나 고급 요구사항에 대한 추가적인 설정옵션을 사용할 수 있다.
17.4.4.1 velocity.properties
이 파일은 완전히 선택적이지만 지정한다면 벨로시티 자체를 설정하기 위한 Velocity 런타임에 전달되는 값을 담고 있다. 고급 설정에만 필요하고 이 파일이 필요하다면 앞에서 나온 VelocityConfigurer 빈 정의에 그 위치를 지정해야 한다.
1 2 3 4 5 | Xml <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="configLocation" value="/WEB-INF/velocity.properties"/> </bean> |
아니면 다음 인라인 프로퍼티로 "configLocation" 프로퍼티를 대체해서 Velocity 설정(config) 빈의 빈 정의에 직접 벨로시티 프로퍼티를 지정할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Xml <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="velocityProperties"> <props> <prop key="resource.loader">file</prop> <prop key="file.resource.loader.class"> org.apache.velocity.runtime.resource.loader.FileResourceLoader </prop> <prop key="file.resource.loader.path">${webapp.root}/WEB-INF/velocity</prop> <prop key="file.resource.loader.cache">false</prop> </props> </property> </bean> |
벨로시티의 스프링 설정은 API 문서를 참고하거나 벨로시티 문서의 예제와 'velocity.properties' 파일의 정의를 참고해라.
17.4.4.2 FreeMarker
FreeMarkerConfigurer 빈에 적절한 빈 프로퍼티를 설정해서 스프링이 관리하는 프리마커 Configuration 객체에 프리마커 'Settings'와 'SharedVariables'를 직접 전달할 수 있다. freemarkerSettings 프로퍼티는 java.util.Properties 객체를, freemarkerVariables는 java.util.Map를 필요로 한다.
1 2 3 4 5 6 7 8 9 10 11 12 | Xml <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> <property name="freemarkerVariables"> <map> <entry key="xml_escape" value-ref="fmXmlEscape"/> </map> </property> </bean> <bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/> |
Configuration 객체에 적용하는 설정과 변수의 자세한 내용은 프리마커 문서를 봐라.
17.4.5 바인딩 지원과 폼(form) 처리
스프링은 <spring:bind/>를 포함해서 JSP에서 사용할 수 있는 태그 라이브러리를 제공한다. 이 태그가 주로 폼을 지원하는 객체(form backing object)의 값과 웹 계층이나 비즈니스 계층의 Validator에서 실패한 유효성검사의 결과를 폼이 보여줄수 있도록 한다. 1.1 버전부터 스프링은 벨로시티와 프리마커 모두에 같은 기능을 제공하고 추가적으로 폼 인풋 요소를 직접 생성하는 편리한 매크로도 제공한다.
17.4.5.1 바인딩 매크로
두 언어에 대한 표준 매크로 세트가 spring-webmvc.jar 파일내에 있다. 그러므로 적절하게 설정된 어플리케이션에서는 항상 사용할 수 있다.
스프링 라이브러리에 정의된 매크로 중 일부는 내부에서 사용할 목적(private)이지만 매크로 정의에 그러한 범위는 존재하지 않으므로 모든 매크로는 호출하는 코드와 사용자 템플릿에서 보인다. 다음 부분에서는 템플릿 내에서 직접 호출해야 하는 매크로만 다룬다. 매크로 코드를 직접 보고싶다면 org.springframework.web.servlet.view.velocity나 org.springframework.web.servlet.view.freemarker 패키지에 각각 있는 spring.vm / spring.ftl 파일이다.
17.4.5.2 간단한 바인딩
스프링 폼 컴트롤러의 'formView'처럼 동작하는 html 폼(vm / ftl 템플릿)에서 JSP와 유사한 방법으로 각 인풋 필드에 필드 값을 바인딩하고 오류 메시지를 보여주도록 다음과 유사한 코드를 사용할 수 있다. 기본적으로 커맨드 객체의 이름은 "command"이지만 폼 컨트롤러에서 'commandName' 빈 프로퍼티를 설정해서 MVC 설정을 오버라이드할 수 있다. 앞에서 설정한 personFormV와 personFormF 뷰에 대한 예제코드가 다음에 나와있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | Html <!-- 벨로시티 매크로는 자동으로 사용가능하다 --> <html> ... <form action="" method="POST"> Name: #springBind( "command.name" ) <input type="text" name="${status.expression}" value="$!status.value" /><br> #foreach($error in $status.errorMessages) <b>$error</b> <br> #end <br> ... <input type="submit" value="submit"/> </form> ... </html> Html <!-- 네임스페이스로 프리마커 메크로를 임포트해야 한다. 'spring' 연결하는 것을 강력히 권장한다. --> <#import "/spring.ftl" as spring /> <html> ... <form action="" method="POST"> Name: <@spring.bind "command.name" /> <input type="text" name="${spring.status.expression}" value="${spring.status.value?default("")}" /><br> <#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list> <br> ... <input type="submit" value="submit"/> </form> ... </html> |
#springBind / <@spring.bind>는 커맨드객체(FormController에서 변경하지 않았다면 커맨드객체는 'command'일 것이다)의 이름과 바인딩하고자 하는 커맨드 객체의 필드 이름과 기간(period)으로 이루어진 'path' 아규먼트를 필요로 한다. 중첩된 필드도 "command.address.street"와 같이 사용할 수 있다. bind 매크로는 web.xml의 ServletContext 파라미터 defaultHtmlEscape로 지정한 기본 HTML 이스케이핑 동작을 가정한다.
선택적인 매크로 폼인 #springBindEscaped / <@spring.bindEscaped>는 두번째 아규먼트를 받고 명시적으로 상태 오류 메시지나 값에서 HTML 이스케이핑을 해야하는지를 명시적으로 지정한다. true나 false로 설정해야 한다. 추가적인 폼 처리 매크로는 HTML 이스케이핑을 간단히 사용할 수 있게 하고 가능하다면 이러한 매크로를 사용해야 한다. 이 매크로들은 다음 섹션에서 설명한다.
17.4.5.3 폼 인풋 생성 매크로
두 언어에 대한 추가적으로 편리한 매크로는 간단히 바인딩과 폼 생성을 할 수 있게 한다. (유효성검사 오류 노출을 포함해서) 폼 인풋 필드를 생성하는데 이러한 매크로를 사용하는 것이 필수는 아니고 간단한 HTML과 섞어서 사용하거나 이전에 강조한 스프링 바인딩 매크로를 직접 호출할 수 있다.
사용가능한 매크로의 다음 표는 VTL과 FTL 정의와 각각이 받는 파라미터 목록을 보여준다.
Table 17.1. 매크로 정의 표
매크로 | VTL 정의 | FTL 정의 |
---|---|---|
message (코드 파라미터에 기반한 리소스 번들의 문자열 출력) | #springMessage($code) | <@spring.message code/> |
messageText (코드 파라미터에 기반한 리소스 번들의 문자열 출력이고 폴백으로 기본 파라미터의 값을 사용한다) | #springMessageText($code $text) | <@spring.messageText code, text/> |
url (어플리케이션의 컨텍스트 루트에 상대적인 URL 접두사) | #springUrl($relativeUrl) | <@spring.url relativeUrl/> |
formInput (사용자 입력을 수집하기 위한 표준 인풋 필드) | #springFormInput($path $attributes) | <@spring.formInput path, attributes, fieldType/> |
formHiddenInput * (사용자가 입력하지 않은 인풋을 제출하기 위한 히든 인풋 필드) | #springFormHiddenInput($path $attributes) | <@spring.formHiddenInput path, attributes/> |
formPasswordInput * (패드워드를 가져오기 위한 표준 인풋 필드. 이 타입의 필드에는 값이 존재하지 않을 것이다.) | #springFormPasswordInput($path $attributes) | <@spring.formPasswordInput path, attributes/> |
formTextarea (길고 형식이 자유로운 텍스트 입력을 가져오기 위한 커다란 텍스트 필드) | #springFormTextarea($path $attributes) | <@spring.formTextarea path, attributes/> |
formSingleSelect (하나만 선택할 수 있는 드랍다운 박스) | #springFormSingleSelect( $path $options $attributes) | <@spring.formSingleSelect path, options, attributes/> |
formMultiSelect ( 0개 이상의 값을 선택할 수있는 리스트박스) | #springFormMultiSelect($path $options $attributes) | <@spring.formMultiSelect path, options, attributes/> |
formRadioButtons ( 선택가능한 후보들 중 하나를 선택할 수 있는 라디오 버튼의 세트) | #springFormRadioButtons($path $options $separator $attributes) | <@spring.formRadioButtons path, options separator, attributes/> |
formCheckboxes ( 0개 이상을 선택할 수 있는 체크박스의 세트) | #springFormCheckboxes($path $options $separator $attributes) | <@spring.formCheckboxes path, options, separator, attributes/> |
formCheckbox (단일 체크박스) | #springFormCheckbox($path $attributes) | <@spring.formCheckbox path, attributes/> |
showErrors ( 쉽게 바인딩된 필드에 유효성검사 오류를 보여준다) | #springShowErrors($separator $classOrStyle) | <@spring.showErrors separator, classOrStyle/> |
* fieldType 파라미터의 값으로 'hidden'나 'password'를 지정하는 일반적인 formInput 매크로 매크로를 사용할 수 있으므로 FTL (FreeMarker)에서 이러한 두 매크로는 사실 필요하지 않다. 위 매크로의 모든 파라미터는 일관된 의미를 가진다.
- path: 바인딩할 필드의 이름(예: "command.name")
- options: 인풋필드에서 선택할 수 있는 모든 값의 맵. 맵의 키는 폼에서 다시 POST해서 커맨드객체에 바인딩될 값을 나타낸다. 키에 저장된 Map 객체는 폼에서 사용자에게 보여지는 라벨이고 폼이 다시 포스팅할 값과는 다르다. 보통 이러한 맵은 컨트롤러가 참조하는 데이터로 제공한다. 필요한 동작에 따라 어떤 Map 구현체라도 사용할 수 있다. 엄격하게 정렬된 맵에서는 적합한 Comparator를 가진 TreeMap같은 SortedMap을 사용할 것이고 추가한 순서로 값을 반환해야 하는 임의의 맵에서는 commons-collection의 LinkedHashMap이나 LinkedMap를 사용한다.
- separator: 조심스러운(discreet) 요소(라디오버튼이나 체크박스)로 다중 옵션을 사용할 수 있는 곳에서 리스트의 각 아이템을 구분하는데 문자의 순서(sequence of characters)를 사용한다.(예: "<br>")
- attributes: HTML 태그자체에 포함시켜야 할 추가적인 태그의 문자열이나 텍스트. 이 문자열은 매크로가 리터럴로 반영한다. 예를 들어 텍스트에어리어에 'rows="5" cols="60"'같은 속성을 추가하거나 'style="border:1px solid silver"'같은 스타일정보를 전달할 수 있다.
- classOrStyle: showErrors 매크로의 CSS클래스의 이름으로 각 오류를 감싸는 span 태그가 사용할 것이다. 어떤 정보도 제공하지 않으면(또는 값이 비어있으면) 오류는 <b></b>태그로 감쌀 것이다.
인풋 필드
1 2 3 4 5 6 7 | Html <!-- VTL에서 폼 매크로를 사용하는 Name 필드 예제 --> ... Name: #springFormInput("command.name" "")<br> #springShowErrors("<br>" "")<br> |
formInput 매크로는 path 파리미터 (command.name)와 추가적인 속성 파라미터 (위 예제에서는 비어있다)를 받는다. formInput 매크로는 다른 폼 생성 매크로와 동일하게 path 파라미터에 암묵적인 스프링 바인딩을 수행한다. 새로운 바인딩이 일어날 때까지 바인딩이 유효한 상태로 유지되므로 showErrors 매크로는 path 파라미터를 다시 전달할 필요가 없다. 마지막에 생성된 바인딩이 어떤 필드에 이루어졌든지 간에 showErrors 매크로는 그냥 그 위에서 수행된다.
showErrors 매크로는 separator 파라미터(해당 필드의 여러 오류를 구분하는데 사용할 문자)를 받고 두번째 파라미터(클래스명이나 style 속성)도 받는다.벨로시티와는 달리 프리마커에서는 속성 파라미터에 기본값을 지정할 수 있고 위의 두 매크로 호출을 FTL에서는 다음과 같이 표현할 수 있다.
1 2 3 4 | Html <@spring.formInput "command.name"/> <@spring.showErrors "<br>"/> |
name 필드를 생성하고 필드에 값이 없이 폼을 제출한 후에 유효성검사 오류를 보여주는 폼 부분의 출력이 다음에 나와있다. 유효성검사는 스프링 Validation 프레임워크로 이뤄진다. 생성된 HTML은 다음과 같을 것이다.
1 2 3 4 5 6 7 8 | Html Name: <input type="text" name="name" value=""> <br> <b>required</b> <br> <br> |
formTextarea 매크로는 formInput 매크로와 같은 방법으로 동작하고 같은 파라미터 리스트를 받는다. 보통 두번째 파라미터(속성)은 style 정보나 텍스트에어리어의 rows, cols 속성을 전달하는데 사용한다.
선택(Selection) 필드
HTML 폼에서 값을 선택하는 인풋의 공통 UI를 생성하는데 네가지 선택(selection) 필드 매크로를 사용할 수 있다.
- formSingleSelect
- formMultiSelect
- formRadioButtons
- formCheckboxes
아래에 FTL에서의 라디오버튼 예제가 있다. 폼을 지원하는 객체(form backing object)는 이 필드에 'London'이라는 기본값을 지정하므로 유효성검사가 필요없다. 폼을 렌더링 할 때 선택할 도시의 전체 리스트는 'cityMap'라는 이름의 모델에 참조데이터로 제공한다.
1 2 3 4 5 | Html ... Town: <@spring.formRadioButtons "command.address.town", cityMap, "" /><br><br> |
이는 "" 구분자를 사용하는 cityMap의 각 값에 대한 라디오 버튼 코드를 렌더링한다. 추가적인 속성은 제공하지 않았다.(매크로의 마지막 파라미터가 빠졌다.) cityMap은 맵의 각 키/값 쌍에 같은 문자열을 사용한다. 맵의 키는 실제로 폼이 POST하는 요청 파라미터로 제출하는 것이고 맵의 값은 사용자가 보는 라벨이다. 유명한 세 도시의 리스트가 주어지고 폼을 지원하는 객체에서 기본값을 가진 위의 예제의 HTML은 다음과 같을 것이다.
1 2 3 4 5 6 7 8 9 | Html Town: <input type="radio" name="address.town" value="London"> London <input type="radio" name="address.town" value="Paris" checked="checked"> Paris <input type="radio" name="address.town" value="New York"> New York |
예를 들어 어플리케이션이 내부 코드로 도시를 다루기를 원한다면 다음 예제와 같이 적절한 키로 코드의 맵을 생성할 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 | Java protected Map referenceData(HttpServletRequest request) throws Exception { Map cityMap = new LinkedHashMap(); cityMap.put("LDN", "London"); cityMap.put("PRS", "Paris"); cityMap.put("NYC", "New York"); Map m = new HashMap(); m.put("cityMap", cityMap); return m; } |
이 코드는 라이오 버튼의 값이 관련된 코드인 곳에 결과코드를 생성하지만 사용자는 여전히 친숙한 도시명을 본다.
1 2 3 4 5 6 7 8 9 | Html Town: <input type="radio" name="address.town" value="LDN"> London <input type="radio" name="address.town" value="PRS" checked="checked"> Paris <input type="radio" name="address.town" value="NYC"> New York |
17.4.5.4 HTML 이스케이프와 XHTML 준수(compliance)
위 폼 매크로의 기본 사용방법은 HTML 4.01을 준수하는 HTML 태그를 생성하고 스프링의 바인딩 지원이 사용하는 web.xml에 정의한 HTML 이스케이프의 기본값을 사용할 것이다. XHTML을 준수하는 태그를 생성하거나 기본 HTML 이스케이프값을 덮어쓰려고 템플릿에(또는 템플릿에서 볼 수 있는 모델에) 두가지 변수를 지정할 수 있다. 템플릿에 이 변수들을 지정할 때의 장점은 폼에서 다른 필드에 다른 동작을 하도록 템플릿에서 나중에 다른값으로 변경할 수 있다는 것이다.
태그가 XHTML을 준수하도록 바꾸려면 xhtmlCompliant라는 이름의 model/context 변수를 'true' 값으로 지정한다.
1 2 3 4 5 6 7 | C-like ## Velocity에서는.. #set($springXhtmlCompliant = true) <#-- FreeMarker에서는 --> <#assign xhtmlCompliant = true in spring> |
이 디렉티브를 처리한 후에는 스프링 매크로가 생성한 모든 태그가 XHTML을 준수할 것이다. 유사한 방법으로 HTML 이스케이핑을 필드마다 지정할 수 있다.
1 2 3 4 5 6 7 8 9 10 | Xml <#-- 이 지점까지는 기본 HTML 이스케이핑을 사용한다 --> <#assign htmlEscape = true in spring> <#-- 다음 필드는 HTML 이스케이핑을 사용할 것이다 --> <@spring.formInput "command.name" /> <#assign htmlEscape = false in spring> <#-- 이후의 모든 필드는 HTML 이스케이핑을 사용하지 않을 것이다 --> |
댓글 없음:
댓글 쓰기