[Info]Tags categorized posts and contents patterns..

[AJAX] Ajax Code E xamples.. [Book] About the book.. [CSS] CSS Code E xamples.. [DB] Sql Code E xamples.. [DEV] All development stor...

2016년 8월 3일 수요일

[JAVA] 17장 뷰 기술 #2..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

17.5 XSLT
XSLT는 XML의 변환언어로 웹 어플리케이션의 뷰 기술로 인기가 있다. XSLT는 어플리케이션이 자연스럽게 XML을 다루거나 모델을 쉽게 XML로 변환할 수 있다면 뷰 기술로 괜찮은 선택이 될 수 있다. 다음 섹션은 모델 데이터로 XML 문서를 생성하고 스프링 웹 MVC 어플리케이션에서 XSLT로 변환하는 방법을 보여준다.

17.5.1 My First Words 예제
이 예제는 Controller에서 단어의 리스트를 생성하고 모델 맵에 단어를 추가하는 간단한 스프링 어플리케이션이다. XSLT의 뷰 이름과 일치하는 맵을 반환한다. 스프링 웹 MVC의 Controller 인터페이스의 자세한 내용은 Section 16.3, “컨트롤러 구현하기”을 참고해라. XSLT 뷰는 변환을 준비히려고 단어의 리스트를 간단한 XML 문서로 바꿀 것이다.

17.5.1.1 빈(Bean) 정의
설정은 간단한 스프링 어플리케이션의 표준 설정이다. 디스패처 서블릿 설정 파일은 ViewResolver, URL 매핑, 단일 컨트롤러 빈 등에 대한 참조를 담고 있다.

Xml

<bean id="homeController"class="xslt.HomeController"/>

이는 단어를 생성하는 로직은 은닉화한다.

17.5.1.2 표준 MVC 컨트롤러 코드
컨트롤러 로직은 다음과 같이 정의된 핸들러 메서드와 함께 AbstractController의 하위클래스에서 은닉화된다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Java

protected ModelAndView handleRequestInternal(
  HttpServletRequest request,
  HttpServletResponse response) throws Exception {

  Map map = new HashMap();
  List wordList = new ArrayList();

  wordList.add("hello");
  wordList.add("world");

  map.put("wordList", wordList);

  return new ModelAndView("home", map);
}

지금까지 XSLT과 관련된 것은 아무것도 하지 않았다. 다른 스프링 MVC 어플리케이션과 같은 방법으로 모델 데이터를 생성한다. 어플리케이션에 설정에 따라 단어 리스트를 요청 속성에 추가해서 JSP/JSTL가 단어 리스트를 렌더링하거나 VelocityContext에 객체를 추가해서 Velocity가 단어리스트를 다룰 수 있다. XSLT가 단어 리스트를 렌더링하기 위해 단어 리스트는 당연히 XML 문서로 어떻게든 변환되어야 한다. 자동으로 객체 그래프를 'domify'할 수 있는 소프트웨어 패키지가 존재하지만 스프링에서는 선택한 어떤 방법으로든 모델에서 DOM을 생성하는 유연성을 가진다. 이는 DOM으로 만드는(domification) 과정을 다루는 도구를 사용하는 경우 위험한 모델 데이터의 구조에서 다루기 너무 큰 XML의 변환을 막는다.

17.5.1.3 모델데이터를 XML로 변환하기
단어 리스트나 다른 모델 데이터에서 DOM 문서를 생성하려면 (제공된) org.springframework.web.servlet.view.xslt.AbstractXsltView의 하위클래스를 만들어야 한다. 하위 클래스를 만들때는 보통 추상 메서드인 createXsltSource(..)를 구현해야 한다. 이 메서드의 전달하는 첫번째 파라미터는 모델 맵이다. 간단한 단어 어플리케이션의 HomePage 클래스의 완전한 소스가 다음에 나와있다.

 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
Java

package xslt;

// 간결함을 위해서 임포트문은 생략한다

public class HomePage extends AbstractXsltView {

