3. WebMVC

3.1 web.xml

다국어 지원을 위해 UTF-8 을 기본 Encoding 으로, org.springframework.web.filter.CharacterEncodingFilter 을 등록 한다. 만약 Tomcat 을 사용하는 경우, server.xml 의 <Connector>에 URIEncoding="UTF-8" 요소도 추가하여야 한다.

<!-- 
##########################################
#	CharacterEncodingFilter
##########################################
-->
<filter>
	<filter-name>encodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>utf-8</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>encodingFilter</filter-name>
	<servlet-name>appServlet</servlet-name>
</filter-mapping>	
		

RESTful URL을 사용하기 위해, org.springframework.web.filter.HiddenHttpMethodFilter 을 등록 한다.

<!-- 
##########################################
#	http MethodFilter
##########################################
-->
<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>appServlet</servlet-name>
</filter-mapping>
		

multipart 지원을 위해 org.springframework.web.multipart.support.MultipartFilter 을 등록 한다.

<!-- 
##########################################
#	MultipartFilter
##########################################
-->
<filter>
	<filter-name>multipartFilter</filter-name>
	<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>multipartFilter</filter-name>
	<servlet-name>appServlet</servlet-name>
</filter-mapping>
		

Spring Context 와 Spring-Security 의 Locale 설정을 통일하기 위해 com.u2ware.springfield.support.i18n.LocaleChangeFilter 을 등록 한다.

<!-- 
##########################################
#	LocaleChangeFilter
##########################################
-->
<filter>
	<filter-name>localeChangeFilter</filter-name>
	<filter-class>com.u2ware.springfield.support.i18n.LocaleChangeFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>localeChangeFilter</filter-name>
	<servlet-name>appServlet</servlet-name>
</filter-mapping>
		

Spring-Security 지원을 위해 org.springframework.web.filter.DelegatingFilterProxy 을 등록 한다.

<!-- 
##########################################
#	springSecurityFilterChain
##########################################
-->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<servlet-name>appServlet</servlet-name>
</filter-mapping>
		

3.2 Request Mapping

3.2.1 topLevelMapping

Section 1.3, “<springfield:modules>” 은 등록된 EntityController 의 handler method 룰 RESTful 요청 경로로 매핑 한다. Section 1.2, “@Springfield” 의 topLevelMapping 속성을 설정하여 요청 경로를 변경할 수 있으며, 별도의 선언 이 없으면, basePackage 를 기준으로 결정된다. 요청 경로에 따른 응답 view name 은 EntityController 의 메소드 이름이 조립되어 문자열로 리턴된다.

Table 3.1. Request mapping. See Section 1.4, “Springfield Example”

handle methodHTTP MethodRequest Path Response ViewName
fooController.find()GET/foo"/foo/find"
fooController.read()GET/foo/1"/foo/read"
fooController.createForm()GET/foo/new"/foo/createForm"
fooController.create()POST/foo/new"/foo/create"
fooController.updateForm()GET/foo/1/edit"/foo/updateForm"
fooController.update()PUT/foo/1/edit"/foo/update"
fooController.delete()DELETE/foo/1/edit"/foo/delete"
barSearchController.find()GET/a/b/c/d"/a/b/c/d/find"
barSearchController.read()GET/a/b/c/d/1"/a/b/c/d/read"
barSearchController.createForm()GET/a/b/c/d/new"/a/b/c/d/createForm"
barSearchController.create()POST/a/b/c/d/new"/a/b/c/d/create"
barSearchController.updateForm()GET/a/b/c/d/1/edit"/a/b/c/d/updateForm"
barSearchController.update()PUT/a/b/c/d/1/edit"/a/b/c/d/update"
barSearchController.delete()DELETE/a/b/c/d/1/edit"/a/b/c/d/delete"

3.2.2 methodLevelMapping

