98..Etc/velocity2009. 3. 23. 15:10
반응형

Velocity의 Developer's Guide를 따라하면서 정리해본것 입니다.


Download(다운로드)

우선 Velocity 배포본을 다운로드 받는다.

그리고 Ant 를 다운받는다.


Dependencies(의존성)

개발을 위해서는 Java 2 Standard Edition SDK (Software Development Kit)이 필요하고,

Velocity를 실행시키기 위해서는 Java 2 Standard Edition RTE (Run Time Environment)이 필요하다.(물론 RTE 대신에 SDK를 사용하는것도 가능하다.)


Velocity는 다음 세가지의 패키지에 의존성을 가지고 있다.

Jakarta Commons Collections - 반드시 필요함.

Jakarta Avalon Logkit - 선택적이지만 Velocity에서 파일기반의 logging을 위해서는 필요하다.

Jakarta ORO - 선택적이지만 org.apache.velocity.convert.WebMacro 템플릿 변환 툴을 사용하기 위해서는 필요하다.

이 패키지들은 build/lib 폴더안에 넣거나 classpath에 추가하면 된다.


How Velocity Works(Velocity는 어떻게 움직이는가)

Velocity를 사용하여 어플리케이션이나 서블릿을 개발할 때는 보통 다음과 같은 과정을 거친다.


1. Velocity의 초기화

2. Context 객체의 생성

3. Context에 사용자의 데이터 객체(data objects)를 추가한다.

4. 템플릿을 선택한다.

5. 산출물(output)을 만들기 위해 템플릿과 사용자의 데이터를 병합(Merge)한다.


code로 이 과정을 표현하면 다음과 같다.

이 파일은 org.apache.velocity.app.Velocity.java 인데, 실제 자신이 어플리케이션을 개발할 때에도 이러한 패턴으로 작성하면 된다.

import java.io.StringWriter; 
import org.apache.velocity.VelocityContext;
import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.MethodInvocationException;
Velocity.init(); // 1.Velocity의 초기화  
VelocityContext context = new VelocityContext(); 
// 2.Context의 생성
context.put( "name", new String("Velocity") ); 
// 3. Context에 사용자의 데이터 객체(data objects)를 추가한다.


Template template = null;
try { 
template = Velocity.getTemplate("mytemplate.vm");
// 4. 템플릿을 선택한다.
} catch( ResourceNotFoundException rnfe ) {
// couldn't find the template
} catch( ParseErrorException pee ) {
// syntax error : problem parsing the template
} catch( MethodInvocationException mie ) {
// something invoked in the template
// threw an exception
} catch( Exception e ) {}
StringWriter sw = new StringWriter(); 
template.merge( context, sw ); 
// 5. 산출물(output)을 만들기 위해 템플릿과 사용자의 데이터를 병합(Merge)한다.
figure 1. velocity의 작동 과정의 예
 

The Context(컨텍스트)

Velocity에서 Context 라는것은 가장 핵심의 요소면서, 시스템의 부분들사이에서 데이터를

손쉽게 이동시킬 수 있는 일종의 컨테이너(Container)다.

즉, 이말은 Context가 실제 Java Layer와 Template Layer간의 데이터를 주고 받을 때, 그 운반체의 역할을 한다는 말과 같다.

Context는 Java의 Map과 같은 형태를 취한다. key-value의 쌍으로 구성되기 때문에,

Java에서 일반적으로 사용되는 Map을 다룰때와 같이 다루면 된다.


Velocity Template Language(밸로시티 템플릿 언어)

VTL은 Template에서 사용되는 Velocity 고유의 언어를 의미한다.

실제 Template파일을 열어서 코드를 보면 따로 설명을 하지 않아도 충분히 이해할 수 있을만큼 간단한 형태를 보여준다.

example중에서 app_example1의 example.vm을 열어보면 다음과 같은 코드를 볼 수 있다.


## This is an example velocity template

#set( $this = "Velocity")

$this is great! but it's so hard.

#foreach( $name in $list )
    $name is great!
#end

#set( $condition = true)

#if ($condition)
    The condition is true!
#else
    The condition is false!
#end   


figure 2. template의 예

 

 

References(참조형)

variables(변수) - Velocity에서 변수는 다음과 같이 $를 먼저 쓰고 그 뒤에 식별자를 적어주는 방식으로 사용한다.

$foo


property(특성) - 프로퍼티도 다음과 같이 $다음에 식별자를 쓰고, 마침표(.)후에 다시 식별자의 형태로 사용한다.

$foo.name


