태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.


2008.08.23 03:54

SView : S(cala|imple) View for Spring MVC = {

SView : S(cala|imple) View for Spring MVC 

들어가는 말

smalltalk부터 시작된 MVC 패턴은 SoC(Separation of Concerns, 관심 분리)이라는 관점에서 응용 프로그램의 복잡도를 관리 할수 있는 아키텍쳐 패턴으로 인기가 있습니다.

특히 웹에서는 MVC 패턴을 구현한 많은 프레임워크가 있는데, 다른 대부분의 웹 프레임워크와 마찬가지로 Spring WebMVC는 여러 뷰/프리젠테이션 기술을 통합하여 사용할수 있습니다. 

Scala는  JavaVM이나 .NET CLR위에서 동작하는 객체지향 프로그래밍과 함수형 프로그래밍을 혼합한 multi-paradigm 언어입니다.  여러가지 흥미로운 특징이 있지만, 객체지향 기반의 패턴 매칭과 언어 신택스차원에서 지원하는 XML Processing은 XML 처리에 용이한 특징이 있습니다.

위의 이러한 특징들을 이용하여  SpringMVC의 View을 Scala의 XML Processing을 이용해서 구현해보았습니다.

관련 소스는 http://github.com/anarcher/sview/tree/master 에 있으며, git을 이용해서 받으실수 있습니다. ( git clone git://github.com/anarcher/sview.git )

SpringMVC의 View,ViewResolver

대개의 MVC 프레임워크가 그러하듯이 Spring WebMVC도 View을 선택하는 기능을 제공합니다. 이 부분에 대한 중요한 두개의 인터페이스가 있는데, org.springframework.web.servlet.View와 org.springframework.web.servlet.ViewResolver입니다.

ViewResolver은 말 그대로 viewName 과 실제 View을 결정해 주는 객체입니다. View는 넘어온 Request을 해당 View 구현체에게 위임하여 처리하는 객체입니다.

특히 ViewResolver는 대개 Ordered 인터페이스를 구현하여 여러 ViewResolver을  등록하여  사용 할 수 있게 구현합니다. (Chain of responsibility)

  1. <bean id="sviewBeanNameViewResolver" class="sview.SViewBeanNameViewResolver">
        <property name="order" value="1" />
        <property name="prettyPrint" value="true" />
        <property name="sviewFactoryBeanName" value="sviewSpringBeanFactory" />
    </bean>
    <bean id="sviewSpringBeanFactory" class="sview.SViewSpringBeanFactory"></bean>
  2.  

    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
        <property name="prefix"><value>/WEB-INF/views/</value></property>
        <property name="suffix"><value>.jsp</value></property>
    </bean>

ViewResolver의 구현상속을 하려면  View resolveViewName(String viewName,Locale locale)이라는 메소드를 구현해야 합니다.

즉 전달받은 ViewName으로 View을 결정해 달라는 부분이라고 할수 있습니다.

  1. public class SViewBeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver,Ordered {
        private final static String DELIMITER = "sview:";
        private int order = Integer.MAX_VALUE;
        private String sviewFactoryBeanName = "sviewSpringBeanFactory";
        private boolean isPrettyPrint = true;

  2.     public void setOrder(int order) {
            this.order = order;
        }
        public int getOrder() {
            return order;   
        }
        public void setSviewFactoryBeanName(String sviewFactoryBeanName) {
            this.sviewFactoryBeanName = sviewFactoryBeanName;
        }
        public void setPrettyPrint(boolean isPrettyPrint) {
            this.isPrettyPrint = isPrettyPrint;
        }

        public View resolveViewName(String viewName,Locale locale) throws Exception {
            if(viewName.indexOf(DELIMITER) < 0) {
               return null;
            }

            String sviewName = viewName.substring(viewName.indexOf(DELIMITER)+6);

            WebApplicationContext ctx = super.getWebApplicationContext();
            SViewFactory sviewFactory = (SViewFactory) ctx.getBean(this.sviewFactoryBeanName);
       
            ViewAdapter view = new ViewAdapter();
            view.setSViewName(sviewName);
            view.setSViewFactory(sviewFactory);
            view.setPrettyPrint(isPrettyPrint);

            return view;
        }   
    }

위의 코드는 ViewResolver의 구현클래스으로, viewName중 DELIMITER으로 시작되는 viewName인지를 검출하고, 스프링 컨텍스트에서 해당 이름으로 등록된 sviewFactory을 얻어 옵니다.

ViewAdapter 는 View 인터페이스의 구현클래스이고 전달받은 sviewFactory을 가지고 해당 sview을 얻어서, 랜더링하는(sviewRenderer)하는 책임이 있습니다. 

Scala의 XML Processing

Scala의 XML Processing은 xml을 문법 신택스처럼 사용할수 있습니다. 특히 xml을 코드상에 작성하면 scala 컴파일러가 xml 신택스를 scala.xml에 있는 객체로 교체해줍니다. 정확한 비교는 아니지만, 우리가 문자열을 변수에 할당 할때, String s = "hello" 과 String s = new String("hello")와 같은 것 처럼, scala에서는 val xml =<xml>hello</xml>이 val xml = Elem(null, "xml", Null, TopScope,Text("hello"))와 같습니다.

특히 내장된 표현(embedded expresions)을 사용하면  별도의 템플릿 엔진을 사용하지 않아도 될 정도로 XQuery을 사용하는 듯한 xml 처리가  용이해 질수 있습니다.

  1.  val date = <date>{ df.format(new java.util.Date()) }</date>
  2.  Console.println(date)

Hello World

sview을 이용한 Hello World을 출력하는 간단한 프로그램을 만들어 보겠습니다.

Spring2.5 의 AnnotationBased Controller와 DI을 사용합니다. 우선 SpringMVC의 View,ViewResolver에 나오는 설정을 springmvc-context.xml에 합니다.

그리고 MVC의 Controller과 View을 작성합니다.

  1. @Controller
    public class IndexController
    {
        @RequestMapping("/index.sview")
        public String indexScala(ModelMap models) {       
            Model model = new Model();
            model.setName("HELLO WORLD");
            models.addAttribute("model",model);       
            return "sview:index:sampleLayout";
        }
    }

IndexController.java

 

  1. @Component
    @Scope("prototype")
    class Index(b:ViewBinding) extends SViewContent(b){
        def head()  = {
            <title> this is index </title>
        }
        def content() = {
           val m : Model = b.getModels.get("model").asInstanceOf[Model]
           val df : java.text.DateFormat = java.text.DateFormat.getDateInstance()
        <html>
        <head>
            { head }
        </head>
        <body>
        <div>
            <div> this is index List ! </div>
            <div> { m.getName } </div>
            <div> today is { df.format(new java.util.Date()) } </div>
        </div>
        </body>
        </html>
        }
    }

index.scala

 

IndexController의 indexScala에서 리턴하는  viewName이 "sview:index:sampleLayout"입니다. 앞의 prefix는 sviewResolver에서 검출하는 용도로 쓰이고, index:sampleLayout은 각각  SView의 이름입니다.

sampleLayout.scala는 SiteMesh처럼  index의 html의 head와 body을 자신의 head와 body으로 추가하여 출력하게 됩니다. 간단한 레이아웃 기능이라고 할수 있습니다.

  1. @Component
    @Scope("prototype")
    class SampleLayout(b:ViewBinding,v:SView) extends SViewComposite(b,v) {
        def content() = {
             <html>
  2.   <head>

  3.   {  v.content \\ "head" \\ "_" }

  4.   </head>

  5.   <body>

  6.   { v.content \\ "body" \\ "_" }

  7.   </body>

  8.   </html>

  9.     }
  10. }

sampleLayout.scala

 

그림_3.png

 

대략적인 구성 요소

sview-pattern-dia.jpg

 

SView 

SView는  Scala의 trait입니다. 자바의 Interface에 해당합니다. SView에는 다음과 같은 메소드가 있습니다.

  1. trait SView
    {
        def doctype() : String
        def contenttype() : String
        def content() : Elem
        def render(render : SViewRenderer) : Unit
    }

SView은 View에서 필요한 행동을 정의합니다. 이중 기본적인 정보를 지원할수 있는 것은  구현 상속으로 제공하게 됩니다.

  1. abstract class AbstractSView extends SView {
        def contenttype() : String = {"text/html"}
        def doctype() : String = {""}
        def render(render : SViewRenderer) : Unit = {
            render.setContentType(contenttype())
            render.setDocType(doctype())
            render.setContent(content())
        }
    }

 

SView,SViewContent,ViewComposite : Composite , case classes

SView,AbstractSView을 상속받는 클래스에는 SViewContent와 SViewComposite가 있습니다. 이 두 클래스는 Scala의 케이스 클래스입니다.

  1. abstract case class SViewContent(binding : ViewBinding) extends AbstractSView {}
    abstract case class SViewComposite(binding : ViewBinding , view : SView) extends AbstractSView {}

어떤 패턴매칭을 하기 위해서 case class으로 만든 건 아니었습니다.(처음에는 패턴 매칭으로 Render의 책임을 다르게 구성하려고  그랬습니다.)

케이스 클래스는 객체 타입을 패턴으로 분해(decomposition)하기 때문에 erlang의 애리티(arity)처럼 패턴 매칭에 매칭변수로 쓰입니다.

그래서 하위 구현 클래스들의 생성자에 대한 제한을 줄수 있는 장점을 이용했습니다.

SViewComposite는 위의 레이아웃처럼 또 다른 SView을 인자로 얻어서 자기 자신의 content에 추가 할수 있습니다. 즉 공통적인  SView을 사용하기때문에 전체/부분이라는 구조를 쉽게 구성할수 있습니다.(Compositie패턴) Client가 보기에 Decorator패턴처럼 하나의 인터페이스을 가지고 다른 기능을 하는 것 처럼, 포함받은 SView의 내용을 자기 자신의 content에 추가하기 때문에 Decorator패턴의 실체화라고도 생각합니다.

SViewFactory,SViewSpringBeanFactory : AbstractFactory , ConcreteFactory

 SViewFactory는 SView을 생성하는 책임이 있습니다.

  1. public interface SViewFactory {
       public SView getSView(String viewName,ViewBinding viewBinding);
    }

여기서 ViewBinding은 구현패턴에서 이야기하는 파라미터 객체입니다.

원래 View 인터페이스는 HttpServletRequest,HttpServletResponse와 Model을 인자로 받는 메소드가 있습니다. ViewBinding은 이 인수를 파라미터객체로 합성합니다.

SViewSpringBeanFactory은 SViewFactory의 인터페이스 상속을 하여 SpringBeanContext에서 viewName으로 SView을 얻어옵니다.

  1. public class SViewSpringBeanFactory extends WebApplicationObjectSupport implements SViewFactory {
        private final String DEFAULT_VIEW_DELIMITER= ":";
        public SView getSView(String viewName,ViewBinding viewBinding) {
            String[] str = viewName.split(DEFAULT_VIEW_DELIMITER);
            String sviewName = null;
            String scompositeViewName = null;
            if(str.length >= 1 && StringUtils.hasText(str[0])) sviewName = str[0];
            if(str.length >= 2 && StringUtils.hasText(str[1])) scompositeViewName = str[1];

            WebApplicationContext ctx = super.getWebApplicationContext();
            SView sview = (SView) ctx.getBean(sviewName,new Object[]{viewBinding});
            if(StringUtils.hasText(scompositeViewName)) {
                SView sviewcomposite = (SView) ctx.getBean(scompositeViewName,
  2.                                      new Object[]{viewBinding,sview});
                return sviewcomposite;
            }
            return sview;
        }
    }

 

ViewAdapter : Adapter

SView을 상속한 객체들은 이미 상속했으므로 View 인터페이스를 상속하지 않는 한 ViewResolver에 추가할수 없습니다.

물론 SView가 View을 extends해도 되지만. ViewAdapter을 사용함으로써, 기존의 클래스(SView)에 대한 변경을 하지 않고, 전혀 다른 타입의 클래스(View)처럼 사용할수 있습니다.

특히 이부분은 스프링 프레임워크의 View Interface의 render의 Map model이 제너릭 타입 파라미터가 되어 않아서, Scala에서 인터페이스 상속을 하지 못해, 전혀 다른 타입인 View인척하는 객체입니다.

  1. public class ViewAdapter implements View {
        private final static String DEFAULT_CONTENT_TYPE = "text/html";
        private String sviewName;
        private SViewFactory sviewFactory;
        private boolean isPrettyPrint = true;
        private String contentType;

        public ViewAdapter() {
        }
        public void setSViewName(String sviewName) {
            this.sviewName = sviewName;
        }
        public void setSViewFactory(SViewFactory sviewFactory) {
            this.sviewFactory = sviewFactory;
        }
        public void setPrettyPrint(boolean isPrettyPrint) {
            this.isPrettyPrint = isPrettyPrint;
        }
        public String getContentType() {
            return contentType;
        }

        public void render(Map model, HttpServletRequest request,HttpServletResponse response)
  2.      throws Exception {
            ViewBinding viewBinding = new ViewBinding((Map<String,Object>) model,request,response);
            SView sview = sviewFactory.getSView(sviewName,viewBinding);
            this.contentType = sview.contenttype();
            SViewWriteRenderer renderer = new SViewWriteRenderer();
            sview.render(renderer);
            response.setContentType(this.contentType);
            PrintWriter out = response.getWriter();
            renderer.print(out,isPrettyPrint);
            out.close();
        }
    }

 

SViewRenderer , SViewWriteRenderer : Abstract Builder, Concrete Builder

공통적인 연산을 정의한 AbstractSView의 build()의 파라미터로 전달되는 SViewRenderer는 출력에 필요한 정보에 대한 메소드만을 가지고 있습니다. 즉 Director인 SView는 Renderer가 어떻게 표현할지에 대하여 알지 못합니다.  SViewRenderer의 구체 클래스인 SViewWriteRenderer는 HttpServletResponse에서 전달 받는 PrintWriter을 이용해서 표현합니다. 만일 ServletOutputStream이나 기타 다른 방식으로의 표현을 바꾸더라도 변화는 ViewAdpater에서 다른 방식의 표현을 하는 Renderer으로 교체로 국한됩니다.

  1. trait SViewRenderer {
        def setDocType(docType : String)
        def setContentType(contentType : String)
        def setContent(content : Elem)
    }

    public class SViewWriteRenderer implements SViewRenderer {
        private String docType = "";
        private String contentType = "";
        private Elem content = null;

        public void setDocType(String docType) {
            this.docType = docType;
        }
        public String getContentType() {
            return this.contentType;
        }
        public void setContentType(String contentType) {
            this.contentType = contentType;
        }
        public void setContent(Elem content) {
            this.content = content;
        }

        public void print(PrintWriter writer,boolean isprettyprint) {
            String result = null;
            if(isprettyprint == true) {
                PrettyPrinter pp = new PrettyPrinter(120,4);
                result = pp.format(content);
            }
            else {
                result = content.toString();
            }
            writer.print(docType);
            writer.print(result);
        }
    }

 

맺는 말

 작성하고 나서 혹시 이와 비슷한 생각을 한 사람이 있는지 알아보니까  Object-Oriented Pattern Matching 논문을 작성한 burak emir 의  scalaservlet 이 있었습니다.

 http://burak.emir.googlepages.com http://burak.emir.googlepages.com/scalaservlet.html

그리고 scala의 웹 프레임워크인 lift의 SHtml에서도 다음과 같이 사용합니다.  http://github.com/dpp/liftweb/tree/master/lift/src/main/scala/net/liftweb/http/SHtml.scala

 XML/HTML의 각 노드마다 객체를 생성하기 때문에 일반적인 JSP보다 비용 발생이 많은 듯 합니다. 실제 필드에서 적용하려면, 캐쉬전략이라든가 Scala의 XML Processing말고   다른 DSL이나 Template 엔진을 구성할 필요도 있다고 생각합니다. SViewSpringBeanFactory에서 sviewName에 해당하는 SView 객체를 얻지 못했을 경우에 좀더 명확한 Exception(SViewNotFoundException 등등)을 발생한다던가 하는 부분과 성능에 대한 부분은 다시 한번 고려해볼만 하다고 생각합니다.

처음 생각은 자바에서의  동적 스크립트 언어의 효과적인 사용에 적합한 레이어가 View가 아닐까해서 View에 대한 가볍고 쉬운 언어를 찾다가 여기까지 왔습니다. :-) 웹 프로그래밍에서 특히 자바 웹 프로그래밍에서 Html과 데이터를 바인딩 하는 방법은 많지만. 효과적으로  관리 가능한 (특히 테스트 가능한) 뷰 기술은 많지 않아 보입니다.

