본문 바로가기

카테고리 없음

Using FreeMarker with servlets (프리마커 설정법)


Using FreeMarker with servlets

기본적으로 FreeMarker을 웹 애플리케이션에 적용하는것은 다른 패키지를 설치하는것과 차이가 없다. FreeMarker은 Writer의 출력을 Template.process 메소드로 전달한다. 그리고 HttpServletResponse의 스트리밍 출력이나 콘솔출력 혹은 파일출력에 대해서 관여하지 않는다. FreeMarker은 서블릿이나 웹에 대해서 알지 못한다. 단지 자바 객체와 템플릿을 머지해서 텍스트를 출력해줄 뿐이다. 여기에서는 어떻게 웹 애플리케이션에 적용하고 빌드하는지에 대해서 설명한다.

아마도 당신은 프리마커가 이미 웹 애플리케이션에 적용 되어 있기를 희망할수도 있다. 많은 프레임워크가 Model2 아키텍처를 기반으로 연관되 있고, JSP페이지는 표현영역을 담당하게 된다. 그러한 프레임워크는 Apache Struts와 같은것을 예로 들수 있을 것이다.

Model2를 적용하기 위해 FreeMarker 사용하기 

많은 프레임워크는 HTTP 요청을 사용자가 정의한 action 클래스에 디스패치 시켜서 처리하는 전략을 따른다. 그리고 ServletContext, HttpSession 그리고 HttpServletRequest 에 데이터를 attributes로 저장하고, 그 요청 결과를 JSP페이지로 포워딩 해서 HTML 페이지를 생성할수 있도록 데이터를 전달하는 방식을 사용한다. 이것이 바로 말하는 모델 2 방식이다.

figure

프리마커는 JSP를 사용하는 대신에 FTL파일을 이용하도록 한다. 그러나 서블릿 컨테이너에(WAS)는 JSP 파일이 아니면 FTL파일을 가지고 무엇을 해야할지 알지 못한다. 그러므로 약간의 설정이 필요하다.

  1. freemarker.jar 파일을 WEB-INF/lib 디렉토리에 복사한다.

  2. WEB-INF/web.xml 파일에 다음과 같이 내용을 변경한다.

<servlet>
  <servlet-name>freemarker</servlet-name>
  <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
    
  <!-- FreemarkerServlet settings: -->
  <init-param>
    <param-name>TemplatePath</param-name>
    <param-value>/</param-value>
  </init-param>
  <init-param>
    <param-name>NoCache</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>ContentType</param-name>
    <param-value>text/html; charset=UTF-8</param-value> <!-- Forces UTF-8 output encoding! -->
  </init-param>
    
  <!-- FreeMarker settings: -->
  <init-param>
    <param-name>template_update_delay</param-name>
    <param-value>0</param-value> <!-- 0 is for development only! Use higher value otherwise. -->
  </init-param>
  <init-param>
    <param-name>default_encoding</param-name>
    <param-value>ISO-8859-1</param-value> <!-- The encoding of the template files. -->
  </init-param>
  <init-param>
    <param-name>number_format</param-name>
    <param-value>0.##########</param-value>
  </init-param>

  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>freemarker</servlet-name>
  <url-pattern>*.ftl</url-pattern>
</servlet-mapping>  

이제 FTL파일을 (*.ftl)을 JSP와 동일한 방법으로 사용할 수 있다. 물론 ftl 확장자 말고 다른것으로 지정할 수 있다.

#노트#

이것이 어떻게 동작할까? JSP들이 어떻게 동작하는지 확인해보자. 많은 서블릿 컨테이너는 *.jsp로 매핑된 URL패턴의 요청에 대해서 적절한 처리를 한다. 서블릿은 .jsp포 끝나는 URL 요청을 받고 내부적으로 Servlet로 컴파일을 수행하여 서블릿으로 동작하게 한다. FreemarkerServlet은 *.ftl로 매핑된 URL 패턴을 읽어들여 서블릿으로 변경하지 않고 Template를 빌드하고 내부의 process메소드를 호출하여 페이지를 생성하는 방식으로 동작한다.

예를 들어, 다음과 같은 내용의 스트럿츠 태그를 이용한 JSP파일을 대신해서 다음과 같이 작성할수 있을 것이다.

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