  protected Source createXsltSource(Map model, String rootName, HttpServletRequest
    request, HttpServletResponse response) throws Exception {

    Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
    Element root = document.createElement(rootName);

    List words = (List) model.get("wordList");
    for (Iterator it = words.iterator(); it.hasNext();) {
      String nextWord = (String) it.next();
      Element wordNode = document.createElement("word");
      Text textNode = document.createTextNode(nextWord);
      wordNode.appendChild(textNode);
      root.appendChild(wordNode);
    }
    return new DOMSource(root);
  }
}

이름/값 쌍의 파라미터들은 변환 객체에 추가할 하위클래스가 선택적으로 정의할 수 있다. 파라미터 이름은 <xsl:param name="myParam">defaultValue</xsl:param>로 선언한 XSLT 템플릿에서 정의한 것과 일치해야 한다. 파라미터를 지정하려면 AbstractXsltView 클래스의 getParameters() 메서드를 오버라이드하고 이름/값 쌍의 Map을 반환해라. 현재 요청 정보에서 파라미터를 얻어야 한다면 대신 getParameters(HttpServletRequest request)를 오버라이드할 수 있다.

17.5.1.4 뷰 프로퍼티 정의하기
views.properties 파일(또는 앞의 Velocity 예제에서 했듯이 XML 기반의 뷰 리졸버를 사용한다면 views.properties의 역할을 하는 XML 정의)은 뷰가 하나인 'My First Words' 어플리케이션에서는 다음과 같을 것이다.

1
2
3
4
5
C-like

home.(class)=xslt.HomePage
home.stylesheetLocation=/WEB-INF/xsl/home.xslt
home.root=words

이 예제에서 첫 프로퍼티 '.(class)'에서 모델을 DOM으로 만드는 과정(model domification)을 다루는 HomePage 클래스와 뷰을 엮는 방법을 볼 수 있다. 'stylesheetLocation' 프로퍼티는 XML을 HTML로 변환하는 과정을 다룰 XSLT 파일을 가리키고 마지막 프로퍼티인 '.root'는 XML 문서의 루트로 사용할 이름이다. 이는 앞의 HomePage 클래스의 createXsltSource(..) 메서드에 두번째 파라미터로 전달해서 얻는다.

17.5.1.5 문서 변환
마지막으로 앞에 문서를 변환하는데 사용할 XSLT 코드가 있다. 앞의 'views.properties' 파일에서 보았듯이 스타일시트는 'home.xslt'이고 이 파일은 'WEB-INF/xsl' 디렉토리의 war 파일에 존재한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Xml

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="html" omit-xml-declaration="yes"/>

  <xsl:template match="/">
    <html>
      <head><title>Hello!</title></head>
      <body>
        <h1>My First Words</h1>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="word">
    <xsl:value-of select="."/><br/>
  </xsl:template>
</xsl:stylesheet>

17.5.2 요약
언급했던 파일의 요약과 WAR 파일에서 이 파일들의 위치를 WAR 구조를 간략화해서 다음에 나와있다.

C-like

ProjectRoot
  |
  +- WebContent
      |
      +- WEB-INF
          |
          +- classes
          |    |
          |    +- xslt
          |    |   |
          |    |   +- HomePageController.class
          |    |   +- HomePage.class
          |    |
          |    +- views.properties
          |
          +- lib
          |   |
          |   +- spring-*.jar
          |
          +- xsl
          |   |
          |   +- home.xslt
          |
          +- frontcontroller-servlet.xml

또한 클래스패스에 XML 파서와 XSLT 엔진을 사용할 수 있는지 확인해야 한다. JDK 1.4는 기본적으로 이 둘을 제공하고 대부분의 Java EE 컨테이너도 기본적으로 이 둘을 사용할 수 있게 하지만 인식오류가 생길 수 있다.

17.6 문서의 뷰 (PDF/Excel)
17.6.1 소개
항상 HTML 페이지를 반환하는 것이 모델의 출력을 사용자에게 보여주는 가장 좋은 방법은 아니다. 스프링은 모델 데이터에서 동적으로 PDF 문서나 Excel 스프리드시트를 쉽게 생성할 수 있게 해준다. 문서는 뷰이고 클라이언트 PC가 응답에서 스프리트시트나 PDF 뷰어 어플리케이션을 실행할 수 있도록 올바른 컨텐트 타입으로 서버에서 스트리밍될 것이다.

