Spring에서 Freemarker 사용하기

– Spring은 Freemarker 2.3이상을 지원합니다.
– 사용하는 방법은 간단합니다.

0. 라이브러리 추가.
클래스패스에 freemarker.jar 파일을 추가해 줍니다. 이 파일은 spring-with-depedencies를 받으셨다면, lib에 freemarker에 있습니다.

1. XXX-servlet.xml에 뷰 리졸버와 프리마커 Configurer 객체를 세팅하는 FreeMarkerConfigurer 빈 등록하기

<!– freemarker config –>
<bean id=”freemarkerConfig” class=”org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer”>
  <property name=”templateLoaderPath” value=”/WEB-INF/freemarker/”/>
</bean>

<!–
  View resolvers can also be configured with ResourceBundles or XML files. If you need
  different view resolving based on Locale, you have to use the resource bundle resolver.
–>
<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>

끝. 매우 간단합니다. freemarkerConfig 빈은 프리마커의 Configuration 객체를 대변하는 객체로 이 객체다가 Configuration 객체에 필요한 설정을 하듯이 세터를 사용해서 설정하면 됩니다. 이 객체는 다른 컴포넌트들에게 공유될 객체가 아니라 순수하게 프리마커 설정을 하기 위해서 사용되는 빈입니다. 따라서 id는 없어도 됩니다. 그리고 FreeMarkerConfigurer 이 빈의 상위 클래스가 제공하는 setFreemarkerVariables 세터를 사용해서 프리마커 탬플릿에서 자주 사용하는 맵 객체를 세팅할 수도 있습니다. 또한 탬플릿 파일들의 기본 위치를 지정해야합니다. 그렇기 때문에 아래에 있는 viewResolver에서 prefix를 굳이 줄 필요가 없습니다.

FreeMarkerViewResolver에서는 인코딩을 설정할 수 있습니다. 위와 같이 레퍼런스에 나와있는 기본 설정대로 하면, 한글이 깨집니다. 그래서 contentType 속성에 text/html; charset=UTF-8 값을 설정해 줍니다.

저는 다음과 같이 설정했습니다.

    <bean 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”/>

    <bean id=”viewResolver”
        class=”org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver”>
        <property name=”contentType” value=”text/html; charset=UTF-8″ />
        <property name=”suffix” value=”.ftl” />
    </bean>

그리고 레퍼런스에서 스프링이 제공하는 커스텀 지시자를 사용하려면 페이지 위에 <#import “spring.ftl” as spring /> 이렇게 추가하라고 나와있는데, 제 경우에는 그렇게 하면 못 찾더군요. <#import “/spring.ftl” as spring /> 이렇게 /를 추가해주어야 클래스 패스(spring.jar안에 모모모 패키지에 들어있습니다.)에 있는 탬플릿 파일을 찾아서 사용할 수 있었습니다.

14.6. Document views (Excel)

1. 컨트롤러 만들고 bean으로 등록하기

2. view 클래스 만들기

3. view 프로퍼티 파일에 class 추가

이렇게 세 단계를 거치면 됩니다. 이 전에 작성한 글 두개에서 컨트롤러 만들고 등록하고 view 프로퍼티에 class추가해 주는 부분은 많이 살펴봤기 때문에 이번에는 생략하겠습니다.

public class MemberListExelView extends AbstractExcelView {

    @SuppressWarnings(“unchecked”)
    @Override
    protected void buildExcelDocument(Map model, HSSFWorkbook wb, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        HSSFSheet sheet;
        HSSFRow sheetRow;
        HSSFCell cell;

        sheet = wb.createSheet(“MemberInfoList”);
        sheet.setDefaultColumnWidth((short) 12);

        cell = getCell(sheet, 0, 0);
        setText(cell, “전체 멤버 조회”);
        cell = getCell(sheet, 1, 0);
        setText(cell, “이름”);
        cell = getCell(sheet, 1, 1);
        setText(cell, “이메일”);
        cell = getCell(sheet, 1, 2);
        setText(cell, “홈페이지”);

        List<MemberInfo> memberInfos = (List<MemberInfo>) model.get(“members”);
        MemberInfo memberInfo = null;

        for(int i = 0 ; i < memberInfos.size() ; i++ ){
            memberInfo = memberInfos.get(i);
            cell = getCell(sheet, i+2, 0);
            setText(cell, memberInfo.getName());
            cell = getCell(sheet, i+2, 1);
            setText(cell, memberInfo.getEmail());
            cell = getCell(sheet, i+2, 2);
            setText(cell, memberInfo.getHome());
        }
    }
}