<html>
<head><title>Acmee Products International</title>
<body>
  <h1>Hello <bean:write name="user"/>!</h1>
  <p>These are our latest offers:
  <ul>
    <logic:iterate name="latestProducts" id="prod">
      <li><bean:write name="prod" property="name"/>
        for <bean:write name="prod" property="price"/> Credits.
    </logic:iterate>
  </ul>
</body>
</html>  

즉, FTL파일로 다음과 같이 대체해보자.

<html>
<head><title>Acmee Products International</title>
<body>
  <h1>Hello ${user}!</h1>
  <p>These are our latest offers:
  <ul>
    <#list latestProducts as prod>
      <li>${prod.name} for ${prod.price} Credits.
    </#list>
  </ul>
</body>
</html>  

주의

FreeMarker에서는 <html:form action="/query">...</html:form>은 단지 그냥 정적인 텍스트잎 뿐이다. 그래서 이것은 XML이나 HTML 마크업으로 인식해서 그대로 출력한다. JSP태그는 단지 프리마커의 디렉티브와 같다. 특별한 의미가 없다는 것이다. 그러므로 FreeMarker 문법을 이용하여 특수한 의미를 부여해야한다. JSP 문법이 아닌 <@html.form action="/query">...</@html.form> 이런식으로  프리마커 문법에서 ${...}을 바로 사용하지 않는다. FreeMarker에서는 ""를 붙여줘야한다.

잘못된 표현 :

<#-- WRONG: -->
<@my.jspTag color="${aVariable}" name="aStringLiteral"
            width="100" height=${a+b} />  

올바른 표현 :

<#-- Good: -->
<@my.jspTag color=aVariable name="aStringLiteral"
            width=100 height=a+b />  

만약 user과 lastestProduct를 작성한다면, 우선 템플릿에서 호출될 변수 이름을 찾으려는 시도를 할 것이다. (prod 처럼 JSP를 알고 있다면 page scope의 속성으로 지정할 것이다.) 만약 찾는것을 실패한다면 HttpServletRequest에 있는 이름을 찾을 것이다. 그리고 HttpSession과 ServletContext에서 그 이름을 찾을수 없을 것이다. FTL의 경우에는 FreemarkerServlet이 언급한 3개에 객체에서 데이터 모델을 빌드하기 때문이다. 프리마커는 데이터 모델에 대해서 매우 매우 유연하다. 만약 "name"라는 변수를 저장하길 원한다면 servletRequest.setAttribute("name", "Fred")라고 해주면 된다. 이것은 Model2의 로직을 프리마커가 채용하고 있음을 나타낸다.

FreemarkerServlet은 3개의 해시를 데이터모델에 저장한다. 그러므로 3개의 객체들을 바로 접근하여 이용할 수 있다. 이러한 해시 값은 Request, Session, Application(ServletContext와 동일)이다. 이것은 또한 HTTP 요청 파라미터를 RequestPrameters라는 이름의 해시로 접근할수 있도록 해준다.

FreemarkerServlet은 다양한 초기 파라미터들을 갖는다. 이것은 서로다른 디렉토리나 클래스패스, 혹은 웹애플리케이션에 연관된 디렉토리의 템플릿을 로드할때 설정할수 있다. 또한 템플릿의 캐릭터 셋을 설정할 수 있도록 해준다. 또한 특정 객체를 wrapper로 지정하여 사용할 수 있도록 할 수 있다.

FreemarkerServlet은 서브클리싱으로 특정 요구에 대해서 적절하게 배치할수 있는 기능을 제공한다. 만약 모든 템플릿을 위해서 당신의 데이터모델에 변수들을 추가해야할 필요가 있다면 preTemplateProcess()메소드를 오버라이드해서 템플릿이 데이터를 획득하기 전에 특정 변수들을 추가해 넣을수도 있다. 또한 Configuration에서 공유할 변수들을 글로벌하게 이용할 수 있도록 설정해 줄수 있도록 서블릿 서브클래스를 만들수 있다.

좀더 자세한 정보는 Java API문서를 읽어보라.

다른 웹 애플리케이션 resources로 부터 컨텐츠 인클루드 하기 

<@include_page path="..."/> 라는 디렉티브를 이용하여 FreemarkerServlet은 아픈  웹애플리케이션의 자원을 컨텐츠내에 삽입할 수 있다. 이것은 JSP페이지들을 프리마커 템플릿으로 통합하기 위해서 매우 유용하게 이용될 수 있다.

