by 허태명(tmhuh)

소개#

WebWork은 Jason Carreira, Pat Lightbody, Mike Cannon-Brookes, Hani Suleiman 등이 포함되어 있는 OpenSymphony 팀이 개발한 모델 2 MVC 웹 프레임워크이다. WebWork은 현재 1.x 릴리즈 버젼으로 이미 상당한 명성을 얻었으며, OpenSymphony 팀은 2.x 버젼을 개발하고 있다. 2.x 버젼은 베타 버젼이 릴리즈 되었다.(역자 주: 2004. 2월 2.0 정식버젼이 릴리즈되었음)

이 글은 WebWork 2.x 를 사용하여 wafer 웹블로그 어플리케이션을 개발하는 과정을 보여주고, WW2의 많은 기본적인 기능들을 다룰 것이다. 지난 번에 필자는 TheServerSide에 Maverick 웹 프레임워크로 Wafer 웹블로그를 개발하는 방법에 대해 기고했다. 필자가 Maverick을 선택한 이유는 Maverick이 "스트럿츠보다 더 가볍다"고 알려졌기 때문이었고, wafer 어플리케이션을 개발한 후에 필자는 이 점에 동의했고 매우 기뻤다. 이번에 WebWork2를 선택한 이유는 IoC(Inversion of Control), 인터셉터 등과 관련해서 WebWork2가 얻고 있는 과대 선전(hype?)의 진실여부에 대해 궁금했기 때문이다. 그리고 이번에 필자는 훨씬 더 감명받았다.

WebWork2의 개괄적인 요약#

WebWork2는 IoC, 인터셉터, OGNL EL(Expression Language)과 같은 뛰어난 기술을 사용하는 모델 2 MVC 웹 프레임워크이다. WW2는 추상 클래스 대신에 인터페이스로 설계되어서 여전히 강력하게 WW2의 기능을 활용하면서 여러분의 솔류션을 프레임워크에 느슨하게 결합하게 해준다 . WW2는 J2EE로부터 독립되어 있어서 예전에는 결코 불가능했던, 여러분의 솔류션을 부드럽게 백엔드 솔류션과 연동하는 것을 가능하게 해준다.

WebWork2 베타 릴리즈는 무엇이 다른가?#

실제로 매우 많은 것이 틀리다. 핵심 MVC 컴포넌트는 WebWork에서 분리되서 웹과 독립적인 Generic command pattern 프레임워크인 Xwork이라는 프로젝트로 옮겨졌다. 또한 Action 상속 클래스가 실행될때 전, 후처리를 수행할 수 있는 인터셉터가 추가되었고, 인터셉터는 Action 클래스에 독립적인 클래스로 끌어내졌다; 그러나 여전히 Action 클래스와 그 환경에 접근할 수 있다. UI 검증은 이제 Action 클래스 내부가 아니라 XML 파일을 통해서 이루어진다; 하지만 이 기능은 필요하다면 아직 사용할 수 있다. 그리고, OGNL EL과 IoC 기능이 추가되었다.

시작하기#

이제 우리의 프로젝트를 설정해 보자. 프로젝트를 설정하는 것은 듣기에는 쉬워 보이지만 때때로 그렇게 단순하지 않다. 어떤 파일이 필요한지, 어떤 디렉토리 구조를 사용할 것인지, Ant 빌드 파일을 어떻게 구성할 것인지는 새로운 프레임워크로 프로젝트를 시작할때 늘 도전거리가 된다. 불행하게도 WW2는 이러한 점에 대해 도움이 될 만한 것을 그다지 많이 제공하지 않는다. 그러나 다른 프레임워크도 마찬가지이다. 그래서 가장 쉬운 방법은 필자가 이전에 wafer weblog application 때 사용했던 것과 마찬가지의 유사한 J2EE 구조를 생성하는 것이다. 마침 이 글을 쓸 때 소스파일, 설정파일, jsp, html, lib 파일 등을 위한 완전한 프로젝트 디렉토리 구조와 같이 여러분이 시작하는데 필요한 거의 모든 것을 포함하여 WW2 프로젝트를 생성해주는 megg 라는 깔끔한 툴이 릴리즈 되었다. megg는 심지어 JUnit 테스트가 되어 있는 간단한 HelloWorld 애플리케이션과 여러분의 웹 애플리케이션 이름으로 되어 있는 완전한 기능을 하는 Ant 빌드 파일까지 생성해 준다!