method(메소드) - 메소드도 역시 $다음에 식별자를 쓰고, 마침표(.)후에 호출할 메소드의 이름을 적는다.

$foo.getName()


Directives(지시형)

#set - reference의 값을 설정한다.

#if/elseif/else - 조건문 제어

#foreach - 반복문 제어

#include - velocity로 파싱(parsing) 되지 않은 파일의 출력

#parse - velocity로 파싱(parsing) 된 파일 출력

#stop - template 엔진의 정지

#macro - 반복적으로 사용할 vm 정의


Comment(주석)

## - 한줄짜리 주석

#* ... *# - 여러줄짜리 주석


The Examples(예제)
Velocity 배포본의 디렉토리 구조는 다음과 같다.

figure 3. velocity 배포본의 디렉토리 구조


일단 실제 Velocity의 소스를 볼 수는 없고 Velocity가 무엇인지 맛부터 보는것이 좋을것이다.

그래서 필요한것이 바로 examples를 이용하는 것이라고 볼 수 있겠다.

우선 앞서 다운받은 Ant가 설치가 되어있어야 한다. Ant의 설치는 별다른게 아니라

압축을 풀고, 자신이 원하는 위치에 이 Ant디렉토리를 위치시킨 다음에

classpath에 ANT_HOME의 이름으로 Ant디렉토리를 등록시켜주면 된다.

(물론 path에 %ANT_HOME%\bin도 추가해줘야한다.)


콘솔창을 이용하여, build 디렉토리로 이동해서

ant examples 명령어를 주면 무언가 작업이 일어난다.

(win98은 실행창에서 command를, nt,2000,xp는 cmd를 입력하면 콘솔창이 뜬다.)

이 명령어가 하는 일은 examples 폴더에 있는 모든 파일을 컴파일 해주는 역할을 한다.

우선 가장 손쉬운 예제부터 보도록 하자.

앞서 얘기가 나온 app_example1부터 살펴보자.



figure 4. 예제 app_example1의 디렉토리 구조


이 예제를 실행시켜보면 다음과 같은 화면이 출력된다.

 
figure 5. 예제 app_example1의 실행 결과
 
-- 이후 작성중 --
네이버

최초작성일 : 2005-02-22

최종수정일 : 2005-02-23

 

이번에는 웹어플리케이션 예제를 한번 분석해보려고 합니다.

다음번에는 이 예제를 조금 더 발전시켜보는 방향으로 해보죠

 

일단 velocity에서 제공해주는 예제중에서 forumdemo라는 예제를 분석해보도록 하겠습니다.

기능은 간단한 forum의 기능을 제공해주는 작은 어플리케이션입니다.

물론 DB는 사용하지 않습니다.

이 어플리케이션은 다음과 같은 폴더 구조를 가지고 있습니다.

 

 

우리가 중요하게 봐야할 곳은 template폴더의 vm들과 WEB-INF/web.xml, WEB-INF/conf/velocity.properties 그리고 소스파일들이지요.

 

template은 명칭 그대로 템플릿을 모아둔 폴더 입니다.

list.vm은 message들의 목록을 출력할 때 쓰이는 템플릿이고,

reply.vm은 해당 message에 reply할 때 사용되는 템플릿이며,

view.vm은 해당 message와 그 하위의 reply들을 화면에 출력할 때 사용되는 템플릿입니다.

 

일단 예제를 한번 돌려보면서 해보는게 좋겠지요?

이전에 ant examples를 했다면 아마도 forumdemo도 모두 컴파일이 되어 있을겁니다.

(안하셨다면 이전 게시물을 참고하시기 바랍니다.)

이 forumdemo폴더 자체를 tomcat이설치된폴더/webapp 밑으로 복사를 합니다.

(물론 ant를 이용하여 자동으로 deploy해도 되겠지만 지금은 일단 복사하는 방법을 선택합니다.)

 

tomcat을 기동하고, 브라우저창에 http://localhost:8080/forumdemo 를 입력하시면 다음과 같은 화면이 나옵니다. 아무거나 클릭을 해볼까요?

 

당황스럽게도 404에러, 즉 페이지를 찾을 수 없다는 에러가 나는데, 이것은 web.xml에서 servlet을 mapping시켜주지 않아서 생기는 현상입니다.

 

 

WEB-INF/web.xml 파일에 다음을 추가시켜 줍니다.

<servlet-mapping>
        <servlet-name>forum</servlet-name>
        <url-pattern>/servlet/forum</url-pattern>
</servlet-mapping>

 

자 tomcat을 재시작 시킨후에 다시 한번 아무 메뉴나 클릭해봅시다.

 

 