요청 매핑은 Section 1.2, “@Springfield” 의 methodLevelMapping 속성값에 의해 결정된다. methodLevelMapping 에서 EntityController 의 handler method 이름과 확장자를 선언 하는 방식으로 요청 매핑을 추가, 변경 할수 있다. 이때, 선언가능한 확장자는 Table 3.2, “Extension Based Resolving views” 과 같다. 와일드카드는 EntityController 의 모든 handler method 를 의미하며, 별도의 선언이 없으면 default 로 {*} 가 등록된다.

  • 기본 매핑 예시
    //
    package com.yourcompany.yourproject.foo;
    
    @Springfield
    @Entity
    public class Foo{
    	@Id
    	private String name;
    	private Integer age;
    	...
    }
    				
    Controller method()HTTP MethodRequest Path Response ViewName
    fooController.find()GET/foo"/foo/find"
    fooController.read()GET/foo/1"/foo/read"
    fooController.createForm()GET/foo/new"/foo/createForm"
    fooController.create()POST/foo/new"/foo/create"
    fooController.updateForm()GET/foo/1/edit"/foo/updateForm"
    fooController.update()PUT/foo/1/edit"/foo/update"
    fooController.delete()DELETE/foo/1/edit"/foo/delete"

     

  • 메소드 매핑 예시
    //
    package com.yourcompany.yourproject.foo;
    
    @Springfield
    @Entity(
    	methodLevelMapping={
    		"find", "find.html", "find.json",
    		"read", "read.html", "read.json" }
    )
    public class Foo{
    	@Id
    	private String name;
    	private Integer age;
    	...
    }
    				
    Controller method()HTTP MethodRequest Path Response ViewName
    fooController.find()GET/foo"/foo/find"
      /foo.html"/foo/find.html"
      /foo.json"/foo/find.json"
    fooController.read()GET/foo/1"/foo/read"
      /foo/1.html"/foo/read.html"
      /foo/1.json"/foo/read.json"

     

  • 확장자 매핑 예시
    //
    package com.yourcompany.yourproject.foo;
    
    @Springfield
    @Entity(
    	methodLevelMapping={"*.jstl"}
    )
    public class Foo{
    	@Id
    	private String name;
    	private Integer age;
    	...
    }
    				
    Controller method()HTTP MethodRequest Path Response ViewName
    fooController.find()GET/foo.jstl"/foo/find.jstl"
    fooController.read()GET/foo/1.jstl"/foo/read.jstl"
    fooController.createForm()GET/foo/new.jstl"/foo/createForm.jstl"
    fooController.create()POST/foo/new.jstl"/foo/create.jstl"
    fooController.updateForm()GET/foo/1/edit.jstl"/foo/updateForm.jstl"
    fooController.update()PUT/foo/1/edit.jstl"/foo/update.jstl"
    fooController.delete()DELETE/foo/1/edit.jstl"/foo/delete.jstl"

     

3.3 Resolving View

Section 1.3, “<springfield:modules>”에 의해 EntityController 가 등록되는 경우 ViewName 을 랜더링하기 위해 다음 과 같은 View Resolver Bean 들이 자동 등록된다. ViewName 의 확장자를 추출하여, 해당하는 View Resolver 를 찾아서 랜더링을 위임한다. 확장자가 없는 경우, Thymeleaf 가 디폴트로 적용된다.

Table 3.2. Extension Based Resolving views

ExtensionView Resolver Bean Nameoperations
*.htmlspringfieldWebmvcRenderThymeleafViewResolver Thymeleaf
*.jstlspringfieldWebmvcRenderJstlViewResolver JSP
*.tilesspringfieldWebmvcRenderTilesViewResolver Tiles2
*.jsonspringfieldWebmvcRenderJsonViewResolver JSON View
*.xmlspringfieldWebmvcRenderXmlViewResolver XML View
*.xlsspringfieldWebmvcRenderXlsViewResolver Excel View
*.csvspringfieldWebmvcRenderCsvViewResolver CSV View
*.downloadspringfieldWebmvcRenderDownloadViewResolver File Download View
*.streamspringfieldWebmvcRenderStreamViewResolver File Streaming View


3.3.1 Thymeleaf

Section 1.3, “<springfield:modules>” 가 지원하는 Thymeleaf는 Rendering 을 위한 정적인 리소스(html, jsp)를 추가하여 Customizing 할수 있다. runtime 에 미리 정해진 위치를 스캔하여 정적인 리소스가 존재하면 이를 사용 한다. 만약 해당 위치에 정적인 리소스가 없다면, <springfield:moduls> 가 기본제공하는 Thymeleaf 페이지가 사용 된다. 리소스 검색 위치 순서는 다음과 같다.

  1. /WEB-INF/com/yourcompany/yourproject/{viewName}.html
  2. classapth:com/yourcompany/yourproject/{viewName}.html
@Springfield(toplevelMapping="/sample")
@Entity
public class Sample {

	@Id
	private Integer intValue;

	private String stringValue;	

	private Float floatValue;	

	private DateTime dateTimeValue;	

	//...	
}
		

다음과 같이 /WEB-INF/com/yourcompany/yourproject/sample/find.html 을 작성하고,

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
	xmlns:th="http://www.thymeleaf.org" 
	xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">

	<h1>My Themyleaf Page</h1>

</html>
		

[GET:/sample] 요청 일때, 위 리소스를 리턴한다.

3.3.2 JSON

Section 1.3, “<springfield:modules>” 은 JSON View 를 위해 jacksonjackson-annotations 을 를 사용하고 있다. property name 을 바꾸기 위해 @JsonProperty 를 사용하거나 , 해당 property 를 출력하지 않도록 @JsonIgnore 를 사용할수 있다.

@Springfield(toplevelMapping="/sample", methodLevelMapping={"*.json"})
@Entity
public class Sample {