설정#

스트럿츠와 친숙한 사람들을 위해 최초의 시작점은 설정 부분이다. 다른 많은 프레임워크와 마찬가지로 WW2는 action과 output의 흐름을 컨트롤하는 xml 파일을 가지고 있다. 다음은 wafer weblog xwork.xml 의 일부분이다:

<action name="RegisterAction"
class="com.flyingbuttress.wafer.actions.RegisterAction">
            <result name="input" type="dispatcher">
                <param name="location">/register.jsp</param>
            </result>
            <result name="success" type="chain">
                <param name="actionName">LoginAction</param>
            </result>

            <interceptor-ref name="defaultStack"/>
            <interceptor-ref name="validation"/>
            <interceptor-ref name="workflow"/>
   </action>

구성은 비교적 명백하다. action 엘레먼트의 name 애트리뷰트 값은 그 action에 대한 레퍼런스 이름이고 이것은 대부분 /HelloWorld.do와 같은 URL로 사용될 것이다. action 포워딩되는 view가 어떤 종류인지 정의하는 result 엘레먼트는 name, type 애트리뷰트를 가지고 있다. name은 input, success, error 등등의 Action 클래스의 리턴 값에 매핑되고, type은 이 action이 진행되는 view의 종류를 정의한다. 아래의 현재 지원되는 type의 목록을 참조하라.

Dispatcher - JSP 포워딩 Redirect - 리다이렉팅 Velocity - 벨로서티 템플릿 뷰 Chain - 또 다른 Action 클래스로의 포워딩 만약 Excel 파일 출력과 같은 result type이 필요하다면, Result 인터페이스를 구현하고 webwork-default.xml 파일의 가능한 result type 목록에 이를 추가함으로써 쉽게 여러분 자신의 result type을 만들 수 있다.

인터셉터#

논리적으로 다음에 다뤄야할 부분은 Action 클래스이다; 그러나 인터셉터의 개념에 대해 설명하지 않고 Action 클래스에 대해 다루는 것은 거의 불가능하다. WW2에서 이루어지는 모든 작업의 대부분이 인터셉터를 통해 이루어진다. 개인적으로 필자는 다른 프레임워크와 WW2를 구분해주는 가장 핵식점인 요소 중의 하나가 인터셉터라고 생각한다. 표면적으로 인터셉터는 서블릿 필터와 매우 유사하다. 어떤 이들은 이를 "Practical AOP"에 비교하기도 한다. 이름과 상관없이, 이 기능은 매우 훌륭하다.

인터셉터는 action이 실행되기 전, 실행된 후, 또는 양쪽 모두에 호출될 수 있다. 인터셉터는 Action 클래스와 실행환경에 대해 완전하게 접근 가능한 능력을 가짐으로써, Action 클래스의 메소드를 호출하거나 실행환경에 대해 원하는 작업을 할 수 있다. 이 두가지에 대한 좋은 예는 TimerInterceptor 가 될 수 있을 것이다. 타이머 인터셉터는 action이 실행되기 전에 시간을 저장하고 action의 실행이 종료된 후에 다시 시간을 저장해서 두 시간의 차를 구함으로써 action의 실행 시간을 계산할 수 있다. 더 이상 불쌍한 수작업의 프로파일링은 필요 없다!