Excel 뷰를 사용하려면 클래스패스에 'poi' 라이브러리를 추가해야 하고 PDF를 생성하려면 iText 라이브러리를 추가해야 한다.

17.6.2 구성과 설정
문서에 기반한 뷰는 XSLT 뷰과 거의 동일한 방법으로 다룬다. 다음 섹션에서는 같은 모델을 PDF문서와 Excel 스프리드시트(Open Office에서 보고 다룰 수도 있다.)로 모두 렌더링하는데 XSLT 예제에서 사용한 동일한 컨트롤러를 호출하는 방법을 설명하면서 앞의 예제를 사용한다.

17.6.2.1 문서 뷰 정의
우선 views.properties 파일(또는 동일한 XML)을 수정하고 두가지 문서타입에 대한 간단한 뷰 정의를 추가해보자. 전체 파일은 이제 다음과 같을 것이고 XSLT 뷰는 앞에 나와있다.

1
2
3
4
5
6
7
8
9
C-like

home.(class)=xslt.HomePage
home.stylesheetLocation=/WEB-INF/xsl/home.xslt
home.root=words

xl.(class)=excel.HomePage

pdf.(class)=pdf.HomePage

모델 데이터를 추가하려고 템플릿 스프리드 시트나 채워넣을 수 있는 PDF 폼으로 시작하고 싶다면 뷰 정의에서 'url' 프로퍼티로 위치를 지정해라.

17.6.2.2 컨트롤러 코드
사용할 컨트롤러 코드는 뷰이름을 변경한것 외에는 앞에서 사용한 XSLT 예제와 정확히 동일하다. 물론 URL이나 다른 로직에 기반해서 뷰 이름을 선택하도록 만들 수 있고 이 부분이 컨트롤러와 뷰의 커플링을 줄이는데 스프링이 정말로 좋다는 증거이다!

17.6.2.3 Excel 뷰의 하위클래스 만들기
XSLT 예제에서 했던것과 정확히 동일하게 출력 문서를 생성하는 동작을 커스터마이징하려면 적절한 추장클래스의 하위클래스르 만들 것이다. 엑셀에서는 org.springframework.web.servlet.view.document.AbstractExcelView (POI가 생성한 Excel파일에서)나 org.springframework.web.servlet.view.document.AbstractJExcelView (JExcelApi가 생성한 Excel 파일에서)의 하위클래스를 작성하고 buildExcelDocument() 메서드를 구현한다.

새로운 스프리드시트의 첫 행의 연속된 열로 모델 맵의 단어 리스트를 보여주는 POI Excel 뷰의 전체코드가 다음에 나와있다.

 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
Java

package excel;

// 간결하게 임포트 문은 생략한다

public class HomePage extends AbstractExcelView {

  protected void buildExcelDocument(
    Map model,
    HSSFWorkbook wb,
    HttpServletRequest req,
    HttpServletResponse resp)
    throws Exception {

    HSSFSheet sheet;
    HSSFRow sheetRow;
    HSSFCell cell;

    // 첫 시트(sheet)로 이동
    // getSheetAt: 존재하는 문서에서 wb가 생성된 경우에만
    // sheet = wb.getSheetAt(0);
    sheet = wb.createSheet("Spring");
    sheet.setDefaultColumnWidth((short) 12);

    // A1에 텍스트를 쓴다
    cell = getCell(sheet, 0, 0);
    setText(cell, "Spring-Excel test");

    List words = (List) model.get("wordList");
    for (int i=0; i < words.size(); i++) {
      cell = getCell(sheet, 2+i, 0);
      setText(cell, (String) words.get(i));
    }
  }
}

그리고 다음은 JExcelApi를 사용해서 같은 Excel 파일을 생성하는 뷰다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Java

package excel;

// 간결하게 임포트 문은 생략한다