지금까지 읽어 주셔서 감사합니다.

 

참고자료

http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/web/servlet/View.html

http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/web/servlet/ViewResolver.html

http://www.scala-lang.org/intro/xml.html

http://burak.emir.googlepages.com/scalaxbook.docbk.html

 

 

}

이 글은 스프링노트에서 작성되었습니다.

신고
Trackback 0 Comment 0
2008.07.06 21:41

ActionAnnotations : Webwork Interceptor와 Annotation의 간단한 활용

Webwork의 Interceptor은 Webwork에서 중요한 위치를 차지 하고 있습니다. Webwork의 많은 기능이 Interceptor로 제공되어 있고, Webwork의 기능 확장은 거의 Interceptor로 이루어져 있습니다. 

가령 Webwork에서 TypeConversion이 실패 했을 경우,  자동으로 Action.INPUT으로 forward됩니다. 어떤 페이지의 입력(request)가 실패하면  다시 사용자 입력을 받기 위해  입력 화면을 그려주듯이 말이죠. 처음에 이부분때문에 무척 당황했는데요.. 이 부분 역시 Interceptor으로 제공되어 있습니다. 즉 converionError이라는 Interceptor을 제거하면 위와 같은 행동을 하지 않게 됩니다. (기본적인 webwork의 interceptor stack에 포함되어 있습니다.)

 프로젝트를 하면서 여러 액션에서 공통적으로-자주- 구성되는  루틴이 존재하게 됩니다. 가장 대표적인 예로는 사용자 인증과 권한에 대한 부분인데요. 대개의 경우  구현상속을 이용하여,  코드를 압축하거나, 위임을 하여 사용하기 편하게 제공하게 됩니다. (물론 서비스로 만들어서 사용 될수도 있지만, 여러 서비스를 사용한 액션처럼 구성하게 되겠지요.) 

 이번에 회사 동료와 버스를 타면서 논의 하다가 나온 아이디어는 (이것 말고 Interface 을 이용한 방법도 이야기 되었습니다.) Annotation을 가지고 Interceptor가 그러한 기능을 지원하는 건 어떠한가? 이었습니다. 