인터셉터의 또 다른 즐거움은 인터셉터 스택을 쉽게 설정할 수 있다는 것이다. xwork.xml의 모든 <action> 태그는 그 action에 관련된 하나 이상의 <interceptor-ref> 태그를 가질 수 있다. wafer weblog 애플리케이션의 "defaultStack"이 참조하는 것과 같이 여러분은 하나 이상의 인터셉터를 호출하기를 원할 것이기 때문에, 가장 좋은 방법은 인터셉터의 스택을 참조하는 것이다. 주의: 스택의 순서는 매우 중요하다; 인터셉터는 설정 파일에 정의한 순서대로 호출되기 때문에 어떤 인터셉터가 이미 호출된 다른 인터셉터에 의존한다면 스택의 순서를 정확하게 지켜줘야 한다! 인터셉터의 전체 목록은 webwork-default.xml 을 참조하라.

webwork1.gif

스트럿츠도 http://struts.sourceforge.net/saif/index.html에서 애드-온으로 인터셉터와 유사한 것을 가지고 있다; 어쨌거나 많은 사람들이 이것을 인터셉터의 개념을 가지고 있는 것으로 여긴다. 대개 이것은 인터셉터와 비슷한 기능을 제공하지만 이것이 스트럿츠 배포판에 포함되려면 아직 시간이 더 걸릴 것이다.

여러분 자신의 인터셉터를 만들기#

충분히 얘기했으니 이제 인터셉터를 하나 만들어 보자! 모든 인터셉터는 기본적으로 init, destroy, intercept 3개의 메소드를 가지고 있는 Interceptor 인터페이스를 구현해야 한다. 필자는 before, after 메소드를 구현한 AroundInterceptor 클래스를 상속받아서 인터셉터 클래스를 작성했다. Action을 실행하기 위해 유저의 로그인을 요구하는 인터셉터를 만들어 보자. 이 인터셉터는 Action이 실행되기 전에 세션에 user 객체가 있는지 검사하고 user 객체가 없다면 로그인 화면으로 리다이렉트시키면 될 것이다.

public class AuthorizeInterceptor extends AroundInterceptor {
  private static final Log log = LogFactory.getLog(LoggingInterceptor.class);
  private boolean loggedIn = false;