이번에는 500에러가 나타납니다. 왜그럴까요? 에러로그를 잘 살펴보시면 template을 가져오지 못했기 때문에 에러를  발생시킨것을 알 수 있습니다.

이것도 역시 WEB-INF/web.xml파일이 잘못되어 있어서 발생하는 현상입니다.

<init-param>
        <param-name>properties</param-name>
        <param-value>/WEB-INF/conf/velocity.properties</param-value>
</init-param>

를 다음과 같이 바꿔줍니다.

 

<init-param>
        <param-name>org.apache.velocity.properties</param-name>
        <param-value>/WEB-INF/conf/velocity.properties</param-value>
</init-param>

 

다시 tomcat을 재시작한 후에 클릭해보면 제대로 나오는것을 확인 할 수 있습니다.

일단 예제가 돌아가기 시작하고 있으니 이제 실제적으로 이 어플리케이션을 분석해보도록 하겠습니다.

 

이 어플리케이션은 모든 서블릿요청은 org.apache.velocity.demo.ControllerServlet 이 담당합니다.

get방식으로 parameter를 받아서 그에 해당하는 action을 org.apache.velocity.demo.action 하위의 클래스들로 실행시켜주는 것이지요.

 

ControllServlet.java

(import는 생략했습니다.)

 

public class ControllerServlet extends VelocityServlet

{
    private static String ERR_MSG_TAG = "forumdemo_current_error_msg";

   
    /**
     *  template path에 대한 정보를 가져오기 위한 메소드 입니다.
     */

    protected Properties loadConfiguration(ServletConfig config )
        throws IOException, FileNotFoundException
    {
        String propsFile = config.getInitParameter(INIT_PROPS_KEY);
       
        /*
         *  webapp root에 대한 절대경로를 통해서 template파일의 위치를 찾습니다.

         *  tomcat과 같은 servlet 2.2 이상의 지원하는 container에서 가능하다고 하네요.
         */

        if ( propsFile != null )
        {
            String realPath = getServletContext().getRealPath(propsFile);
       
            if ( realPath != null )
            {
                propsFile = realPath;
            }
        }
       

       // 설정파일을 load합니다. 500에러가 발생했던 이유가 여기에 있는거지요.

       // 내부적으로는 org.apache.velocity.properties로 정의되어 있었는데,

       // web.xml에서는 properties로 정의되어 있었기때문에 찾지못해서 에러가 발생했던 것이죠.
       Properties p = new Properties();
       p.load( new FileInputStream(propsFile) );
     

       /**
        *  template의 경로와 log의 경로를 설정합니다.

        */

       // 설정파일에 정의되어있는 resource loader(차후에 다시 얘기나옵니다.)의 경로(path)를 찾아서 실제 경로로 바꾸어 다시 정의합니다. [file.resource.loader.path = /template]

       String path = p.getProperty("file.resource.loader.path");

       if (path != null)
       {
           path = getServletContext().getRealPath( path );
           p.setProperty( "file.resource.loader.path", path );
       }
       

       // 역시 설정파일에 정의되어있는 runtime.log의 경로를 실제 경로로 바꾸어 다시 설정합니다. [runtime.log = /forumdemo_velocity.log]
       path = p.getProperty("runtime.log");

       if (path != null)
       {
           path = getServletContext().getRealPath( path );
           p.setProperty("runtime.log", path );
       }

       return p;
    }


    /**
     * 서블릿을 제어하기 위해서 VelocityServlet의 handleRequest메소드를 확장하여 사용합니다.

     * @param VelocityServlet에서 생성된 Context

     * @return template
     */

    public Template handleRequest( Context ctx )
    {
        HttpServletRequest req = (HttpServletRequest)ctx.get(VelocityServlet.REQUEST);
        HttpServletResponse resp = (HttpServletResponse)ctx.get(VelocityServlet.RESPONSE);
        Template template = null;
        String templateName = null;
       
        HttpSession sess = req.getSession();
        sess.setAttribute(ERR_MSG_TAG, "all ok" );

        try
        {
            // processRequest를 통해서 파라미터의 command(list, reply 등등)에 해당하는

            // template의 이름을 받아옵니다.
            templateName = processRequest( req, resp, ctx );
            // template이름을 이용하여 template을 가져옵니다.
            template  = getTemplate( templateName );
        }
        catch( ResourceNotFoundException rnfe )
        {
            String err = "ForumDemo -> ControllerServlet.handleRequest() : Cannot find template " + templateName ;
            sess.setAttribute( ERR_MSG_TAG, err );
            System.out.println(err );
        }
        catch( ParseErrorException pee )
        {
            String err = "ForumDemo -> ControllerServlet.handleRequest() : Syntax error in template " + templateName + ":" + pee ;
            sess.setAttribute( ERR_MSG_TAG, err );
            System.out.println(err );
        }
        catch( Exception e )
        {
            String err = "Error handling the request: " + e ;
            sess.setAttribute( ERR_MSG_TAG, err );
            System.out.println(err );
        }

        return template;
    }
   