간단한 예를 들자면,

  1. @MusyBeLogin(loginUrl="/auth/login.action")
  2. class BlogPostListAction extends ActionSupport {
  3.      @LoginMember 
  4.      private Member loginMember 

     

         @Permission

         private Permission permission
    ...

이런식으로 구성하게 됩니다. 

Annotation으로 BlogPostListAction에서의 필요한 여러 객체를 제공하거나(LoginMember,Permission),Action에 annotation된 조건이 아니라면 흐름을 제어하는 기능들을 할수 있습니다. 

이런식의 구성이 좋은지는 아직 모르겠지만. (http://forum.ksug.org/viewtopic.php?f=5&t=76#p175) Action에 존재할수 있는 코드를 압축한다는 개념을 좀 간단하게 구현했다고 볼수 있습니다. 
우선 간단하게 책임을 나누어서, 

  1. public abstract class ActionAnnotationInterceptor extend AroundInterceptor {
  2.    protected abstract ActionAnnotationHandling getHandling();
  3.    @Override
  4.    protected void before(ActionInvocation invocation)
  5.        Object action = invocation.getAction();
  6.        HttpServletRequest request = getHttpServletRequest(innovation);
  7.        getHandlling().handling(action,request);
  8.    }
  9. ...

ActionAnnotationInterceptor이라는 간단한 Interceptor을 만듭니다. 이 추상 클래스을 구현 상속한 객체에서 getHandling()을 구현합니다.

ActionAnnotationHandling은 말 그대로 ActionAnnotation을 핸들링하는 인터페이스입니다.

  1. public interface ActionAnnotationHandling {
  2.    public void handling(Object action,HttpServletRequest request) throws Exception
  3. }

여러 Annotation을 각각 핸들링하는 객체를 포함하고는 ActionAnnotationHandlers 을 만들었습니다.

  1. public class ActionAnnotationHandlers implements ActionAnnotationHandling {
  2.    public void handling(Object action,HttpServletRequest request)  {
  3.        for(ActionAnnotationHandling handling : handlers) {
  4.                    handler.handling(action,request);
  5.        }    
  6.    }  
  7.  ....
  8. }

 

그리고 각각 ActionAnnotationHandler들의 공통적인 기능을 AbstractActionAnnotationHandler에 넣었습니다.

  1. public abstract class AbstractActionAnnotationHandler implements ActionAnnotationHandling {
  2.    public boolean hasTypeAtAnnotation(Calss<? extends Object>klass,Class<? extends Annotation> annotationClass){
  3.      if(kclass.isAnnotationPresent(annotationClass) { return true ;} return false;
  4.    }
  5.     ...
  6.    public abstract void handling(Object action,HttpServletRequest request)  throws Exception;
  7.  ....
  8. }

 

 

그리고 각각 특정 AnnotationHandler을 작성했습니다. 여러 객체로 나누어서 작업하는게 불편해서 하나의 객체를 만들었는데요, action의 Type이나 fileld에 해당하는 annotation 이 존재하면 reflection api으로 필요한 객체를 넣어준다거나, 세션의 인증키가 맞지 않다면 예외상황을 발생하여 ExceptionHandler로 하여금 에러 페이지로 가도록 했습니다.

 

쓰다보니까 벌거 아니긴 해서 :-} 팀내에 아이디어 프로토타입으로 만들어 봤는데요.. 이미 프로젝트가 진행된지 절반은 되어서 (하지만 새로 사이트를 개발 들어가는 시점이긴 해서) 시간도 없고  이부분에 대한 비용이 많이 지출될거 같아서 이정도까지 해보았습니다.

 

지금 생각해보니, Handler으로 분리한게 좋은건지. 라는 생각도 드는 군요. Interceptor의 제어 흐름에서 멀어서져 특정 Annotation이흐름을 제어할때 쉽지 않을 듯 하고 ,  webwork은 ActionContext을 가지고 거의 모든 정보를 참조할수 있기 때문에 ActionAnnotationHandling.handling()에 HttpServletRequest을 넣어줄 필요는 없지 않을까 싶긴 하네요..

 

언제 다시 Webwork을 다시 사용하게 되면 그때 가서 다시 한번 끄집어 낼지도 모르겠습니다. :-)

 

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

신고
Trackback 0 Comment 0
2008.06.08 03:16

WebWork의 PageController Pattern와 SiteMesh 사용

이번 프로젝트는 이런저런 이유로 해서 Framework Stack을 SiteMesh+WebWork+Spring+SqlMap으로 구성하게 되었습니다.

Struts1을 사용할때 Webwork에 대한 관심이 있었지만,Webwork으로 사이트를 빌딩한 경험이 전무한 저로써는 Struts1/SpringMVC 스타일과 좀 (많이) 다른 Webwork이 신기했었습니다. 

특히 Webwork은 PageController 이라는 패턴을 사용할수 있는데요. 간단하게 말해, 대개의 WebMVC는 Controller/Action이 중앙 집권적인 제어를 위해 View 레이어가 랜더링 되기 전에 제어를 하지만. PageController 패턴은 View에서 특정 Action을 호출 할수 있는. 즉 페이지의 특정 논리적인 단위를 재사용하거나 파티셔닝 하는  좋은 방법이 있습니다. 사용법도 매우 간단한데요.

 

  1. <div>
  2. <ww:action name="noticeList" namespace="/tiles" executeResult="true" />
  3. </div>

 

mvc4.JPG

 

SiteMesh와 함께 자바에서 많이 사용하는 Tiles(원래 Struts의 서브 프로젝트이었지만,Top Project으로 승격되었습니다.)도   비슷한 것을 가지고 있습니다.

특히 SpringFramework와 함께 Tiles 사용한다면,

org.springframework.web.servlet.view.tiles.ComponentControllerSupport을 구현상속해서 쉽게 구성할수 있습니다.

 

FrontController에 비해, 몇가지 생각해야 하는 부분이 있습니다. 우선 제어의 선후가 바뀌기  때문에, Action에 대한 제어가 쉽지 않습니다. 가령. X,Y,Z에서 동일한 쿼리를 발생한다면 한 페이지를 랜더링 하기 위해 동일한 데이터를 얻기 위해 쓸모없는 퀴리를 날리게 됩니다. 물론 1초캐쉬같은 것등으로 해결이 가능합니다만.

 

물론 한 페이지에서 여러 독립적인 Action이 존재하여 페이지의 각 요소를 재사용하는 일도 있지만, Sitemesh와 같은 페이지 레이아웃/데이코레이션을 구성하는 라이브러리를 사용할 경우 복잡한 레이아웃이 아닌 경우, 아직까지는 크게 사용하지 않을 듯 합니다.

 

그래서 이번에 다시 레이아웃을 잡을때 SiteMesh의 Decorator마다 Action을 하나만 두는 방식은 어떤가 생각해봤습니다. 

webwork-sitemesh-dia.png

 

즉 SiteMesh/Decorator에서 호출하는 Webwork/Action이 Result을 가지고 있지 않고. 단지 해당하는 작업을 한 후 화면에 바인딩할 객체를 ServletRequest의 속성(attribute)에 담기만 하고 Decortator에서 그 정보를 출력하는 것입니다. 

즉 제어가 두군데의 Action으로만 나누어 지는 것입니다. 

 

 <ww:action> 태그에서 result을 실행하지 않기 위해서 (executeResult="true"으로해도 Action의 execute()메소드의 리턴이 NONE이기 때문에 view가 보이지 않습니다. )

  1. <html>
  2. <head>
  3. <ww:action name="noticeList" namespace="/tiles" executeResult="false" />
  4. </head>
  5. <body>
  6. ...
  7.   <c:out value="${tileNotice}" /> 
  8. ...

 

  1. public class NoticeTileAction extends ActionSupport {
  2.     public String execute() throws Exception {
  3.        ServletActionContext().getRequest().setAttribute("tileNotice","hello world!");
  4.        return NONE;
  5.     }
  6. }

 

사실 별거 아닌데. 글만 길어지고 그림만 생겼군요. :-) 물론 SiteMesh가 없이도 사용할수 있는 방법입니다.  