public class HomePage extends AbstractJExcelView {

  protected void buildExcelDocument(Map model,
    WritableWorkbook wb,
    HttpServletRequest request,
    HttpServletResponse response)
  throws Exception {

    WritableSheet sheet = wb.createSheet("Spring", 0);

    sheet.addCell(new Label(0, 0, "Spring-Excel test"));

    List words = (List) model.get("wordList");
    for (int i = 0; i < words.size(); i++) {
      sheet.addCell(new Label(2+i, 0, (String) words.get(i)));
    }
  }
}

API의 차이점에 주목해라. JExcelApi가 약간 더 직관적이고 이지미 처리 기능이 좀 더 좋다. 하지만 JExcelApi를 사용하는 경우 큰 Excel 파일에서는 메모리 문제가 있다.

이제 컨트롤러가 뷰 이름으로 xl를 반환하는 (return new ModelAndView("xl", map);) 등의 수정을 하고 어플리케이션을 다시 실행하면 이전과 같은 페이지를 요청했을 때 자동적으로 Excel 스프리드시트가 생성되고 다운로드 되는 것을 볼 것이다.

17.6.2.4 PDF 뷰의 하위클래스 만들기
단어 리스트의 PDF 버전은 더 간단하다. 이번에는 다음과 같이 org.springframework.web.servlet.view.document.AbstractPdfView를 확장하고 buildPdfDocument() 메서드를 구현한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java

package pdf;

// 간결하게 임포트 문은 생략한다

public class PDFPage extends AbstractPdfView {

  protected void buildPdfDocument(
    Map model,
    Document doc,
    PdfWriter writer,
    HttpServletRequest req,
    HttpServletResponse resp)
    throws Exception {

    List words = (List) model.get("wordList");

    for (int i=0; i<words.size(); i++)
      doc.add( new Paragraph((String) words.get(i)));
  }
}

다시 한번 return new ModelAndView("pdf", map);로 pdf 뷰를 반환하게 컨트롤러를 수정하고 어플리케이션에서 URL을 갱신하자. 이번에는 모델 맵의 각 단어 리스트가 나오는 PDF 문서가 나올 것이다.