getCell()로 cell을 가져 온 다음 setText(cell, String)으로 각각의 셀을 채워 넣는 형식입니다.
사용자 삽입 이미지사용자 삽입 이미지올커니.. 한글이 또 깨졌구나.

한글은 2byte 영문은 1byte 그래서 자꾸 깨지는 건데 HSSFCell API를 보면 setEncoding이라는 메소드가 있습니다. 여기에 1을 넣어주면 16bit, 0을 넣어주면 8bit로 설정된다고 합니다.

cell = getCell(sheet, 0, 0);
cell.setEncoding((short)1);
setText(cell, “전체 멤버 조회”);

요런 식으로 중간 중간 setEncoding() 사용해주면 한글도 제대로 출력할 수 있습니다.

사용자 삽입 이미지

14.6. Document views (PDF)

이미 이전 글에서 XML 형식으로 모델 데이타를 보여주는 방법을 살펴 봤기 때문에 PDF와 Exel로 출력하는 방법도 비슷하게 배울 수 있을 것 같습니다.

문제는 view 클레스를 만드는 기술인데 XSLT 만드는 방법은 쉬웠는데 PDF와 Exel 만드는 코딩은 어떨지 해봐야겠네요. 🙂

1. 먼저 추가 적인 jar 파일들이 필요합니다.

Exel은 poi.jar PDF는 iText.jar가 필요합니다.

2. 이번에도 컨트롤러 만들고 등록 합니다.

public class ViewMemberListWithPDFController extends AbstractController{

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
        List<MemberInfo> infos = ServiceManager.getMemberInfoService().getAll();
        Map<String, Object> members = new HashMap<String, Object>();
        members.put(“members”, infos);
        return new ModelAndView(“memberListPdf”, members);
    }

}

<bean name=”/viewMemberListWithPdf.html” class=”net.ailgejava.members.controller.ViewMemberListWithPDFController” />

Xstl 예제랑 거의 같은데 view 이름만 다릅니다.

3. view 클레스들을 만듭니다.

public class MemberListPdfView extends AbstractPdfView {

    @SuppressWarnings(“unchecked”)
    @Override
    protected void buildPdfDocument(Map model, Document document, PdfWriter writer, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        List<MemberInfo> memberInfos = (List<MemberInfo>) model.get(“members”);

        for(MemberInfo memberInfo : memberInfos){
            document.add(new Paragraph(
                memberInfo.getName() + ” ” +
                memberInfo.getEmail() + ” ” +
                memberInfo.getHome()
            ));
        }
    }

}

몬지 모르겠지만 Paragraph 객체로 찍는 것 같군요.

4. view 프로퍼티에 등록합니다.

memberListXml.class = net.ailgejava.members.view.xml.MemberListXMLView
memberListXml.stylesheetLocation=/WEB-INF/xsl/memberList.xslt
memberListXml.root=memberInfos

memberListPdf.class=net.ailgejava.members.view.pdf.MemberListPdfView

이전 글에 등록했던 내용과 같이 있습니다.

5. 데모

사용자 삽입 이미지사용자 삽입 이미지이런.. 이름이 안 찍혔네요. 흠… 왜이럴까요; 이름은 한글이라 인코딩 문제가 있는 걸까요. 흠… confluence에서 PDF로 빼낼 때도 한글 문제가 있었는데 어떻게 해결했었는지 살펴봐야겠네요.

14.5. XSLT

모델 데이타를 XML 형식으로 출력할 수 있습니다.

1. 컨트롤러를 만들고 등록합니다.

public class ViewMemberListWithXMLController extends AbstractController{

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
        List<MemberInfo> infos = ServiceManager.getMemberInfoService().getAll();
        Map<String, Object> members = new HashMap<String, Object>();
        members.put(“members”, infos);
        return new ModelAndView(“memberListXml”, members);
    }

}

<bean name=”/viewMemberListWithXml.html” class=”net.ailgejava.members.controller.ViewMemberListWithXMLController” />

2. 모델 데이타를 XML 로 만드는 view 클레스를 만듭니다.

public class MemberListXMLView extends AbstractXsltView {

    @SuppressWarnings(“unchecked”)
    @Override
    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<MemberInfo> memberInfos = (List<MemberInfo>) model.get(“members”);
        for (MemberInfo memberInfo : memberInfos) {
            String name = memberInfo.getName();
            String email = memberInfo.getEmail();
            String home = memberInfo.getHome();
            String pictureAddress = memberInfo.getPictureAddress();

            Element memberInfoNode = document.createElement(“memberInfo”);
            Text nameText = document.createTextNode(name);
            Text emailText = document.createTextNode(email);
            Text homeText = document.createTextNode(home);
            Text pictureAddressText = document.createTextNode(pictureAddress);

            memberInfoNode.appendChild(nameText);
            memberInfoNode.appendChild(emailText);
            memberInfoNode.appendChild(homeText);
            memberInfoNode.appendChild(pictureAddressText);

            root.appendChild(memberInfoNode);
        }