정리도 안되어 있고 두서도 없는 글 읽어 주셔서 감사합니다. (...) 

 

관련 링크

http://www.opensymphony.com/webwork/wikidocs/action.html

Webwork In Action

 

 


 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

신고
Trackback 0 Comment 0
2008.06.07 02:19

Tabla - 간단한 태그 기반 템플릿 유틸리티

Tabla는 간단한 태그 기반의 템플릿 유틸리티입니다.  컨텐츠의 특정부분을 마크업을 이용하여 컨텐츠의 내용을 가공하기 위해서 작성했었습니다. :-)

간단한 예제


  1. Tags tags = new Tags(); tags.addTag(new NamedTag() {     public String execute(ParseTag pt,TagContext tc) {       return "hello world!";     }     public String tagName() { return "t:hello"; } };   Parser p = new Parser(tags); String result = p.parse("<t:hello />");   >> hello world!

클래스들

 1. Tag - 태그의 인터페이스.  태그의 행동을 정의합니다.

 2. NamedTag - 태그(Tag)의 인터페이스 상속.  태그이름(tagName)을 등록할수 있습니다.  

 3. Tags - 태그들

 4. TagBinding - 파싱된 컨텐츠(Parse)와 Tag을 바인딩(Binding)하는 책임이 있습니다.

 5. TagContext - 외부 환경이나 태그 간의 문맥적으로 값을 전달할때 사용됩니다.

 6. Parse - 파싱된 컨텐츠(Parse)

 7. ParseTag  - 파싱된 컨텐츠중 태그 정보

 8. Parser - 파서

 9. ParseFailureException - 파싱 실패 예외 상황

 10. utils.ParseTagRemover - 태그 시그니처 제거자 유틸리티.


태그 정의 하기

태그를 정의하는 건 매우 쉽습니다. Tag,NamedTag을 구현하면 됩니다.

  1. Tag t = new Tag() {
  2.     public String execute(ParseTag pt,TagContext tc) {
  3.        return pt.getContent();
  4.     }
  5. };
  6. Tags tags = new Tags();
  7. tags.addTag("t:*",t);
  8. ...

위의 코드는 t:으로 시작하는 모든 태그에 반응하게 됩니다. 물론  ParseTag.getTagName()으로는 정확한 태그 이름을 얻을수 있습니다.


파싱된 컨텐츠의 태그정보 - ParseTag

모든 태그는 ParseTag와 TagContext이라는 파라미터 객체를 얻게 됩니다. ParseTag은 Parser가 파싱한 컨텐츠중에 현재 태그에 해당하는 영역을 ParseTag에 담아서 전달하게 됩니다.  

  1. class NewsDataTag implements NamedTag {
  2.    public NewsDataTag(NewsDataService newsDataService) {
  3.       this.newsDataService = newsDataService;
  4.    }
  5.    public String execute(ParseTag pt,TagContext tc) {
  6.       String size = pt.getTagAttrMap("size");
  7.       String category = pt.getTagAttrMap("category");
  8.       List<NewsData> newsData = newsDataService.findBy(size,category);
  9.       return newsData.toBuildHtml();
  10.    }
  11. }


태그의 글로벌 영역? - TagContext

태그와 태그(중첩된 태그 등등)나 태그를 실행중으로 값을 전달할때가 있습니다.

  1. tags.add("t:container",new Tag() {
  2.    public String execute(ParseTag pt,TagContext tc) {
  3.       ...
  4.       for(Item item : items) {
  5.           tc.addLocalAttribute("itemTitle",item.getTitle());
  6.           tc.addLocalAttribute("itemUrl",item.getUrl());
  7.           result.append(pt.getContent());
  8.           tc.clearLocalAttibutes();
  9.       }
  10.       return result.toString();
  11.    }
  12. });
  13.  
  14. tags.add("t:itemTitle",new Tag() {
  15.     public String execute(ParseTag pt,TagContext tc) {
  16.        String itemTitle = (String) tc.getLocalAttribute("itemTitle");
  17.        if(itemTitle != null) {
  18.            return itemTitle;
  19.        }
  20.        return pt.getContent();
  21.     }
  22. });
  23.  

  1. items: a,b,c
  2. <t:containter ><t:itemTitle />,</t:container>  
  3. >> a,b,c,

기타

뭐 그리 잘 만들건 아니지만. 그리고 만든지 몇달이 지나서 그닥 머리속에 남아 있는 것이 별루 없긴 하네요. 좀더 수정할것은 많은 듯 합니다만. :-)