    /**
     * command패턴을 이용하여 request에서 넘어온 command에
 해당하는 template의 이름을 반환합니다.

     * 각 command는 org.apache.velocity.demo.action의 하위의 클래스들에 구현되어 있습니다.

     * exec메소드에 각 command가 담당한 부분에 대한 처리가 들어있습니다.

     * 메세지들을 context에 담는다던지하는 것들 말이지요...
     * @param the request
     * @param the response
     * @param the context
     * @return 사용할 템플릿의 이름
     */
    private String processRequest( HttpServletRequest req, HttpServletResponse resp, Context context )
     throws Exception
    {
        Command c = null;
        String template = null;
        String name = req.getParameter("action");
       
        if ( name == null || name.length() == 0 )
        {
            throw new Exception("Unrecognized action request!");
        }
               
        if ( name.equalsIgnoreCase("list") )
        {
            c = new ListCommand( req, resp);
            template = c.exec( context );
        }
        else if ( name.equalsIgnoreCase("post") )
        {
            c = new PostCommand( req, resp );
            template = c.exec( context );
        }
        else if (  name.equalsIgnoreCase("reply") )
        {
            c = new ReplyCommand( req, resp );
            template = c.exec( context );
        }
        else if (  name.equalsIgnoreCase("postreply") )
        {
            c = new PostReplyCommand( req, resp );
            template = c.exec( context );
        }
        else if ( name.equalsIgnoreCase("view") )
        {
            c = new ViewCommand( req, resp );
            template = c.exec( context );
        }
        return template;
    }


    /**
     *  에러가 발생했을때 브라우저 상에 출력하기 위한 메소드입니다.

     *  VelocityServlet의 error메소드를 override하여 사용합니다.

     */
    protected  void error( HttpServletRequest request, HttpServletResponse response, Exception cause )
        throws ServletException, IOException
    {
        HttpSession sess = request.getSession();
        String err = (String) sess.getAttribute( ERR_MSG_TAG );

        StringBuffer html = new StringBuffer();
        html.append("<html>");
        html.append("<body bgcolor=\"#ffffff\">");
        html.append("<h2>ForumDemo : Error processing the request</h2>");
        html.append("<br><br>There was a problem in the request." );
        html.append("<br><br>The relevant error is :<br>");
        html.append( err );
        html.append("<br><br><br>");
        html.append("The error occurred at :<br><br>");

        StringWriter sw = new StringWriter();
        cause.printStackTrace( new PrintWriter( sw ) );

        html.append( sw.toString()  );
        html.append("</body>");
        html.append("</html>");
        response.getOutputStream().print( html.toString() );
    }
}

 

네이버

최초작성일 : 2005-02-23

최종수정일 : 2005-02-25


이번엔 command 클래스들과 om패키지를 조금 살펴볼까 합니다.

이전에 말했듯이 command의 구현체들은 org.apache.velocity.demo.action 패키지안에 있습니다.



command.java의 경우에는 하위 command들을 위한 abstract class(추상클래스)입니다.

우선 ListCommand를 살펴보겠습니다. 생각보단 짧죠?


public class ListCommand extends Command
{
    /** template의 이름을 정의해 둡니다. */
    public static final String LIST = "list.vm";

    // 나중엔 이 변수에 담길 template의 이름을 DB에서 가져오거나 parameter로 받으면 되겠죠?
   

    // 생성자겠지요?
    public ListCommand( HttpServletRequest req, HttpServletResponse resp )
    {
        super( req, resp );
    }
   
    /**
     * 메세지들을 가져와서 context에 담습니다. 이때 메세지들은 Vector의 형태로 묶여 있게 됩니다.
     */
    public String exec( Context ctx )
    {
        Object[] list = ForumDatabase.listAll(); // 메세지를 가져오는 부분입니다.
       
        if ( list == null || list.length == 0 )
        {
            ctx.put("hasMessages", Boolean.FALSE );   
        }
        else
        {
            ctx.put("hasMessages", Boolean.TRUE );
            ctx.put("listall", list ); // 메세지가 존재하면 해당 메세지 list를 context에 담습니다(put)
        }
       
        return LIST; // 그리고 해당하는 템플릿의 이름을 반환해 줍니다.
    }
}