17.7 JasperReports
JasperReports (http://jasperreports.sourceforge.net)는 이해하기 쉬운 XML 파일 형식을 사용해서 리포트를 생성할 수 있는 강력한 오픈소스 리포트 엔진이다. JasperReports는 CSV, Excel, HTML, PDF 네가지 형식으로 리포트를 만들 수 있다.

17.7.1 의존성
어플리케이션에는 JasperReports의 최신버전이 포함시켜야 한다. 이 글을 쓰는 시점에서 최신 버전은 0.6.1이다. JasperReports는 다음 프로젝트에 의존하고 있다.

  • BeanShell
  • Commons BeanUtils
  • Commons Collections
  • Commons Digester
  • Commons Logging
  • iText
  • POI
JasperReports도 JAXP를 따르는 XML 파서가 필요하다.

17.7.2 구성
스프링 컨테이너 구성에서 JasperReports 뷰를 설정하려면 뷰 이름을 원하는 리포트 형식에 맞는 뷰 클래스에 매핑할 ViewResolver를 정의해야 한다.

17.7.2.1 ViewResolver 설정
보통 프로퍼티 파일에서 뷰 이름을 뷰 클래스와 파일들에 매핑하는데 ResourceBundleViewResolver를 사용할 것이다.

1
2
3
4
5
Xml

<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
  <property name="basename" value="views"/>
</bean>

여기서 기반이름인 views으로(이 파일의 내용은 다른 섹션에서 설명한다.) 리소스 번들에서 뷰 매핑을 검색할 ResourceBundleViewResolver 클래스의 인스턴스를 설정했다.

17.7.2.2 View 구성
스프링 프레임워크는 JasperReports에 대한 다섯가지 View 구현체를 가진다. 네가지는 JasperReports가 지원하는 출력 포맷에 대응하고 한가지는 런타임에서 포맷을 결정할 수 있게 한다.

Table 17.2. JasperReports View 클래스

클래스명렌더링 형식
JasperReportsCsvViewCSV
JasperReportsHtmlViewHTML
JasperReportsPdfViewPDF
JasperReportsXlsViewMicrosoft Excel
JasperReportsMultiFormatView이 뷰는 런타임에서 결정된다






이러한 클래스를 뷰 이름과 리포트 파일에 매핑하는 것은 이전 섹션에서 설정한 리소스 번들에 추가한 적합한 엔트리에 관련되어 있다.

1
2
3
4
C-like

simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper

여기서 simpleReport 뷰 이름이 JasperReportsPdfView 클래스에 매핑된 것을 볼 수 있다. 그래서 이 리포트는 PDF 형식으로 출력된다. 뷰의 url 프로퍼티는 사용하는 리포트 파일의 위치로 설정한다.

17.7.2.3 리포트 파일에 대해서
JasperReports 는 두 가지 다른 타입의 리포트 파일을 가진다. .jrxml 확장자를 갖는 디자인 파일과 .jasper 확장자를 갖는 컴파일된 리포트 파일이다. 보통 어플리케이션에 배포하기 전에 .jrxml 디자인 파일을 .jasper 파일로 컴파일하는데 JasperReports Ant 테스크를 사용한다. 스프링 프레임워크에서는 이러한 파일들을 모두 리포트 파일에 매핑할 수 있고 스프링 프레임워크가 알아서 .jrxml파일을 컴파일할 것이다. 스프링 프레임워크가 .jrxml 파일을 컴파일한 후에 컴파일된 리포트는 어플리케이션 생명주기동안 캐싱된다는 것을 기억해야 한다. 그러므로 파일을 변경하려면 어플리케이션을 재시작해야 한다.

17.7.2.4 JasperReportsMultiFormatView 사용하기
JasperReportsMultiFormatView 로 런타임에서 리포트 형식을 지정할 수 있다. 리포트의 실제 렌더링은 다른 JasperReports 뷰 클래스에 위임한다. JasperReportsMultiFormatView 클래스는 런타임에서 지정되는 정확한 구현체를 위한 랩퍼 계층을 추가할 뿐이다.

JasperReportsMultiFormatView 클래스는 두가지 컨셉을 도입했다. 두 컨셉은 형식키(format key)와 식별자키(discriminator key)이다. JasperReportsMultiFormatView 클래스는 실제 뷰 구현체 클래스를 검색하는데 매핑 키를 사용하고 매핑키를 검색하는데 형식키를 사용한다. 코딩하는 관점에서는 키에는 포맷키를, 값에는 매핑키를 가진 모델에 엔트리를 추가한다. 예를 들면 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Java

public ModelAndView handleSimpleReportMulti(HttpServletRequest request,
HttpServletResponse response) throws Exception {

  String uri = request.getRequestURI();
  String format = uri.substring(uri.lastIndexOf(".") + 1);

  Map model = getModel();
  model.put("format", format);

  return new ModelAndView("simpleReportMulti", model);
}

이 예제에서 매핑키는 요청 URI의 확장자로 결정하고 기본 형식키 (format)로 모델에 추가한다. 다른 형식키를 사용하고 싶다면 JasperReportsMultiFormatView 클래스의 formatKey 프로퍼티를 사용해서 설정할 수 있다. 기본적으로 다음 매핑키의 매핑은 JasperReportsMultiFormatView에서 설정한다.

Table 17.3. JasperReportsMultiFormatView 기본 매핑키의 매핑

매핑 키뷰 클래스
csvJasperReportsCsvView
htmlJasperReportsHtmlView
pdfJasperReportsPdfView
xlsJasperReportsXlsView





그러므로 위의 예제에서 /foo/myReport.pdf URI의 요청은 JasperReportsPdfView 클래스로 매핑될 것이다. JasperReportsMultiFormatView의 formatMappings 프로퍼티로 매핑키와 뷰 클래스의 매핑을 오버라이드할 수 있다.

17.7.3 ModelAndView의 생성
선택한 포맷으로 리포트를 제대로 렌더링하려면 리포트에 필요한 모든 데이터를 스프링에 제공해야 한다. JasperReports에서는 리포트 데이터소스와 함께 모든 리포트 파라미터를 전달해야 한다. 리포트 파라미터는 간단한 이름/값의 쌍이고 다른 값/쌍을 추가하듯이 모델의 Map에 추가할 수 있다.

모델에 데이터소스를 추가할 때 선택할 수 있는 두가지 접근법이 있다. 첫 접근법은 JRDataSource의 인스턴스나 Collection타입을 임의의 키로 모델 Map에 추가하는 것이다. 그러면 스프링이 모델에서 이 객체를 찾아내서 리포트 데이터소스로 다룰 것이다. 예를 들어 다음과 같이 모델을 생성할 것이다.

1
2
3
4
5
6
7
8
Java

private Map getModel() {
  Map model = new HashMap();
  Collection beanData = getBeanData();
  model.put("myBeanData", beanData);
  return model;
}

두 번째 접근방법은 JRDataSource의 인스턴스나 Collection를 특정 키 아래 추가하고 뷰 클래스의 reportDataKey 프로퍼티를 사용해서 이 키를 설정하는 것이다. 두 경우에 스프링은 JRBeanCollectionDataSource 인스턴스로 Collection의 인스턴스를 감쌀 것이다. 예를 들면 다음과 같다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

private Map getModel() {
  Map model = new HashMap();
  Collection beanData = getBeanData();
  Collection someData = getSomeData();
  model.put("myBeanData", beanData);
  model.put("someData", someData);
  return model;
}

여기서 두 Collection 인스턴스가 모델에 추가되는 것을 볼 수 있다. 올바른 인스턴스가 사용되는지 보장하기 위해 뷰 설정을 적절하게 수정한다.
1
2
3
4
5
C-like

simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper
simpleReport.reportDataKey=myBeanData

첫 접근방법을 사용하는 경우 스프링은 찾아낸 JRDataSource의 첫 인스턴스나 Collection을 사용한다는 것을 알아야 한다. JRDataSource의 여러 인스턴스나 Collection을 모델에 두어야 한다면 두번째 접근방법을 사용해야 한다.

17.7.4 하위리포트 사용하기
JasperReports 는 마스터 리포트 파일내에 내장된 하위 리포트를 지원한다. 리포트 파일에 하위 리포트를 추가하는 아주 다양한 메타니즘이 좀재한다. 가장 쉬운 방법은 디자인 파일에 하위리포트의 피로트 경로와 SQL 쿼리를 하드코딩하는 것이다. 이 접근방법의 단점은 명확하다. 리포트 파일에 하드코딩된 값은 재사용성을 낮추고 리포트 디자인을 수정하기 어렵게 한다. 이 문제를 해결하기 위해 선언적으로 하위리포트를 설정하고 컨트롤러에서 이러한 하위리포트에 추가적인 데이터를 직접 포함시킬 수 있다.

17.7.4.1 하위리포트 파일 구성하기
마스터 리포트에 포함된 하위리포트 파일들을 스프링을 사용해서 제어하려면 리포트 파일이 외부 소스에서 하위리포트를 받아들이도록 설정해야 한다. 이 설정을 하려면 리포트파일에서 다음과 같이 파라미터를 설정한다.

1
2
3
Xml

<parameter name="ProductsSubReport" class="net.sf.jasperreports.engine.JasperReport"/>

그 다음 이 리포트 파라미터를 사용하는 하위리포트를 선언한다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Xml

<subreport>
  <reportElement isPrintRepeatedValues="false" x="5" y="25" width="325"
    height="20" isRemoveLineWhenBlank="true" backcolor="#ffcc99"/>
  <subreportParameter name="City">
    <subreportParameterExpression><![CDATA[$F{city}]]></subreportParameterExpression>
  </subreportParameter>
  <dataSourceExpression><![CDATA[$P{SubReportData}]]></dataSourceExpression>
  <subreportExpression class="net.sf.jasperreports.engine.JasperReport">
      <![CDATA[$P{ProductsSubReport}]]></subreportExpression>
</subreport>

이 예제는 ProductsSubReport 파라미터에 net.sf.jasperreports.engine.JasperReports 인스턴스로 전달되는 하위리포트를 기대하는 마스터 리포트를 정의한다. Jasper 뷰 클래스를 구성할 때 subReportUrls 프로퍼티를 사용해서 스프링이 리포트 파일을 로딩해서 JasperReports에 하위리포트로 전달하도록 할 수 있다.
1
2
3
4
5
6
7
Xml

<property name="subReportUrls">
  <map>
    <entry key="ProductsSubReport" value="/WEB-INF/reports/subReportChild.jrxml"/>
  </map>
</property>

여기서 Map의 키는 리포트 디자인 파일의 하위리포트 파라미터의 이름과 일치하고 엔트리는 리포트 파일의 URL이다. 스프링은 이 리포트 파일을 로딩하고 필요하다면 컴파일해서 주어진 키로 JasperReports 엔진에 전달한다.

17.7.4.2 하위리포트 데이터 소스 설정하기
하위리포트를 구성하는데 스프링을 사용하는 경우 이 과정은 완전히 선택적이다. 원한다면 정적 쿼리를 사용해서 하위 리포트에 대한 데이터 소스를 설정할 수 있다. 하지만 스프링이 ModelAndView가 반환한 데이터를 JRDataSource 인스턴스로 변환하기를 원한다면 스프링이 변환해야 하는 ModelAndView에 어느 파라미터를 변환해야 하는지 지정해야 한다. 선택한 뷰 클래스의 subReportDataKeys 프로퍼티를 사용해서 파라미터명 목록을 설정한다.

Xml

<property name="subReportDataKeys" value="SubReportData"/>

여기서 제공한 키는 ModelAndView에서 사용한 키와 리포트 디자인 파일에서 사용한 키 모두와 반드시 일치해야 한다.

17.7.5 익스포터(Exporter) 파라미터 설정
익스포터(exporter) 구성과 관련해서 특수한 요구사항이 있다면(PDF 리포트의 페이지 크기를 지정하는 등) 뷰 클래스의 exporterParameters 파라미터를 사용해서 스프링 설정 파일에 선언적으로 익스포터 파라미터를 설정할 수 있다. exporterParameters 프로퍼티의 타입은 Map이다. 설정에서 엔트리의 키는 익스포터 파라미터 정의를 포함하는 정정 필드의 정규화된 이름이어야 하고 엔트리의 값은 파라미터에 할당하고자 하는 값이어야 한다. 다음에 이 예제가 나와있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Xml

<bean id="htmlReport" class="org.springframework.web.servlet.view.jasperreports.JasperReportsHtmlView">
  <property name="url" value="/WEB-INF/reports/simpleReport.jrxml"/>
  <property name="exporterParameters">
    <map>
      <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER">
        <value>Footer by Spring!
          &lt;/td&gt;&lt;td width="50%"&gt;&amp;nbsp; &lt;/td&gt;&lt;/tr&gt;
          &lt;/table&gt;&lt;/body&gt;&lt;/html&gt;
        </value>
      </entry>
    </map>
  </property>
</bean>

이 예제에서 출력 HTML의 푸터에 출력할 net.sf.jasperreports.engine.export.JRHtmlExporterParameter.HTML_FOOTER로 익스포터 파라미터가 설정된 JasperReportsHtmlView를 볼 수 있다.

17.8 피드(Feed) 뷰
AbstractAtomFeedView와 AbstractRssFeedView 모두 기반클래스인 AbstractFeedView를 상속받고 각각 Atom과 RSS 피드에 사용한다. 이 두 클래스는 java.net의 ROME 프로젝트에 기반하고 있으며 org.springframework.web.servlet.view.feed 패키지에 있다.

다음과 같이 AbstractAtomFeedView에서는 buildFeedEntries() 메서드를 구현해야 하고 선택적으로 buildFeedMetadata() 메서드(기본 구현체는 비어있다.)를 오버라이드한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Java

public class SampleContentAtomView extends AbstractAtomFeedView {

  @Override
  protected void buildFeedMetadata(Map<String, Object> model, Feed feed,
      HttpServletRequest request) {
    // 구현부는 생략한다
  }

  @Override
  protected List<Entry> buildFeedEntries(Map<String, Object> model,
      HttpServletRequest request, HttpServletResponse response)
      throws Exception {

    // 구현부는 생략한다
  }
}

다음과 같이 AbstractRssFeedView를 구현할 때도 비슷한 요구사항이 적용된다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Java

public class SampleContentAtomView extends AbstractRssFeedView {

  @Override
  protected void buildFeedMetadata(Map<String, Object> model, Channel feed,
      HttpServletRequest request) {
    // 구현부는 생략한다
  }

  @Override
  protected List<Item> buildFeedItems(Map<String, Object> model,
      HttpServletRequest request, HttpServletResponse response)
      throws Exception {
    // 구현부는 생략한다
  }
}

로케일(Locale)에 접근해야 하는 경우 buildFeedItems()와 buildFeedEntires() 메서드를 HTTP 요청에 전달한다. HTTP 응답은 쿠키나 다른 HTTP 헤더를 설정할 때만 전달된다. 메서드가 반환된 뒤 피드는 자동적으로 응답 객체에 자동적으로 쓰여질 것이다. Atom 뷰를 생성하는 예제는 Alef Arendsen가 작성한 SpringSource Team Blog의 을 참고해라.

17.9 XML 마샬링 뷰
MarhsallingView 는 XML로 응답 내용을 렌더링할 때 org.springframework.oxm 패키지에 정의된 XML Marshaller를 사용한다. 마샬링되어야 하는 객체는 MarhsallingView의 modelKey 빈 프로퍼티를 사용해서 명시적으로 설정할 수 있다. 아니면 뷰는 모든 모델 프로퍼티를 순회하고 Marshaller가 지원하는 타입만 마샬링할 것이다. org.springframework.oxm 패키지의 기능에 대한 내용은 O/X 매퍼(Mapper)를 사용한 XML 마샬링(Marshalling)장을 참고해라.

17.10 JSON 매핑 뷰
MappingJacksonJsonView 는 JSON으로 응답 내용을 렌더링할 때 Jackson 라이브러리의 ObjectMapper를 사용한다. 기본적으로 모델 맵의 전체 내용(프레임워크에 특화된 클래스의 예외와 함께)은 JSON으로 인코딩 될 것이다. 맵의 내용을 필터링해야 하는 경우 사용자들은 RenderedAttributes 프로퍼티로 인코딩해야 하는 모델 속성을 지정할 것이다. extractValueFromSingleKeyModel 프로퍼티도 모델 속성의 맵 보다는 직접 추출하고 직렬화한 단일키(single-key) 모델에 값을 갖는데 사용할 것이다.

Jackson이 제공하는 어노테이션을 사용해서 JSON 매핑을 필요에 따라 커스터마이징 할 수 있다. 더 세밀한 제어가 필요하다면 특정 타입에 제공해야 하는 커스텀 JSON 직렬변환기(serializers)/역직렬변환기(deserializers)가 필요한 곳에 ObjectMapper 프로퍼티로 커스텀 ObjectMapper를 주입할 수 있다.


[JAVA] 17장 뷰 기술 #1..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

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)이다.

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'가 된다.
접근방법과 상관없이 같은 HTML 구조를 생성한다. 다음은 몇몇 체크박스의 HTML 코드들이다.

 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)
VelocityFreeMarker는 스프링 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를 추가해라.

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>태그로 감쌀 것이다.
아래에 FTL과 VTL에서의 매크로 예제가 나와있다. 두 언어사이의 사용법 차이는 추가로 설명해놨다.

인풋 필드


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
각 네 매크로는 폼 필드의 값과 해당 값에 대응되는 라벨을 담고 있는 Map을 받는다. 값과 라벨이 같을 수 있다.

아래에 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 이스케이핑을 사용하지 않을 것이다 -->