  protected void before(ActionInvocation invocationthrows Exception
  {
    User u = null;
    ActionContext ctx = ActionContext.getContext();
    Map session = ctx.getSession();
    u = (User)session.get("user");

    if(u == null)
    {
      log.info("User not logged in");
      loggedIn = false;
    }
    else
      loggedIn = true;
  }

  protected void after(ActionInvocation invocation, String resultthrows Exception
  {
    log.info("After");
  }

  public String intercept(ActionInvocation invocationthrows Exception {

    before(invocation);
    if(loggedIn == false)
      return Action.LOGIN;    // send em back to the login screen
    else
    {
      String result = invocation.invoke();
      after(invocation, result);

      return result;
    }
  }
}

before 메소드는 action이 실행되기 전에 호출되고 유저가 로그인 했는지 세션을 검사해서 로그인 하지 않았다면, xwork.xml 파일의 <global-results> 엘레먼트에 정의되어 있는 "login"이라는 전역 값을 리턴한다. 다음으로 해야 할 일은 xwork.xml 파일에 우리가 만든 인터셉터를 정의하는 것이다.

<interceptors>
<!-- custom created Interceptor for checking if a user has already logged -->
  <interceptor name="login"
          class="com.flyingbuttress.wafer.interceptor.AuthorizeInterceptor"/>
</interceptors>

이제 AuthorizationInterceptor가 완성되었다. 로그인을 필요로 하는 모든 action 클래스를 위해, 여러분이 해야 할 일은 아래와 같이 action config에 이 인터셉터를 참조하도록 하기만 하면 된다.

<action name="ShowCommentsAction" class="com.flyingbuttress.wafer.actions.ShowCommentsAction">
            <result name="success" type="dispatcher">
                <param name="location">/comments.jsp</param>
            </result>
            <interceptor-ref name="login"/>
            <interceptor-ref name="defaultStack"/>
</action>

여러분은 또한 "workflow" 인터셉터가 action 클래스가 실행되기 전에 validate() 메소드를 호출하는 것과 유사하게 execute 메소드의 실행 전이나 후에 action 클래스의 어떤 메소드를 호출하는 인터셉터를 만들 수도 있다. 이 개념은 여러분의 모든 action 클래스가 execute 메소드를 실행하기 전에 init() 메소드를 호출하게 하는 식으로 적용할 수 있다.

인터셉터의 단점#

필자는 인터셉터를 사랑한다. 하지만 인터셉터는 성공적으로 작동하게 하는 것이 가장 어려운 부분 중의 하나이다. 문제는 어떤 인터셉터는 작동하기 위해 다른 인터셉터에 의존하기 때문에 action에 인터셉터를 제대로 정의했는지 또한 인터셉터의 순서가 제대로 맞게 되어 있는지 확인하는 것이 매우 중요하다는 것이다. 이를 단순하게 하기 위해 필자는 defaultStack 라고 불리는 오직 1개의 표준 인터셉터 스택만 참조했다. 그 외에 다른 인터셉터들은 <action/> 태그 내에 일일이 선언해서 참조했다. WW2의 베테랑들은 이것이 비효율적이라고 하지만, 필자는 이 방법이 모든 것을 명료하게 유지하는데 크게 도움이 됐다고 생각한다.

Action Classes#

개발자는 실제 개발할 때 대부분 Actions, Controllers, Commands들을 다룬다. WW2에서 이것들은 Action이라고 불리고 기본적으로 두 가지 종류가 있다: 그것은 Field Driven과 Model Driven이다. Field Driven Action은 Controller-as-Model 스타일로 생각하라; 이러한 방식은 아마도 그다지 크지 않은 애플리케이션일 경우 가장 적절한 선택일 것이다. wafer weblog 애플리케이션의 대부분이 이 모델을 사용해서 만들어 졌다.

또 다른 타입은 Model Driven이다. 이 방식은 모델이 그 자체의 POJO 이다. 이것은 더 좋은 코드 재사용을 지원하고, 대규모 애플리케이션일 때 적당하다. 위에서 언급한 타입에 상관없이 Action 클래스를 정의하기 위해서는 ActionSupport 클래스를 상속받던지 Action 인터페이스를 구현해야 한다. UI 에러 핸들링과 로깅 같은 도움이 되는 모든 기능때문에 필자는 ActionSupport를 상속받아서 사용했다. Field Driven Action 클래스인 RegisterAction.java 를 작성해 보자.

public class RegisterAction extends ActionSupport {
  String username, email, firstname, lastname, password;
  private User tempUser;

  public String getUserName() {
    return username;
  }

  public void setUserName(String username) {
    this.username = username;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getFirstName() {
    return firstname;
  }

  public void setFirstName(String firstname) {
    this.firstname = firstname;
  }

  public String getLastName() {
    return lastname;
  }

  public void setLastName(String lastname) {
    this.lastname = lastname;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String execute() throws Exception {

    if (hasErrors())
      return INPUT;
    else
    {
      tempUser = WebLogSystem.getUserStore().create(this.username,this.password );

      tempUser.setFirstName(this.getFirstName());
      tempUser.setLastName(this.getLastName());
      tempUser.setEmail(this.getEmail());
      tempUser.save();
      return SUCCESS;
    }
  }

  /**
   * Do business logic validation by checking to see if the user entered is
   * already in the database.
   */
  public void validate()
  {
    LOG.info("Validating the registration page");
    try{

      if(WebLogSystem.getUserStore().verify(this.username))
      {
        this.addFieldError("Username""Someone already has that name");
      }
    }
    catch(Exception e)
    {e.printStackTrace();
    }

  }
}

여러분이 구현해야하는 메소드는 execute() 메소드 하나 뿐이다. Execute는 action이 실행될때 마다 호출되고 String 값을 리턴한다. Action 인터페이스에는 success, input, none, error 과 같은 값이 디폴트로 정의되어 있고, 이 값들은 xwork.xml 파일의 result name 애트리뷰트에 직접적으로 매핑되어 있다.

검증#

WW2는 UI와 데이타 검증 두 가지를 지원한다.UI 검증은 기본적으로 폼의 입력값의 타입과 범위가 맞는지 검사한다. 예를 들어 숫자 필드가 정말로 숫자인지 또는 날짜값이 유효한 날짜인지 등을 검사하는 것이다. 데이타 검증은 데이터베이스 검색 등을 통해 주어진 값이 유효한지 검사하는 것을 말한다. 데이타 검증은 우편번호 검증과 같이 단순히 숫자가 주어진 범위 안에 있는지 검사하는 것만으로 충분치 않아서 실제 유효한 우편번호인지 검사해야 하는 때 사용될 것이다.

User Interface 검증#

UI 검증은 XML 파일을 통해 이루어 진다. 이 XML 파일은 검증을 수행하려는 Action 클래스와 같은 패키지에 <ActionClassName>-validation.xml으로 만들어서 정의한다. 다음은 RegisterAction-validation.xml의 예이다.

<validators>
    <field name="userName">
        <field-validator type="requiredstring">
            <message>You must enter a value for username</message>
        </field-validator>
    </field>
    <field name="email">
        <field-validator type="requiredstring">
            <message>You must enter a value for email</message>
        </field-validator>
    </field>
    <field name="email">
        <field-validator type="email">
            <message>You must enter a valid email</message>
        </field-validator>
    </field>
    <field name="firstName">
        <field-validator type="requiredstring">
            <message>You must enter a value for first name</message>
        </field-validator>
    </field>
    <field name="lastName">
        <field-validator type="requiredstring">
            <message>You must enter a value for last name</message>
        </field-validator>
    </field>
    <field name="password">
        <field-validator type="requiredstring">
            <message>You must enter a value for password</message>
        </field-validator>
    </field>
</validators>

아래는 UI 검증이 실패한 화면의 스크린 샷이다.

webwork2.gif

WW2는 날짜, E-mail, int, string 등과 같은 많은 필드 발리데이터를 지원한고, 모든 유효한 발리데이터들은 validator.xml 파일에 정의되어 있다; 그러나, 추가로 필요하다면 Validator 인터페이스를 구현하고 validator.xml 파일에 레퍼런스를 추가함으로써 여러분 자신의 필드 발리데이터를 만드는 것은 쉬운 일이다. UI 검증은 "validation" 인터셉터에 의해 호출되므로, UI 검증을 실행하기 위해서는 wafer weblog의 xwork.xml 파일에 있는 RegisterAction과 같이 여러분의 action 설정에 "validation" 인터셉터를 참조해야 한다.

데이타 검증#

데이타 검증은 아마도 대개 개발자가 "이것이 유효한 우편번호인가? 데이터베이스에 질의해보자."와 같은 시나리오를 검사하는 코드를 작성하는 것을 의미할 것이다. 데이타 검증을 하기 위해서는 Action 클래스에 인자가 없는 validate() 메소드를 추가하고 그 안에 필요한 모든 기능들을 작성하면 된다. 그 다음에 Action 클래스에 Validateable 인터페이스를 구현하고 "workflow" 인터셉터를 참조하면 된다. 이 인터셉터는 Action 클래스의 execute 메소드가 실행되기 전에 컨텍스트에, 필요한 어떤 에러라도 추가할 수 있도록 validate() 메소드를 호출할 것이다.

Inversion of Control#

IoC는 클래스와 컴포넌트 간의 느슨한 결합을 지원하는 디자인 패턴이다. 현재는 코딩할 때 작동하기 위해 다른 클래스에 의존하는 클래스들이 있고 이 의존성은 클래스들을 긴밀하게 결합시킨다. 그러면 왜 IoC가 흥미로운가? IoC는 인터페이스와 구현을 통해 클래스들을 분리시킴으로써 좋은 아키텍쳐 디자인을 촉진시키고 컨테이너가 여러분의 컴포넌트의 라이프사이클을 관리하도록 해 준다.

이 개념에 대해 설명하는 가장 좋은 방법은 예제를 보여주는 것일 것이다. 여러분의 회사가 인간과 외계인이 자신의 몸무계를 측정할 수 있는 체중계를 생산한다고 해 보자. 이 체중계는 지구, 금성, 화성에서 판매될 것이다. 문제는 행성마다 중력이 다르다는 것이다. 따라서 지구의 파운드 단위로 정확한 자신의 몸무계를 알 수 있도록 보장하기 위해, 체중계는 이러한 요구를 수용할 수 있도록 유연성이 있어야만 할 것이다. IoC 기능을 위해 필요한 요소는 다음과 같다:

components.xml (IoC config file) Scale.java (Interface for all the components) ScaleAware.java (Interface for Action class) MarsScaleImpl.java (component) VenusScaleImpl.java (component) EarthScaleImpl.java (component) ScaleAction.java (Action class) 먼저 component.xml을 살펴 보자

<components>
    <component>
        <scope>application</scope>
        <class>com.flyingbuttress.scale.MarsScale</class>
        <enabler>com.flyingbuttress.scale.ScaleAware</enabler>
    </component>
</components>

이제 컴포넌트의 영역을 정의할 것이다. Action 클래스는 위의 MarsScale 클래스에 의존성을 가지고 있는 ScaleAware 인터페이스를 구현할 것이다. 아래의 실제 코드를 살펴 보자:

public class ScaleAction implements Action, ScaleAware
{
  private Scale scale;

  public void setScale(Scale scale)
  {
    this.scale = scale;
  }
  public String execute() throws Exception
  {
    System.out.println("The weight of you is:" + scale.getWeight());
    return SUCCESS;
  }
}

이제 컨테이너는 이 action 클래스가 ScaleAware를 구현한다는 것을 알고 있다; 그러므로, 컨테이너는 setScale 메소드를 호출하고 인터페이스를 통해 구현된 클래스를 전달할 것이다. 현재 화성에서 판매될 체중계를 위해 필요한 것은 components.xml에 클래스 정의를 하는 것뿐이다. 그리고 지구에서 판매될 체중계를 위해서는 EarthScale로 바꾸기만 하면 된다. 기회가 주어진다면 Wafer weblog 애플리케이션의 전체 유저 관리로 IoC를 사용함으로써 이루어 질 수 있다.

IoC 기능은 흥미롭지만 IoC를 모든 문제를 해결하는 해결책으로 생각하지 않도록 주의해야 한다. IoC는 어디서나 동작하는 것은 아니며 따라서 적절히 사용해야 한다.

JSP View 와의 작업#

The Tags 대부분의 프레임워크가 JSP 페이지로 정보를 주고 받는 가장 일반적인 방식은 태그 라이브러리를 이용하는 것이다. 어떤 이는 JSTL을 사용하고 반면에 어떤 이는 WebWork2에서 제공하는 자체적인 태그 라이브러리를 좋아한다. WW2의 전체 태그 라이브러리의 목록은 WW2 Wiki를 참조하라. Wafer 애플리케이션에서 대부분의 JSP 페이지는 WW 태그를 사용했고, 나는 WW 태그가 매우 유용하고 사용하기 쉽다고 생각한다. 아래에 태그들의 간단한 사용예가 나와 있다:

<ww:property value="user.firstName" />

또는

<ww:textfield label="First Name" name="firstName" ></ww:textfield>

첫번째 예제는 이 페이지를 호출한 Action 클래스의 getUser() 메소드를 호출할 것이다. 그리고 그것은 다시 getFirstName() 메소드를 호출할 것이다. 두번째 예제는 First Name이라는 label을 가지고 있고 input box의 name 속성값이 firstName인 input box를 생성할 것이다. 이 태그가 하는 일은 그다지 많아 보이지 않지만(누구나 단순한 HTML input 태그는 만들 수 있다), 이 태그는 매우 유용하게 인라인 에러 메세지를 다룬다.(위의 스크린 샷을 참조하라)

JSTL 여러분이 "표준적"인 사람이라면 JSTL을 사용할 수 있다. 아래의 태그는 위의 <ww:property value="user.firstName" /> WW2 태그와 동일한 작동을 한다.

<c:out value=${user.firstName}/> 

Ognl 과 the OgnlValueStack JSTL이 주로 출력을 위해서 사용되는 것과 달리 Ognl (Object Graphical Navigation Language)은 출력뿐 아니라 변수 할당과 같은 것을 하기 위해 사용될 수 있다는 점을 제외하고는 JSTL과 매우 비슷하다. Ognl을 사용하여 다음과 같은 Map을 생성할 수있다.

<ww:select label="'Gender'" name="'Gender'" list="#{'true' : 'Male', 'false' : 'Female'"/>

또한 ActionContext에서 다음과 같이 변수를 가져 올 수 있다:

<ww:property value="#name" />

Action 클래스에서 name은 다음과 같이 설정되어 있을 것이다:

ActionContext ctx = ActionContext.getContext();
ctx.put("name", otherUser.getUsername());

Ognl은 또한 사소한 것부터 완전한 타입 변환을 다룰 수 있다. 예를 들어 text input box로 부터 "10/14/1971" 이라는 값을 입력받고 Action 클래스에서 setDate와 같은 접근자 메소드를 사용할 수 있도록 이 값을 Date 객체로 변환할 수 있다. 필요하거나 원한다면, 여러분 자신의 커스텀 객체를 위한 타입 컨버터를 만들 수 있다.

마지막으로 Ognl은 Xwork이 기본적으로 리퀘스트 영역의 변수들을 저장하기 위한 스택인 OgnlValueStack 안에 있을 수 있도록 한다. 파라미터화된 인터셉터를 사용한다면 여러분은 모든 폼 파라미터를 나중에 코드내에서 쓰기 위해 스택에 저장할 수 있다. 이것은 Servlet API와는 달리 J2EE 컴포넌트(HttpRequset)처럼 작동하지만 실제로는 같지 않은 WW2의 또 다른 기능이다. 또 하나의 OgnlValueStack의 뛰어난 사용법은 큰 Controller-as-model Action 클래스를 단순하게 만드는 것이다. 30개의 파라미터를 가지고 있는 폼을 매핑시키는 Action 클래스가 있다고 가정해 보자. 이것은 Controller-as-Model pattern을 사용한다면 클래스로 submit된 파라미터를 저장하기 위해 여러분은 적어도 30개의 접근자 메소드를 가질 것이라는 걸 의미한다. 그러나 OgnlValueStack을 사용하면 단순히 다음과 같이 호출할 수 있다:

String bla = (Stringstack.findValue("#bla");

이것은 아마도 접근자 메소드만큼 명백하지 않을지 몰라도 또 다른 하나의 방식이다.

상세히 언급되지 않은 다른 기능들#

Action packaging 이것은 Action 클래스 집합과 xwork.xml 파일을 마스터 xwork.xml 파일로서 jar 패키지하도록 해 준다. 여러분은 또한 벨로서티 뷰에 대해서도 마찬가지로 jar 파일을 만들 수 있다. 패키징화 하는 것은 여러분의 애플리케이션을 컴포넌트화함으로써 구현된 기능들을 다른 애플리케이션에서 더 쉽게 공유할 수 있게 해 준다.

UI 컴포넌트와 커스텀 컴포넌트 WebWork는 재사용 가능하고, 최근 많은 웹블로그들이 가지고 있는 Calendar date picker와 같은 스킨화 할 수 있는 UI 컴포넌트를 만들 수 있다.

Namespaces 와 Packages 다른 패키지가 확장해서 쓸 수 있도록 xwork.xml의 설정을 묶어서 패키지화 할 수 있다. 이것은 모든 action과 인터셉터 등에 대해 접근할 수 있게 한다. 패키징과 네임스페이스를 추가하면 RegisterAction.action에 네임스페이스에 따라 다른 클래스가 매핑되도록 action의 알리어스를 만들 수 있다.

왜 내가 WebWork2를 좋아하는가?#

몇 달 전에 TSS에서 필자가 Maverick이 얼마나 훌륭한지 장담했던 것을 여러분은 아마 기억할 것이다. 그래서 WebWork2와 Maverick을 비교해보면 어떤가? 두 개는 매우 비슷한 점이 많다. 둘다 확장성과 분리(Decoupling)를 활성화하는데 도움이 되는 많은 인터페이스들로 이루어져 있다. 둘다 사용하기 쉽고 잘 디자인된 소스 코드 기반을 가지고 있다. 그러나 큰 차이는 WW2는 인터셉터와 IoC를 가지고 있다는 것이다. Wafer weblog 애플리케이션에서 Maverick을 사용하여 유저인증을 위해 사용했던 것과 같은 Action 계층 구조 대신에 인터셉터에서 훨씬 더 많은 부분을 전, 후 처리로 다룰 수 있다. 그럼 스트럿츠는 어떤가? 스트럿츠도 WW2가 가지고 있는 기능의 많은 부분을 가지고 있다. 그러나 이것은 메인 릴리즈에 포함되어 있는 것이 아니다. 많은 사람들에게 이것은 별 상관이 없을지도 모르지만, 실제 기업 표준에 이러한 애드-온들은 대부분 허용되지 않을 것이다. 또한 스트럿츠는 대부분 추상 클래스 기반이고, 인터페이스는 거의 사용하지 않고 있다.

WebWork2는 모델 2 MVC 프레임워크이다. 모델 2 MVC 기능에 적합한 다른 프레임워크들이 얼마나 많이 있나? 여기에 여러분이 WW2를 사용하는데 흥미를 가질 수 있는 이유들의 목록이 있다:

WebWork2는 구체적인 클래스 대신에 인터페이스 기반으로 설계되어 있다. 여러분은 IoC와 인터셉터와 같은 "다른" 프레임워크에는 없는 기능들을 좋아할 것이다. 여러분은 다른 작업과 함께 유닛 테스팅이 쉽고, J2EE 환경에 얽매이지 않은 MVC 프레임워크를 찾고 있다. 많은 기업들이 스트럿츠에서 WebWork2로 기준을 옮겨가고 있다. 당신은 코카콜라대신 펩시를 마신다.

결론#

WebWork2로 Wafer weblog 애플리케이션을 작성한 것은 정말로 꿈을 꾸는 듯이 멋졌다. 추상클래스 대신 인터페이스를 사용하는 것과 인터셉터 사용은 웹 애플리케이션 개발을 쉽고 유연성 있게 만들었고, 이런 새로운 개념들로 작업하는 것은 그다지 어렵지 않았다. WW2 커뮤니티는 뛰어난 리더들이 있으며, 크고 강력하다. 나의 Maverick 기사에서 나는 여러분이 프레임워크를 배워야 한다면 스트럿츠보다는 Maverick을 배워야만 할 것이라고 말했었다. 이번에, 나는 WebWork2를 선택할 것이다.

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
gif
webwork1.gif 9.9 kB 1 06-Apr-2006 09:45 218.239.69.158
gif
webwork2.gif 8.8 kB 1 06-Apr-2006 09:45 218.239.69.158
« This page (revision-1) was last changed on 06-Apr-2006 09:45 by UnknownAuthor