        return new DOMSource(root);
    }
}

이때 Text, Element, Document 객체의 패키지는 org.w3c.dom 패키지 입니다. 레퍼런스의 예제는 Generic을 사용하지 않는 Map과 List 그리고 Iterator를 명시적으로 사용하고 있더군요. Java 5버전 형태로 바꿔주면 좋을텐데 말이죠. 🙂

변수와 메소드 이름의 작명을 잘해뒀기 때문에 직감적으로 대강 어떻게 코딩 해야 하는지 알 수 있습니다.

3. 뷰 프로퍼티 파일 정의하기.

memberListXml.class = net.ailgejava.members.view.xml.MemberListXMLView
memberListXml.stylesheetLocation=/WEB-INF/xsl/memberList.xslt
memberListXml.root=memberInfos

class 는 위에서 만든 view 클레스
stylesheetLocation은 XML을 HTML로 변환하여 보여줄 xslt 의 위치
root 는 XML의 root 엘리먼트 이름을 나타냅니다.

뷰 프로퍼티 파일을 썼다는 것은 ResourceBundleViewResolver를 사용하였다는 것입니다. 기존에 사용하던 리졸버와 상관없이 추가로 등록해 주면 됩니다. 대신 어차피 기본적으로 InternalResourceViewResolver 이 녀석이 Resolver Chaining 할 때 맨 뒤로 가긴 하지만 그래도 명시적으로 order 프로퍼티를 사용하여 순서를 명확히 해줍니다.

    <!– View Resolver –>
    <!– XML, Exel, PDF –>
    <bean id=”ExViewResolver” class=”org.springframework.web.servlet.view.ResourceBundleViewResolver”>
          <property name=”basename” value=”views”/>
          <property name=”order” value=”1″ />
    </bean>

    <!– jstl –>
    <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” />
        <property name=”order” value=”2″ />
    </bean>


4. XSLT 만들기

<?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>AJN Members</title></head>
            <body>
                <h1>전체 회원 목록(XML)</h1>
                <xsl:apply-templates/>
            </body>
        </html>
    </xsl:template>

    <xsl:template match=”memberInfo“>
        <xsl:value-of select=”.”/><br/>
    </xsl:template>

</xsl:stylesheet>

<xsl:apply-templates/> 이녀석은 xml 보여줄 곳을 명식하는 것 같고 이 부분에 뿌려줄 내용을 아랫 부분에서 정의해 주는 것 같습니다. 저기서 memberInfo 는 위의 View 클레스 만들 때 사용한 Element의 이름과 동일해야 합니다.

5. 데모

사용자 삽입 이미지 
사용자 삽입 이미지

14.3. Tiles

14.3.1. Dependencies

Tiles를 사용하려면 다음의 jar파일들이 필요합니다.
– Struts version 1.1 or highe
– Commons BeanUtils
– Commons Digester
– Commons Lang
– Commons Logging

14.3.2. How to integrate Tiles

Tile를 사용할 때 필요한 definition 파일들을 서술해 줍니다.

<bean id=”tilesConfigurer” class=”org.springframework.web.servlet.view.tiles.TilesConfigurer”>
  <property name=”factoryClass” value=”org.apache.struts.tiles.xmlDefinition.I18nFactorySet”/>
  <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>

위 설정에서 다섯개의 definition들을 세팅했습니다. 저 파일들이 각각 하나의 tile인건가요? 흠;; 안써봐서 모르겠군요;

Anyway, Tiles도 사용하려면 역시 ViewResolver를 등록해야 하는데 역시 두 가지 Resolver를 사용할 수 있습니다.

– InternalResourceViewResolver

<bean id=”viewResolver” class=”org.springframework.web.servlet.view.InternalResourceViewResolver“>
  <property name=”requestContextAttribute” value=”requestContext”/>
  <property name=”viewClass” value=”org.springframework.web.servlet.view.tiles.TilesView“/>
</bean>

– ResourceBundleViewResolver

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


welcomeView.class=org.springframework.web.servlet.view.tiles.TilesView
welcomeView.url=welcome (&lt;b&gt;this is the name of a definition&lt;/b&gt;)

vetsView.class=org.springframework.web.servlet.view.tiles.TilesView
vetsView.url=vetsView (again, this is the name of a definition)

findOwnersForm.class=org.springframework.web.servlet.view.JstlView
findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp

여기서 볼 수 있듯이 ResourceBundleViewResolver 를 사용하면 여러 view 기술을 혼용할 수 있습니다.