tabla-0.0.2-sources.jar

tabla-0.0.2.jar






이 글은 스프링노트에서 작성되었습니다.

신고
Trackback 0 Comment 1
2007.12.13 18:09

[펌] 스스로 만족할 수 있는 일하기

인생에서 70년은 20억초에 불과한 시간이다.

스스로 자랑스럽게 여기지 않는 일을 하며 세월을 낭비하고 싶지는 않다.

 

프로그래머로써 여러분은 소중한 시간과 재능, 돈, 기회를 가지고 있다.

이러한 선물들을 책임있게 사용하기 위해, 여러분은 무엇을 할 것인가?

이 질문에 대한 나의 대답은 다음과 같다. 자신과 컴퓨터뿐만 아니라 다른 이를 생각하며 코드를 작성하라.

(code for others as well as myself and my buddy the CPU)

 

by Kent Beck

※ 서문에 나오는 내용인데 임의로 가감편집했다. 원문은 더 길다.


http://blog.naver.com/django44/40044982845 에서 무단 도용했습니다. :-)




신고
Trackback 0 Comment 3
2007.12.01 22:42

Spring 2.5의 Annotation-based SpringMVC Configuration

Spring2.5의 Annotation Configuration의 도입에 가장 큰 영향을 받는 곳이 spring-mvc이다.  특히 AbstractController이나 SimpleFormController의 구체적인 상속 없이 URL과 객체가 매핑이 가능하다.

  1. @Controller
    @RequestMapping("/hello.html")
    public class HelloWorldController {
       @RequestMapping(method=RequestMethod.GET)
       public String hello(ModelMap model) {
           return "hello";
       }
    }