간단하지 않습니까?

사실상 이녀석이 하는 일은 context에 db클래스에서 뽑아낸 정보들을 담는역할밖에 없으니까요.

이 역할자체는 나머지 command 클래스들에서도 공통적으로 나타납니다.

(당연한거겠지요-_-;;)

PostCommand.java를 한번 살펴보겠습니다.

다른곳이라곤 exec메소드 뿐입니다.(이 역시 당연한거겠죠?)


public String exec( Context ctx )
    {

        // 변수에 넘겨받은 값들을 정의합니다.
        String name = request.getParameter("name");
        String subject = request.getParameter("subject");
        String email = request.getParameter("email");
        String content = request.getParameter("content");
       

        // 메세지 객체를 만들고 앞에서 설정한 변수를 이 객체에 담습니다.
        Message message = new Message();
        message.setName( name );
        message.setSubject( subject );
        message.setEmail( email );
        message.setContents( content );
       

        // Database 클래스에 메세지객체를 저장합니다.
        ForumDatabase.postMessage( message );
       

        // 메세지를 저장한 후에 보여줄 화면을 List로 정의하고 있기 때문에

        // list를 생성하여 context에 담습니다.

        Object[] list = ForumDatabase.listAll();
       
        ctx.put("listall", list );
        ctx.put("hasMessages", Boolean.TRUE );
        // 그리고 ListCommand의 List 즉 list.vm을 반환해줌으로써 보여줄 화면을 list.vm으로 설정하게 됩니다.
        return ListCommand.LIST;
    }


ListCommand에서는 말씀드렸던것처럼 db클래스에서 데이터를 가져오는 역할을 했습니다.

PostCommand의 경우에는 메세지를 작성할 때 사용하는 클래스인데, 기본적으로

parameter로 받은 정보들을 message객체에 set하고 그것을 db클래스에 저장하는 역할을 하고 있습니다.


나머지 클래스들도 같은 방법으로 동작하고 있기 때문에 추가적인 설명은 하지 않겠습니다.

Command클래스의 이름으로 대충 파악하실수 있을꺼라 생각합니다.


org.apache.velocity.demo.om 패키지에 있는 두 클래스가 실제적으로 persistence layer를 맡고 있습니다.

Message 클래스는 설명이 필요 없는 Bean 클래스입니다.(실제 소스를 보시면 아시겠지만 set/get만을 하고 있는 클래스입니다)

한가지 특이할만한 점이라고 하면, 한 메세지에 달리는 덧글들은 해당하는 메세지가 가지고 있다는 것입니다.

이는 소스에서 살펴보실수 있듯이 Reply을 Vector형태로 가지고 있기 때문에 가능하게 되는 것입니다.

바로 이 부분이죠. private Vector replies = null;


ForumDatabase는 HashTable에 사용자가 작성한 메세지를 key/value 방식으로 담고 있습니다.

따라서 클래스가 하는 작업은 HashTable에 메세지를 넣고, 빼고, 리스트 정도가 있습니다.


ForumDatabase.java

public class ForumDatabase
{
    private static Hashtable messages = new Hashtable(); // 메세지를 저장할 HashTable
    private static int nextId = 0; // key값입니다. 0부터 시작합니다.
    private static ForumDatabase me = null;
   
    /** 생성자 */
    private ForumDatabase()
    {}
   
    /**
     * 신규 메세지 작성

     */
    public static synchronized void postMessage( Message message )
    {
        Integer nextNumber = new Integer( nextId++ ); // 기존의 key 값에 +1을 해줍니다.

        message.setId( nextNumber ); //  메세지의 key값으로 설정합니다.
        messages.put( nextNumber,  message ); // HashTable에 key와 message를 담습니다.
    }
   
    /**
     * 저장되어 있는 모든 메세지들의 리스트를 반환합니다.

     */
    public static Object[] listAll()
    {
        return messages.values().toArray();
    }
   
    /**
     * 특정한 한 메세지를 가져옵니다.

     */
    public static synchronized Message getMessage( String index )
    {

        // HashTable에서 넘겨받은 key으로 해당하는 메세지를 찾아서 반환합니다.
        return (Message)messages.get( new Integer( index ) );
    }
   
    /**
     * 메세지에 덧글을 추가합니다.
     */
    public static synchronized void postReply( Message reply, String parent )
    {
        Message thread = getMessage( parent );
        thread.addReply( reply );
    }

Posted by 1010