	@Id
	private Integer intValue;

	@JsonProperty("newPropertyName")
	private String stringValue;	

	@JsonIgnore
	private Float floatValue;	

	private DateTime dateTimeValue;	

	//...	
}
		

[GET:/sample/{intValue}.json] 요청 일때 , 다음과 같은 JSON Content 를 리턴 한다.

{
	"intValue":1,
	"newPropertyName":"abcd"
	"dateTimeValue":"2011-01-01T00:00:00.000+09:00",
}			
		

3.3.3 SpreadSheet

Section 1.3, “<springfield:modules>” 은 Excel View 를 위해 jExcelApi 를 사용하고 있다. Excel 의 Herder 셀의 명칭을 바꾸기 위해 @XlsProperty 를 사용하거나 , 해당 property 를 출력하지 않도록 @XlsIgnore 를 사용할수 있다.

@Springfield(toplevelMapping="/sample", methodLevelMapping={"*.xls"})
@Entity
public class Sample {

	@Id
	private Integer intValue;

	@XlsProperty("새이름")
	private String stringValue;	

	@XlsIgnore
	private Float floatValue;	

	private DateTime dateTimeValue;	

	//...	
}
		

[GET:/sample.xls] 요청 일때, 다음과 같은 Excel Sheet 를 응답한다.

** Response Sheet
+----------+-------------+---------------+
| intValue | 새이름      | dateTimeValue |
+----------+-------------+---------------+
| 1        | a           | 2010-01-01    |
+----------+-------------+---------------+
| 1        | b           | 2010-01-01    |
+----------+-------------+---------------+
		

Section 1.3, “<springfield:modules>”의 Excel View 는 runtime 에 미리 정해진 위치를 스캔하여 Template File 이 존재하면 이를 parsing 하여 응답 한다. Template File 은 Spring Expression Language (SpEL) 문법을 따른다. Template File 이 사용될 경우 @XlsProperty @XlsIgnore 는 모두 무시된다. Template File 검색 위치 순서는 다음과 같다.

  1. /WEB-INF/com/yourcompany/yourproject/{viewName}.xls
  2. classapth:com/yourcompany/yourproject/{viewName}.xls

다음과 같이 /WEB-INF/com/yourcompany/yourproject/sample/read.xls 을 작성하고,

** Template Sheet                          
+--------+------------------+                
| 정수   | #{intValue}      |		   
+--------+------------------+		     
| 문자   | #{stringValue}   |		   
+--------+------------------+  
| 실수   | #{floatValue}    |		   
+--------+------------------+		     
| 날짜   | #{dateTimeValue} |		   
+--------+------------------+		     
		

[GET:/sample/{intValue}.xls] 요청 일때 , 다음과 같은 Excel Sheet 를 리턴한다.

** Response Sheet              
+--------+------------------+  
| 정수   | 1                |  
+--------+------------------+  
| 문자   | a                |  
+--------+------------------+  
| 실수   | 1                |  
+--------+------------------+  
| 날짜   | 2010-01-01       |  
+--------+------------------+  
		

Section 1.3, “<springfield:modules>”의 Excel View 는 요청은 파일 다운로드 형태로 응답한다. 이때 다운로드되는 파일 이름을 변경하기 위해서, DownloadBean 을 implements 한다.

@Springfield(toplevelMapping="/sample", methodLevelMapping={"*.xls"})
@Entity
public class Sample implements DownloadBean{

	@Id
	private Integer intValue;

	@XlsProperty("새이름")
	private String stringValue;	

	@XlsIgnore
	private Float floatValue;	

	private DateTime dateTimeValue;	

	@Override
	public String getContentName(){
		return "YourFileName";
	}
}
		

[GET:/sample/{intValue}.xls] 요청 일때 , "YourFileName.xls" 이름으로 Excel Sheet 를 리턴한다.

3.4 Navigation

Section 1.3, “<springfield:modules>”은 basePackage 를 기준으로 "/**/navigation.xml" 을 자동 스캔한다. 다음 예시의 navigation_ko.xml 과 navigation_en.xml 파일이 basePackage 내에 어떤 위치라도 존재한다면 별도의 설정없이 이를 load 한다.

  • navigation_ko.xml
    <navigation>
    	<navigation	path="/foo"  name="회사" pattern="/foo/**"  access="hasRole('ADMIN')" />
    	...	
    </navigation>			
    				
  • navigation_en.xml
    <navigation>
    	<navigation	path="/foo"  name="yourcompany" pattern="/foo/**"  access="hasRole('ADMIN')" />
    	...	
    </navigation>			
    				

Table 3.3. navigation element

AttributeDesc
path Menu Link.
name Menu Display Name.
pattern Spring-Security Request-matching
access Spring-Security Common built-in expressions