이전  ModelAndView에서 ModelMap으로 Model과 View을 나누었다. ModelMap을 보면 Map 인터페이스가 가지고 있는 메소드를 가지고 있다. 즉 Map 인터페이스의 구현체중 하나(LinkedHashMap)를 상속했다.

SpringMVC의 UrlMapping에서 하나 불만 사항이 있다면  package개념이 없다는 점이다. WebWork/Struts2의 경우 패캐지 태그안에 하나의 네임스페이스처럼 구성할수가 있는데.. 많은 url mapping이 존재하게 되면 package개념(관례적으로라도) 구성하게 되는데 이런 부분이 없는게 아쉽다. 가령

  1. @Controller
    @RequestMapping("/hello")
    public class HelloWorldController {
       @RequestMapping("/world.html")
       public String hello(ModelMap model) {
           return "hello/world";
       }
    }

이렇게 구성해도 Exception은 발생하지만 않지만 Mapping을 찾지 못한다. 버그인지도 :-)

또 이렇게 Annotation 기반으로 mapping을 할때 고민되는 점이 Interceptor부분이다. XML의 경우 각각 Interceptor마다 UrlHandlerMapping 빈을 만들어서 해결했지만,  Annotation 기반의 매핑의 경우 불가능하다. 결국 생각해 보면,


  1. <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
      <property name="interceptors">
          <bean class="kata.springmvc.interceptors.UrlPatternMatchingInterceptor" >
  2.          <property name="allowUrlPatterns">
                <list>
                <value>/app/board/*</value>
                <value>/app/client/*</value>
                </props>
            </property>
          </bean>
      </property>
    </bean>

처럼 UrlPatternMatchingInterceptor을 하나 만들어서 (상속해서) handle하도록 해야 할듯 하다.

SpringMVC에서 가장 많이 쓰는 Controller 구현체는 대개 AbstractController과 SimpleFormController,MultiActionController등이 될듯 한데. Annotation기반 설정은 특정 Controller 구현체를 상속받지 않으므로 (더군다나 Servlet API과도 decoupling되는데.)

SimpleFormController 처럼 HTTP Method가 GET 일경우와 POST일 경우 Controller의 움직임을 나누고 싶다면

  1. @Controller
    @RequestMapping("/board_form.html")
    public class BoardFormController {
       @RequestMapping(method = RequestMethod.GET)
       public String onForm(ModelMap model) {
           Board board = new Board();
           model.addAttribute("board", board);
           return "form";
       }
       @RequestMapping(method = RequestMethod.POST)
       public String onSubmit(
  2.         @ModelAttribute("board") Board board,BindingResult result,ModelMap model) {
           return "goto_list";
       }
    }

으로 간단히 구성된다.  initBinder의 경우 annotation으로 존재하고 onValidate같은 경우는 onSubmit에서 메소드를 호출하면 될듯 하다(흐름을 강제적으로 제약되지 않는다.)  SimpleFormController을 쓰면 기본적인 흐름에 대한 필요한 요소만 채워준다는 느낌이 들었는데(Framework과 대화한다고 할까?) 이런 강제적인 부분이 사라진듯 하지만, SimpleFormController에서 약간만 예외적인 흐름을

구성하려고 하면 복잡함이 다가오는 걸 볼때, 이런 간단한 흐름만 제공하는것도 맞을 것 같다.

MultiActionController은 좀더 간단한데,

  1.  @RequestMapping("/multi/list.html")
    public String list(ModelMap model) {
       model.addAttribute("test", new String("test....."));
       return "list";
    }

당연하겠지만 메소드의 return이 void 일 경우 CoC으로 메소드 이름으로 view을 찾는다.

특히 Servlet API을 직접 사용하지 않아도 되는 @RequestParam,@RequestAttribute,@SessionAttributes등이 있다.

이 부분에서 약간 불만인 점은 @RequestAttribute("board") Board board 같은 Form을 Command객체 하나로만 Binding된다는 점이다.  폼이 하나의 객체로만 환원 되는 일이 얼마나 될까. 복잡한 폼일수록 여러 객체를 한 화면에서 Form에 그릴 일이 많아지는데

말이다. 물롬 FormBean처럼 Command 객체를 만들고  Model을 Composite하면 해결되는 문제이긴 하지만.

만일 Form Tag에서

  1. <from:input path="board.title" ...

  2. <form:input path="menu.title" ...
  1. public String list(@ModelAttribute("board")Board board,@RequestAttribute("menu")Menu menu...

이러게 되면 어떨까? '_'  되는데 모르는 걸수도 있겠다. .이 부분은 그냥 나의 짧은 생각.. :-) (해보지 않았음)


약간 2시간 정도면 간단한게 알아 볼수 있을 꺼라 생각했지만..한 4시간은 쓴거 같다.. T_T 사실 tomcat 설정도 기억 안나서 servlet 띠우는 거만으로 삽질한거 보면.. 대충보면 쉬워 보이는 일이라도 막상 하면 쉬운 일은 하나도 없는거 같다.

기술을 하나 제대로 배운다는게 결코 쉬운 일이 아니라는 생각이 든다. 단지 이런 사용법을 안다고 어떤 기술의 가지고 있다고 할수가 없는 거 같다. 배면의 움직임을 이해하고 습득하는게 중요하다고 생각되기도 하지만. 어느 도메인을 추구하는지에 따라 해답을 달라 질듯 하다.  Over Layered Architecture... Layered Developer? :-)




이 글은 스프링노트에서 작성되었습니다.

신고
Trackback 0 Comment 3
2007.11.26 18:46

Spring2.5에서 SpringJDBC 사용하기

SpringFramework 2.5의 릴리즈에서 많은 부분이 추가/개선되었지만, SpringJDBC에서도 몇가지 개선/추가가 이루어졌다.

우선 SimpleJdbcTemplate에서 SqlParameterSource의 사용과 SimpleJdbcInsert의 추가가 아닐까 싶다.

SimpleJdbcTemplate는 Java5의 varargs와 autoboxing을 지원하여 (특히 목록을 얻어오는 퀴리의 경우) 캐스팅을 별도로 고민 할 필요가 없긴 한데.

문제는 복잡한 퀴리의 경우 classic placeholder ('?')으로는 불편해서 결국 NamedParameterJdbcTemplate으로 작성하곤 했는데.

SimpleJdbcTemplate에서 SqlParameterSource가 가능하게 되었다.


  1. String sql = "SELECT id,title,content FROM boards "+
  2.                                      "WHERE fl_public = :fl_public ";
  3. MapSqlParameterSource params = new MapSqlParameterSource();
  4. params.addValue("fl_public",fl_public);
  5. List<Board> list = this.simpleJdbcTemplate.query(sql,
  6.    new ParameterizedRowMapper<Board>()
  7.   {
          public Board mapRow(ResultSet rs, int rowNum)
  8.           throws SQLException
  9.      {               
  10.          Board b = new Board();               
  11.          b.setId(rs.getLong("id"));               
  12.          b.setTitle(rs.getString("title"));               
  13.          b.setContent(rs.getString("content"));               
  14.          return b;  
  15.      }
      },
  16.   params
  17. );

사실 SimpleJdbcTemplate이 추가될때 추가되어야 할 기능일지도. (...)

그리고 추가된 부분이 SimpleJdbcInsert,SimpleJdbcCall이다. SimpleJdbcCall은 stored procedure을 잘 사용하지 않아서 패스. SimpleJdbcInsert는 QueryObject[PoEAA(316)]패턴과 비슷하게 쿼리를 객체화 할수 있다고 볼수가 있는데. (물론 접근 스타일은 다르지만.)

  1. jdbcInsert = new SimpleJdbcInsert(this.dataSource).withTableName("boards");
  2. Map<String, Object> parameters = new HashMap<String, Object>();
  3. parameters.put("id",id);
  4. parameters.put("title", board.getTitle());
  5. parameters.put("content",board.getContent());
  6. jdbcInsert.execute(parameters);

빈의 프로퍼티과 테이블의 컬럼이름이 잘 맞는다면.

  1. jdbcInsert = new SimpleJdbcInsert(this.dataSource).withTableName("boards");
  2. SqlParameterSource parameters = new BeanPropertySqlParameterSource(board);
  3. jdbcInsert.execute(parameters);

 데이터베이스의 시퀀스 키를 얻을수 있지만(executeAndReturnKey(),usingGeneratedKeyColumns()). PgSql에서 지원하지 않는 데이터베이스라고 나와서 패스. -_- (org.springframework.jdbc.core.metadata에 PostgresqlCallMataDataProvider가 없다.)

SimpleJdbcUpdate나 SimpleJdbcSelect는 없고 단지 SimpleJdbcInsert만 나온 이유가 Insert 문이 가장 생성하기 쉽고.

또 개인적인 경험에 따르면 가장 작성할때 오타가 가장 많이 나는 퀴리인 듯 하다. Update문의 경우 key-value 스타일이기 때문에 Insert문보다는 작성하기 수월한 면이 있다는 걸 부정할수 없겠다.

그리고 Spring이 버젼업하면 SimpleJdbcUpdate나 SimpleJdbcSelect가 나와서 ORM이 없는 어둠의 장소,약속의 저편...에서도 광명이 찾아오지 않을까? :-)






이 글은 스프링노트에서 작성된걸 가지고 티스토리에서 삽질했습니다.

신고
Trackback 0 Comment 4
2007.11.09 00:49

그냥저냥 스크럼(Scrum) 이야기.

이번달 마소에 스크럼 이야기를 잼 있게 읽고 하나 만들어 봤다.
표절과 왜곡이 컨셉이고.



나는 방법론을 좋아하지 않는다.(관심도 없다) 대개 개발자들이 안 좋아한다고는 하는데.
내가 개발자이라서 안 좋아하는지. 천성이 체계적이지 않아서 그런지.
(그래서 개발을 발로 하나? '_')
(우리 회사 유행어다. 발로하는거.)
개인적인 생각은 그 프로세스라는게 무겁고 권위적이기만 하지 말고 엔터테인먼트 요소가 있어야 한다고 생각한다.
또 하나의 걱정은 나이 들어서 현실에 너무 밀착되어서 생각이 편협해지지 않기를 바란다.
이리저리 생각만 많고 몸은 무겁다.
또 하나 기억해야 하는 것.
삶에 대한 고뇌가 깊어질수록 웃을수 있는 여유 또한 넓어진다는 것. :-)


덧. 나도 "감사합니다!"라고 한번 넣고 싶었다.. 회식후 술먹고 작성한것 치고는 잘하지 않았냐? 일필휘지로 이정도면..ㅋㅋ;
신고
Trackback 1 Comment 2
2007.11.04 19:01

어떤 책의 서평중에서.


다소 허망하지만 좋은 프로그래가 어떤 기법을 쓰느냐와 상관없이 좋은 프로그램을 만드는 것과 같이
좋은 모델러는 어떤 기법을 쓰느냐와 상관없이 좋은 모델을 만듭니다.

이 책이 좋은 모델러를 만드는데 기여할수는 있겠지만
책의 방법을 따른다고 "해당 프로젝트의 기간"내에서 좋은 모델이 만들어지지는 않을 것 같습니다.

마틴파울러의 리팩토링책이 그러했던 것처럼
단지 우리는 실수를 통해 더 빠른 피드백을 얻고 더 많은 생각을 하게 됨으로써
조금씩이나마 조금 더 나은 모델러로 성장하여 "다음 프로젝트에서는" 조금 더 나은 모델을 만들수 있을것입니다.


신고
Trackback 0 Comment 0
2007.10.28 14:13

스프링 세미나 갔다가 오다.





졸려서 제대로 듣지 못했음. (...)
신고
Trackback 0 Comment 0


티스토리 툴바