<@include_page path="path/to/some.jsp"/>  

is identical to using this tag in JSP:

<jsp:include page="path/to/some.jsp">  

#노트#

<@include_page ... > 은 <#include ...> 와 혼돈되지 않는다. 적어도 프리마커 템플릿은 서블릿 컨테이너를 추가하지 않고도 해당 jsp를 추가할 수 있다. <#include ...>가 있는 템플릿은 포함된 템플릿과 같이 자원을 공우한다. 데이터모델과 템플릿에 있는 변수를 함께 공유한다. 그러나 <@include_page ...> 은 HTTP요청 처리에 독립적으로 동작한다.

#노트#

몇몇 웹 애플리케이션 프레임워크는 이러한 처리를 위한 각각의 솔루션을 가지고 있어 이러한 방법과 다른 방법으로 처리하기도 한다. 그리고 어떤 웹 애플리케이션 프레임워크는 FreemarkerServer을 이용하지 않을수도 있다. 그래서 include_page가 동작하지 않을 수 있다.

경로는 상대 혹은 절대경로 다 이용할 수 있다. 상대경로는 현대 HTTP요청의 URL을 기준으로 상대 경로를 찾게된다. 절대경로는 현재 서블릿 컨텍스트에서 부터 경로를 찾아가게 된다. 현대 웹 애플리케이션에 외부로 부터 페이지를 인클루드 하지 않아도 된다. 어떠한 페이지든 인클루드 할 수 있다.

추가적으로 path 파라미터는 특정 처럼 파라미터 이름을 지정할수 있다. inherit_params라고 불리며 이것은 boolean값에 따라 동작한다. 이것은 HTTP요청 파라미터에 해당하는 파라미터가 존재하면 true로 설정되면 인클루드된 페이지에서 현재 요청된 파라미터를 사용할 수 있게 된다.

마지막으로 params라는 이름의 추가적인 파라미터를 지정할 수 있다. 이것은 인클루드 되는 페이지에 지정한 파라미터가 보이도록 한다. 아래 경우는 inherited 파라미터를 전달하고, 또한 특정 파라미터를 지정하여 지정된 이름으로 인클루드 되는 페이지에서 값을 이용할 수 있도록 한것이다. 파라미터 밸류는 hash값으로 반드시 정의 되어야 한다. 각 값은 스트링이나 스트링의 시퀀스가 되어야 한다.

<@include_page path="path/to/some.jsp" inherit_params=true params={"foo": "99", "bar": ["a", "b"]}/>  

이 예제는 path/to/some.jsp 페이지를 인클루드 하고, 현재 요청된 파라미터를 그대로 이용할 수 있도록 전달한다. 단 예외적으로 foo, bar라는 변수에는 각각 99와 "a", "b"값을 전달하고 있다. 이 케이스에서는 요청 파라미터에 foo와 bar이 이미 들어와 있다고 가정하고, 값을 변경한 경우의 예이다.

사실 스트링이 아닌 다른 형의 파라미터를 넘기고 싶을것이다. 그러한 경우 적당한 자바 객체로 변경하고 그것을 toString메소들르 이용하여 스트링으로 변경하여 전송하고 그것을 스트링으로 받아서 변경하는 방식으로 처리해야한다. 템플릿 레벨에서 이러한 전달된 파라미터를 변경하려면 포매팅을 이용하여 처리할 수 있다. ?string 이나 ?c를 이용하면 된다.

 

JSP 커스텀 태그를 FTL에서 이용하기

FreemarkerServlet은 JspTaglibs를 데이터모델에 해시값으로 입력할 수 있다. 그렇게 해서 JSP태그 라이브러리를 이용할 수 있는 것이다. JSP 커스텀 태그는 사용자 정의 디렉티브를 이용하여 접근할 수 있다. 예를 들어 JSP파일에 다음과 같은 Struts태그들이 있다면

<%@page contentType="text/html;charset=ISO-8859-2" language="java"%>
<%@taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>

<html>
  <body>
    <h1><bean:message key="welcome.title"/></h1>
    <html:errors/>
    <html:form action="/query">
      Keyword: <html:text property="keyword"/><br>
      Exclude: <html:text property="exclude"/><br>
      <html:submit value="Send"/>
    </html:form>
  </body>
</html>  

And this is the (near) equivalent FTL:

<#assign html=JspTaglibs["/WEB-INF/struts-html.tld"]>
<#assign bean=JspTaglibs["/WEB-INF/struts-bean.tld"]>

<html>
  <body>
    <h1><@bean.message key="welcome.title"/></h1>
    <@html.errors/>
    <@html.form action="/query">
      Keyword: <@html.text property="keyword"/><br>
      Exclude: <@html.text property="exclude"/><br>
      <@html.submit value="Send"/>
    </@html.form>
  </body>
</html>  

JSP 커스텀 태그는 JSP환경에서 수행되도록 작성되어 있다. 그것은 변수는 4개의 scope에 저장되었음을 가정하는 것이다. page, request, session, application 스콥이 그것이다. FTL은 이러한 4개의 스콥에 대한 표기법이 없다. 그러나 FreemarkerServlet은 커스텀 태그를 위해서 JSP 환경을 에뮬레이트 할수 있는 환경을 제공한다. 이것은 JSP의 beans의 스콥 영역과 FTL 변수사이에 매핑을 관리해주는 역할을 해준다. 커스텀 JSP태그를 위해서 request, session, application 스콥은 실제 JSP와 동일하게 작동하고, attrubutes는 javax.servlet.ServletContext, HttpSession, ServletREquest 객체의 속성과 매핑된다. FTL 에서 3개의 스콥은 데이터모델과 함께 이용된다. 페이지 스콥은 FTL의 글로벌 변수와 매핑된다. 그것은 global 디렉티브를 이용하여 생성할 수 있다. 이것은 JSP환경 에뮬레이터로 페이지 스콥의 커스텀 태그와 매핑되도록 해준다. 또한 JSP태그가 새 페이지 스콥 변소루 생성되면 global 디렉티브를 이용하여 생성한 것과 동일한 처리를 한다. 데이터모델의 변수들은 page스코프에 지정된 JSP태그 속성에는 보이지 않는다. 그러나 그것들은 데이터 모델에 request, session, application스콥에는 글로벌 하게 매핑된다. 단 page스콥만 매핑되지 않게 된다.

JSP페이지에서 모든 속성값을 인용할 수 있다. 이것은 파라미터가 string이든 boolean이든 number이든 상관하지 않는다. 그러나 사용자 정의 FTL 디렉티브와 간은 FTL템플릿에서의 커스텀 태그 접근은 FTL 문법을 이용해야한다. JSP룰을 이용해서는 안된다. attribute의 특정 값을 지정할때에는 = 은 FTL 표현으로 처리한다면 그 값은 boolean인경우나 숫자인경우 쿼테이션 마크를 해서는 안된다. <@tiles.insert page="/layout.ftl" flush=true/> 처럼 또한 문자형 값이 아닌데 문자를 쓰는경우 프리마커는 타입이 맞지 않는다는 에러를 던질것이다. <@tiles.insert page=layoutName flush=foo && bar/>

FreeMarker은 서블릿 컨테이너의 JSP 지원에 의존하지 않는다. 이것은 JSP태그 라이브러리를 이용할때에도 경량의 JSP 실행 환경을 이용할 뿐이다. 이를 이용하기 위해서 WEB-INF/web.xml 에 다음과 같이 추가만 해주면 JSP 실행 환경을 JSP태그 라이브러리를 이용하기 위해 제공해준다.

<listener>
  <listener-class>freemarker.ext.jsp.EventForwarding</listener-class>
</listener>  

JSP태그 라이브러리를 FreeMarker과 함께 이용할수 있게 되었다. 서블릿 컨테이너가 native Jsp를 지원하지 않는다면 웹애플리케이션이 JSP 1.2혹은 그 이후 버젼만으로도 javax.servlet.jsp.*패키지를 사용할수 있을 것이다. 만약 JSP 1.1 버젼이라면 다음과 같은 6개의 클래스를 WEB-INF/classes/... 디렉토리에 넣어주면 된다. javax.servlet.jsp.tagext.IterationTag, javax.servlet.jsp.tagext.TryCatchFinally, javax.servlet.ServletContextListener, javax.servlet.ServletContextAttributeListener, javax.servlet.http.HttpSessionAttributeListener, javax.servlet.http.HttpSessionListener. 그러나 주의할 것은 JSP 1.1은 JSP 1.2 태그 라이브러리와 같은 것들을 지원하지 않는다는 것이다.

참고문서 : http://www.freemarker.org/docs/pgui_misc_servlet.html