웹 어플리케이션의 흐름 제어를 위한 인터페이스를 정의해본다.
프로바이더: 최범균

지금까지 2회에 걸쳐서 모델 2 구조를 이용한 '로직과 프리젠테이션의 분리' 및 모델 2 구조에 커맨드 패턴을 적용하는 것에 대해서 살펴보았다. 물론, 이 두가지 요소를 통해서 우리는 모델 2 구조를 상당부분 객체 지향적으로 구현할 수 있게 되었지만 여기에 추가적으로 우리가 해야 할 부분이 있다. 바로 흐름제어를 위한 별도의 방안을 강구하는 것이다.

2부에서는 커맨드 패턴에서 사용되는 각 커맨드 처리 객체가 다음에 보여줄 JSP 페이지를 처리의 결과값을 리턴하는 방식을 사용하여 웹 어플리케이션의 흐름을 제어했었다. 물론, 이처럼 커맨드 패턴의 각 객체가 흐름제어를 하는 방식만으로 충분할 수도 있다. 하지만 커맨드 패턴에서 사용되는 객체가 아닌 별도의 객체에서 흐름 제어를 할 수 있다면, 즉 흐름 제어의 역할을 담당하는 객체를 별도로 사용한다면, 웹 어플리케이션은 더욱 객체 지향적인 구조를 갖게 될 것이다. 이번 3부에서는 흐름 제어를 위한 인터페이스를 작성해봄으로써 모델 2 구조에 대한 애기를 끝맺도록 하겠다.

처음 흐름 제어를 위한 인터페이스를 생각했을 때 생각났던 것이 JDBC API 였다. JDBC의 java.sql.ResultSet 인터페이스를 보면 next(), absolute() 등 결과 집합 행을 이동할 때 사용되는 메소드가 존재하는 것을 알 수 있으며, 이를 통해 읽어올 행의 순서를 제어한다는 사실도 알수 있다. 필자는 이러한 ResultSet 인터페이스와 비슷한 방법으로 웹 어플리케이션의 흐름 제어를 위한 인터페이스를 설계해보았으며, 그 인터페이스는 다음과 같다. (주석문은 소스 코드를 쉽게 볼 수 있도록 하기 위해서 삭제하였다.)

  package org.jcore.webapp.control.spi;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import org.jcore.webapp.control.exception.ControlBoxException;
  
  public interface ControlBox {
     
     public void setHttpInfo(HttpServletRequest request,
                             HttpServletResponse response);
     
     public int getPreviousStep();
     public int getNextStep();
     public int getCurrentStep();
     public int getRequestStep();
     public boolean isValidStep();
     public boolean isFirstStep();
     public boolean isLastStep();
     public void goNextStep() throws ControlBoxException;
     public void goPreviousStep() throws ControlBoxException;
     public void goFirstStep() throws ControlBoxException;
     public void goLastStep() throws ControlBoxException;
     public void goInvalidStep() throws ControlBoxException;
     public void clearControlBox() throws ControlBoxException;
  }

ControlBox 인터페이스를 보면 getXXX() 메소드와 isXXX() 메소드 그리고 goXXX() 메소드의 세 가지 종류의 메소드가 존재하는 것을 알 수 있다. 이러한 메소드들이 무엇을 의미하지 살펴보기 전에 ControlBox 인터페이스가 어떤 의미로 설계되었는지 이해해야만 한다. 필자는 ControlBox를 설계할 때 스텝이라는 개념을 도입하였다. 여기서 스텝은 어떤 일을 처리하는 단계를 의미하는 것이다. 스텝의 의미를 좀더 구체적으로 정의하기 위해서 다음 그림을 살펴보자.

위 그림은 사이트 가입 절차의 일부를 표시한 것으로서 유저는 일반적으로 사이트에 가입신청을 하면 약관을 보게 되고, 이후 회원 정보 입력과 확인 절차 후 최종적으로 사이트의 회원으로 가입하게 된다. 이러한 과정은 크게 두 개의 부분으로 나뉜다. 하나는 위 그림에서 윗줄에 표시된 어떤 행위를 요청하는 부분이고 다른 하나는 위 그림에서 아랫줄에 표시된 요청을 처리하는 부분이다. 여기서 각각의 요청을 하나의 스텝으로 볼 수도 있고 또는 요청의 처리를 하나의 스텝으로 볼 수도 있다. 각각의 요청을 하나의 스텝으로 볼 때, 위 그림에서 "약관보기요청", "화면1 요청", "입력확인 요청" 등이 하나의 스텝이 된다.

그렇다면 흐름 제어는 무엇인가? 위 그림에서 흐름은 "사용자가 일정한 순서로 스텝을 거치는 것"이라고 정의할 수 있으며, 따라서 흐름 제어는 "사용자가 거치게 될 스텝의 순서를 제어해주는 것"으로 정의할 수 있다. 스텝의 순서를 제어하는 것에는 다음과 같은 것이 있을 수 있다.

  • 다음 스텝으로 이동
  • 이전 스텝으로 이동
  • 처음 스텝으로 이동
  • 마지막 스텝으로 이동

ControlBox 인터페이스에 선언되어 있는 goNextStep(), goPreviousStep(), goFirstStep(), goLastStep() 메소드가 바로 이러한 스텝의 이동을 위한 것들이다. 그리고 ControlBox 인터페이스는 현재 스텝이 무엇이고 사용자가 요청한 스텝이 무엇인지 그리고 요청한 스텝은 흐름에서 올바른 지의 여부를 판단하기 위한 메소드를 정의하고 있는 데, 이들 메소드는 다음과 같다.

  • getCurrentStep() - 현재 처리할(또는 처리하는) 스텝을 구한다.
  • getRequestStep() - 요청한 스텝을 구한다.
  • goInvalidStep() - 잘못된 스텝을 요청할 경우 에러 페이지로 이동한다.
  • clearControlBox() - ControlBox와 관련해서 저장된 정보를 삭제한다.
  • isValidStep() - 올바른 스텝인지 판단한다.
  • isFirstStep() - 첫번째 스텝인지 판단한다.
  • isLastStep() - 마지막 스텝인지 판단한다.

그리고 ControlBox 인터페이스는 웹 어플리케이션의 요청/응답과 관련된 객체를 전달받기 위한 메소드인 setHttpInfo()를 선언하고 있다. ControlBox 인터페이스의 기본적인 사용 방법은 다음과 같다.

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
                    throws IOException, ServletException {
	  
	  // ControlBoxImpl은 ControlBox 인터페이스를 구현한 클래스
	  ControlBox box = new ControlBoxImpl();
	  
	  box.setHttpInfo(request, response);
	  if (box.isValidStep()) {
	    // 어떤 알맞은 처리를 한다.
	    ...
	    box.goNext();
	  } else {
	    // 알맞은 스텝이 아니면,
	    box.goInvalidStep();
	  }
  }

위 코드를 보면, 웹 어플리케이션의 흐름 제어는 ControlBox 인터페이스에 정의되어 있는 메소드를 통해서 처리되기 때문에 서블릿이나 기타 로직을 처리하는 객체에서 흐름을 제어하기 위한 코드를 삽입할 필요가 없음을 알 수 있다. 흐름 제어에 있어서 중요한 것은 ControlBox 인터페이스를 구현하는 것 뿐이다. 실제 ControlBox 인터페이스를 구현한 예제는 뒤에서 살펴볼 것이다.

 

프로바이더 최범균 ( madvirus@madvirus.net ) :
가메출판사의 'JSP Professional'을 저술하였으며 Oreilly의 'Java and XML'을 번역하기도 하였다. 현재는 티페이지 글로벌의 기술팀에서 근무하고 있다.

3개의 커멘트가 등록되어 있습니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 백성용 헬로우보이
모델 2 구조를 객체 지향적으로 구현하는 방법에 대해서 살펴본다.
프로바이더: 최범균
모델 2 구조의 올바른 구현 방법

1부에서 살펴보았듯이 모델 2 구조에서 모든 클라이언트의 요청은 서블릿으로 전달되며, 서블릿은 그 요청을 알맞게 처리해야 할 책임이 따른다. 여기서 모든 클라이언트의 요청이라는 것은 연관성이 있는 기능등을 함께 묶어 놓은 집합을 의미한다. 예를 들어 게시판을 생각해보자. 게시판은 읽기/쓰기/삭제/목록보기 등의 기능을 필요로 하며 이러한 각 기능을 하나의 서블릿에서 구현하는 것이 바로 모델 2 구조의 알맞은 구현 방법이라 할 수 있다.

언뜻 생각해보면 모든 기능을 하나의 서블릿에서 처리한다는 것이 매우 이상하게 느껴질 것이다. 아마 여러분은 게시판을 서블릿으로 구현하고자 할 때 글 목록을 보여주는 ListServlet, 글을 작성하는 WriteServlet 그리고 글을 삭제하는 DeleteServlet 처럼 기능등을 각각 하나의 서블릿을 통해서 구현하고자 할 것이다. 이는 여러분들이 지금까지 살펴본 대부분의 서적에서는(또한 필자와 이동훈씨가 함쎄 지은 JSP Professional에서 조차도) 게시판의 각 기능을 별도의 서블릿 또는 JSP 페이지에서 처리하고 있기 때문이며, 또한 여러분이 이러한 사고 방식에 익숙해져 있기 때문이다.

이 시점에서 그러면 다음과 같은 질문이 떠오를 것이다.

  그렇다면 모든 일련의 기능을 하나의 서블릿에서 구현하는 것이 정말로 좋은가?

이 질문에 대한 대답은 바로 "그렇다" 이다. 조금은 못미덥겠지만 이 글을 끝까지 읽어나가면 여러 서블릿에 분산시키는 것 보다 하나의 서블릿에서 관련된 일련의 기능을 구현하는 것이(좀더 정확하게 표현하면 관련된 일련의 기능을 제어하는 것이) 효과적이며 또한 확장성도 뛰어나다는 것을 알 수 있을 것이다.

하나의 서블릿에 모든 관련된 기능을 집중시키자!

이제부터 서블릿에서 관련된 모든 기능을 구현하는 것에 대해서 살펴보자. 일단 모든 관련된 기능을 하나의 서블릿에서 구현하기 위해서는 서블릿이 각각의 요청이 어떤 기능을 요구하는 것인지 구분할 수 있어야 한다. 어떤 클라이언트는 게시판 목록을 보길 원할 것이고, 어떤 클라이언트는 글쓰기를 원할 것이고 그리고 어떤 클라이언트는 글에 대한 답변을 작성하기를 원할 것이다. 이처럼 각각의 요청은 서블릿으로부터 서로 다른 서비스를 받길 원하며 서블릿은 이를 구분하여 각각의 요청에 대해 알맞은 응답을 해 주어야 한다.

서블릿이 각 기능을 구분할 수 있는 한 가지 방법은 각 기능마다 고유의 명령어를 부여하는 것이다. 예를 들어, 게시판 목록 보기는 "List" 명령어를, 글 쓰기 작성 폼은 "WriteForm" 명령어를, 그리고 작성한 글을 저장하는 것은 "Write" 명령어를 부여하는 것이다. 이제 클라이언트는 이렇게 기능별로 부여한 명령어를 서블릿에 전달하기만 하면 된다. 몇몇 독자는 짐작했겠지만 서블릿에 명령어를 전달하는 방법은 파라미터를 통해서 이루어진다. 예를 들어, command 라는 파라미터를 통해서 명령어를 전달한다고 할 경우 게시판 목록 보기 요청은 /servlet/BoardServlet?command=List 와 같은 URL을 통해서 표현될 것이다.

각 기능별로 고유의 명령어를 부여했기 때문에 서블릿은 클라이언트가 보내온 명령어에 따라 알맞은 응답을 해 주기만 하면 된다. 간단하게 전체적인 코드의 형태를 보여준다면 다음과 같다.

   public void doGet(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
   
      String command = request.getParameter("command");
      String nextPage = "";
      
      if (command.compareTo("List") == 0) {
         // 목록 보여주기 위한 처리를 한다.
         BoardManager bMgr = BoardManager.getInstance();
         BoardList[] bList = bMgr.getList(request.getParameter("pageno");
         ...
         nextPage = "/board/list.jsp";
      } else if (command.compareTo("WriteForm") == 0) {
         // 글을 작성할 수 있는 폼을 보여준다.
         ...
         nextPage = "/board/writeform.jsp";
      } else if (command.compareTo("Write") == 0) {
         // 사용자가 입력한 데이터를 알맞게 저장한다.
         ...
         nextPage = "/board/write.jsp";
      }
      
      RequestDispatcher rd = request.getRequestDispatcher(nextPage);
      rs.forward(request, response);
   }

위 코드를 보면 command 파라미터의 값에 따라 알맞은 처리를 한 후 결과를 보여줄 JSP 페이지를 nextPage에 저장한느 것을 알 수 있다. 모든 처리가 끝나면 서블릿은 RequestDispatcher를 사용하여 nextPage에서 지정한 JSP 페이지를 보여준다. 위 코드의 경우 간단하게 "..."을 삽입하긴 했지만 실제로 "..."이 대신 완전한 코드를 넣었다면 위 코드는 아마 매우 길어졌을 것이다. 또한 위와 같이 하나의 메소드에 모든 구현을 다 넣는 것이 이상하게 느껴질 것이다.

물론, 위와 같이 하나의 메소드에 모든 기능을 다 넣는 것은 좋지 않은 방법이며 요구하는 기능이 많아질 경우 소스 코드가 복잡해지는 단점이 있다. 일단 덜 복잡한 코드를 작성하기 위해서는 기능들을 각각 별도의 메소드에서 구현해야 한다. 다음은 각각의 명령어를 별도의 메소드를 통해서 처리하도록 수정한 서블릿의 형태이다.

   public void doGet(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
   
      String command = request.getParameter("command");
      String nextPage = "";
      
      if (command.compareTo("List") == 0) {
         nextPage = processListCommand(request, response);
      } else if (command.compareTo("WriteForm") == 0) {
         nextPage = processWriteFormCommand(request, response);
      } else if (command.compareTo("Write") == 0) {
         nextPage = processWriteFormCommand(request, response);
      }
      
      RequestDispatcher rd = request.getRequestDispatcher(nextPage);
      rs.forward(request, response);
   }
   
   private String processListCommand(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
      // 목록 보여주기 위한 처리를 한다.
      BoardManager bMgr = BoardManager.getInstance();
      BoardList[] bList = bMgr.getList(request.getParameter("pageno");
      ...
      return "/board/list.jsp";
   }

   private String processWriteFormCommand(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
      // 글을 작성할 수 있는 폼을 보여준다.
      ...
      return "/board/writeform.jsp";
   }

   private String processWriteFormCommand(HttpServletRequest request,
                     HttpServletResponse response)
                     throws IOException, ServletException {
      // 사용자가 입력한 데이터를 알맞게 저장한다.
      ...
      return "/board/write.jsp";
   }

위 코드를 보면 하나의 메소드에서 하나의 명령어를 처리하는 것을 알 수 있다. 각각의 메소드는 자신의 처리해야 할 명령어에 대한 알맞은 처리를 한 후 request나 session의 setAttribute() 메소드를 사용하여 그 결과값을 저장해서 결과 화면을 보여주는 JSP 페이지에서 사용할 수 있도록 하고, 또한 결과를 보여줄 JSP 페이지를 리턴값으로 돌려준다. 그려면 서블릿의 doGet()이나 doPost() 메소드에서는 메소드가 리턴한 URI(즉, 결과를 보여줄 JSP 페이지)로 포워딩시키면 된다. 이것이 모델 2 구조의 가장 기본적인 구조라 할 수 있다.

명령어 기반 모델 2 구조의 가장 기본적인 구현 형태를 정리하면 다음과 같다.

  public class CommandBaseServlet extends HttpServlet {
     
     public static final String DEFAULT_COMMAND = "...";
     
     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        processCommand(request, response);
     }
  
     public void doProcess(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        process(request, response);
     }
     
     private void process(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        String command = request.getParameter("command");
        if (command == null) command = DEFAULT_COMMAND;
        
        String nextPage = null;
        
        if (command.compareTo("Command1") == 0) {
           nextPage = processCommand1(request, response);
        } else if (command.compareTo("Command2") == 0) {
           nextPage = processCommand2(request, response);
        } else ...
           ...
        }
        RequestDispatcher rd = request.getRequestDispatcher(nextPage);
        rs.forward(request, response);
     }
     
     private String processCommand1(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        // 어떤 처리를 한다.
        ...
        return "/process/command1result.jsp";
     }
     
     ...
     
     private String processCommandN(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        // 어떤 처리를 한다.
        ...
        return "/process/commandNresult.jsp";
     }
  }

위 코드에서 doGet() 메소드와 doPost() 메소드에서 모두 process() 메소드를 호출하는 것을 알 수 있다. 이렇게 함으로써 doGet()과 doPost()에서 중복되는 코드를 없앨 수 있다. 단 하나 주의할 점이 있다면 인코딩 타입이 multipart/form-data로 전송될 경우 추가적으로 이에 알맞은 처리를 해 주어야 한다.

명령어 기반의 모델 2 구조의 구현을 잘 살펴보면 하나의 명령어는 하나의 작업(또는 역할)과 관련된 것을 알 수 있다. 예를 들어, 게시판과 관련된 명령어가 "List", "Write", "Edit"라고 할 경우 이 명령어들 각각은 "목록보기", "글쓰기", "글 수정하기"라는 역할을 나타내고 있는 것이다.

여기서부터 우리는 좀더 객체 지향적으로 나아가 보자! 하나의 명령어가 하나의 작업을 처리한다는 것은 또는 하나의 역할을 의미한다는 것은 각각의 명령어들을 하나의 클래스로 표현할 수 있다는 것을 의미한다. (객체 지향에서 대부분의 클래스는 그 클래스만의 역할을 갖고 있다.) 즉, 커맨드 패턴을 적용할 수 있는 것이다. 커맨드 패턴은 하나의 명령어에 대하여 하나의 클래스를 대응시키는 것으로서 이에 대한 자세한 내용은 필자가 자바캔에 기고했던 글인 '커맨드(Command) 패턴과 그 구현'을 참고하기 바란다. (이 부분을 읽기 전에 먼저 커맨드 패턴에 대한 것부터 반드시 숙지하기 바란다!)

'커맨드 패턴과 그 구현'을 읽었다면 (또는 커맨드 패턴에 대해서 이해하고 있다면) 이제부터 커맨드 패턴을 모델 2 구조에 적용하는 것에 대해서 살펴보자. (이 글에서는 각각의 커맨드의 기능을 실제로 구현한 클래스를 커맨드 클래스라고 표현하고 모든 커맨드 클래스가 공통으로 구현해야 하는 인터페이스를 커맨드 인터페이스라고 표현할 것이다.)

먼저 커맨드 인터페이스에 대해서 살펴보자. 모델 2 구조에서 사용될 커맨드 인터페이스는 HTTP 요청으로부터 정보를 추출할 수 있어야 하고 또한 세션, 요청 객체(HttpServletRequest 클래스의 객체) 등에 접근할 수 있어야 한다. 뿐만 아니라 HTTP 응답 객체(HttpServletResponse 클래스의 객체)를 사용할 수 있어야 한다. 이러한 것을 충족시키기 위해서는 다음과 같이 커맨드 인터페이스를 정의해주어야 한다.

  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import com.board.command.ProcessingException;
  
  public interface CommandIF {
     public String processCommand(HttpServletRequest request,
                                  HttpServletResponse response)
                                  throws ProcessingException;
  }

여기서 processCommand() 메소드의 리턴 타입이 String인 것을 알 수 있는데, 여기서 String은 CommandIF를 통해서 커맨드를 처리한 후 서블릿이 포워딩할 페이지의 URI를 나타낸다. 이에 대한 것은 뒤에서 좀더 구체적으로 설명하도록 하자.

명령어를 처리해주는 모든 커맨드 클래스가 구현해야할 인터페이스를 정의하였으므로, 그 다음으로 해야 할 것은 각각의 명령어에 알맞게 커맨드 클래스를 처리해주는 것이다. 예를 들어, 앞에서 예로 들었던 게시판 관련 명령어 중에서 "List"를 처리해주는 커맨드 클래스를 com.board.command.ListCommand 라고 해 보자. 이 경우 ListCommand 클래스는 다음과 비슷할 것이다.

  package com.board.command;
  
  import com.board.BoardMgr;
  import com.board.BoardData;
  import com.board.command.ProcessingException;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  public class ListCommand implements CommandIF {
     
     public String processCommand(HttpServletRequest request,
                                  HttpServletResponse response)
                                  throws ProcessingException {
        try {
           String pageNo = request.getParameter("page");
           String boardCode = request.getParameter("board");
           
           int page = Integer.parseInt(pageNo);
           
           BoardMgr boardMgr = BoardMgr.getInstance();
           BoardData[] list = boardMgr.getBoardDataAtPage(boardCode, page);
           
           request.setAttribute("boardMgr.boardList", list);
           
           return "/board/list.jsp";
        } catch(Exception ex) {
           throw new ProcessingException(ex);
        }
     }
  }

위 코드에서 ListCommand 클래스의 processCommand() 메소드는 파라미터로 전달받은 request 객체를 통해서 서블릿에서 해야 할 모든 작업을 할 수 있다. (그리고 서블릿에서 해야 할 작업을 커맨드 클래스로 옮겨놓은 것이 커맨드 패턴을 모델 2 구조에 적용한 목적이었다.) 따라서 request 기본 객체의 속성(attribute)을 사용하여 서블릿이나 JSP 페이지에 특정한 값을 전달할 수 있으며, 위 코드의 경우는 게시판 글 목록을 저장하고 있는 BoardData 배열인 list를 request 기본객체의 속성에 저장하고 있음을 알 수 있다.

ListCommand와 비슷하게 모든 다른 명령어에 대해서 각각 하나의 커맨드 클래스를 작성을 하면 비로서 서블릿에서 커맨드 클래스들을 사용할 수 있게 된다. 다음은 서블릿에 커맨드 패턴을 적용했을 때의 코드 형태이다.

  public class BoardServlet extends HttpServlet {
  
     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        process(request, response);
     }
  
     public void doPost(HttpServletRequest request,
                        HttpServletResponse response)
                        throws IOException, ServletException {
        process(request, response);
     }
     
     private void process(HttpServletRequest request,
                          HttpServletResponse response)
                          throws IOException, ServletException {
        String command = request.getParameter("command");
        CommandIF processor = null;
        // 명령어에 따라서 알맞은 커맨드 클래스의 인스턴스를 생성한다.
        if (command == null) {
           command = new NullCommand();
        } else if (command.compareTo("List") == 0) {
           command = new ListCommand();
        } else if (command.compareTo("Write") == 0) {
           command = new WriteCommand();
        }
        
        ...
        
        String nextPage = null; // 명령어를 처리한 후 보여줄 JSP 페이지
        try {
           nextPage = command.processCommand(request, response);
        } catch(ProcessingException ex) {
           request.setAttribute("javax.servlet.jsp.jspException", ex);
           nextPage = "/error/noticeError.jsp";
        }
        ServletContext sc = getServletContext();
        RequestDispatcher rd = sc.getRequestDispatcher(nextPage);
        rd.forward(request, response);
     }
  }

앞에서 살펴봤던 코드가 command 파라미터의 값에 따라 같은 클래스에 정의되어 있는 알맞은 메소드를 호출하였다면, 커맨드 패턴을 적용한 이후에는 알맞은 명령어에 따라 알맞은 커맨드 클래스의 인스턴스를 생성하고 그 인스턴스의 processCommand() 메소드를 호출한다는 점이 다르다.

팩토리 패턴을 커맨드 패턴과 함께 사용한다면 위 코드는 더욱 간단해진다. 예를 들어, CommandFactory 라는 클래스가 있고, 이 클래스의 createCommand(String command) 메소드는 파라미터로 전달받은 command의 값에 따라 알맞은 커맨드 객체를 리턴한다고 해 보자. 이는 다음과 같이 CommandFactory를 통해서 커맨드 객체를 생성할 수 있다는 것을 의미한다.

  CommandIF processor = CommandFactory.createCommand("List");

이를 모델 2의 서블릿 코드에 적용하면 다음과 같이 변한다.

  public class BoardServlet extends HttpServlet {
  
     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
                       throws IOException, ServletException {
        process(request, response);
     }
  
     public void doPost(HttpServletRequest request,
                        HttpServletResponse response)
                        throws IOException, ServletException {
        process(request, response);
     }
     
     private void process(HttpServletRequest request,
                          HttpServletResponse response)
                          throws IOException, ServletException {
        String command = request.getParameter("command");
        CommandIF processor = CommandFactory.createCommand(command);
        String nextPage = null; // 명령어를 처리한 후 보여줄 JSP 페이지
        try {
           nextPage = command.processCommand(request, response);
        } catch(ProcessingException ex) {
           request.setAttribute("javax.servlet.jsp.jspException", ex);
           nextPage = "/error/noticeError.jsp";
        }
        ServletContext sc = getServletContext();
        RequestDispatcher rd = sc.getRequestDispatcher(nextPage);
        rd.forward(request, response);
     }
  }

if - else 가 사라짐으로써 서블릿 코드가 매우 간결해진다는 것을 알 수 있다.

 
프로바이더 최범균 ( madvirus@madvirus.net ) :
최범균씨는 최근에 출간된 'JSP Professional'(가메출판사)을 이동훈씨와 함께 저술하였으며, 현재 e-마켓플레이스 티페이지 글로벌에서 근무하고 있다. 한빛출판사의 'Java and XML'을 번역하기도 하였다
2개의 커멘트가 등록되어 있습니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 백성용 헬로우보이
모델2 구조에 대한 기본적인 내용을 살펴본다.
프로바이더: 최범균
모델1 구조

JSP는 이제 더 이상 일부 자바 개발자만 사용하는 기술이 아닌 웹 프로그래밍을 해야 하는 개발자들이 알아야 하는 기술이 되고 있다. 서점에 가보면 20여종에 가까운 JSP 서적이 존재하고 있으며 JSP 서적을 찾는 사람들 역시 많아지고 있다.

JSP가 이처럼 인기를 얻어 가고 있지만 막상 JSP 자체를 올바르게 사용하는 개발자는 매우 드문 것 같다. 특히 JSP 페이지로만 모든 걸 해결하는 개발자들도 많다고 볼 수 있다. 심지어는 ASP 코드를 그대로 JSP에서 사용하고(물론, VB 스크립트를 Java 언어로 변경하지만) 또는 PHP와 비슷한 형태로 사용하는 경우도 있다. 하지만, JSP가 세상에 나온 이유 중의 하나는 로직(logic)과 프리젠테이션(presentation)을 구분하기 위한 것이었다.

JSP는 로직과 표현부를 알맞게 구분하기 위해 모델 2 구조를 지원하고 있다. 이 글에서는 모델 2 구조가 무엇이며 어떤 식으로 구현되는 지, 그리고 모델 2 구조를 사용했을 때의 장단점에 대해서 살펴보도록 하자.

모델 1 구조와 단점

모델 2 구조에 대해 알아보기 전에 먼저 모델 1 구조에 대해서 살펴보자. 모델 1 구조는 JSP 페이지만으로 구성되어 있는 구조를 말한다. 즉, JSP 페이지에서 동적인 부분(즉, 로직 부분)은 스크립트릿으로 처리하고 그외 나머지 부분은 템플릿으로 처리하는 것이다. 그림1은 모델 1 구조의 전체 흐름을 보여주고 있다.


모델 1 구조의 전체 흐름

위 그림에서 클라이언트는 JSP 콘테이너에 HTTP 요청을 전송한다. 그러면 JSP 콘테이너는 HTTP 요청에 따라 알맞은 JSP에 그 요청을 전달하며, JSP 페이지는 클라이언트의 요청을 알맞게 처리한 후 응답을 클라이언트에 전송한다. 여기서 JSP 페이지는 클라이언트의 요청을 알맞게 처리하는 로직 부분을 구현하고 있다. 때에 따라 자바빈 컴포넌트에 로직 부분을 옮길 수도 있으나, 대부분의 경우 자바빈 컴포넌트는 단순히 데이터를 저장하는 역할만을 맡게 되며 모델 1 구조에서 JSP 페이지는 로직과 프리젠테이션의 역할을 동시에 맡게 된다.

예를 들어, 사용자가 특정한 JSP 페이지를 몇번 보았는지 알려주는 것을 생각해보자. 이 경우 세션이나 쿠키를 사용하여 사용자가 방문한 회수를 저장할 수 있다. 세션을 사용한다고 할 경우, JSP 페이지만을 사용하는 모델 1 구조에서는 다음과 같은 JSP 페이지를 작성할 것이다.

  <%@ page contentType="text/html; charset="euc-kr" %>
  <%@ page import="SessionCountBean" %>
  <%
     SessionCountBean count = (SessionCountBean)session.getAttribute("count");
     if (count == null) {
        count = new SessionCountBean();
        session.setAttribute("count", count);
     } else {
        count.increase();
     }
  %>
  <html>
  <head><title>방문회수</title></head>
  <body>
  지금까지 <%= count.getPageCount() %> 만큼 이 페이지에 방문하셨습니다.
  </body>
  </html>

위 코드를 보면 알겠지만 이 간단한 JSP 페이지에서조차도 HTML 코드 못지 않게 많은 양의 스크립트 코드가 JSP에 삽입되어 있다. JSP 페이지에서 데이터베이스부터 시작해서 모든 걸 처리한다고 할 경우 스크립트 코드와 HTML 코드는 스파게티처럼 막 뒤 섞이게 된다.

이렇게 로직 부분과 프리젠테이션 부분이 함께 섞여 있는 것은 JSP 개발자와 웹 디자이너가 함께 작업하는 데 많은 불편을 제공한다. 예를 들어, 개발자는 웹 디자이너가 작업한 HTML 문서의 알맞은 위치를 찾아서 스크립트 코드를 삽입해야 한다. 반대로 웹 디자이너는 디자인을 변경해야 할 때 최소한 JSP 스크립트 코드가 어떤 것인지 알아야 하며, 또한 HTML 코드 사이에 난잡하게 존재하는 스크립트 코드 때문에 사소한 디자인 변경 작업조차도 어려운 상황이 발생한다. 심지어, 개발자가 없이는 디자인을 변경하지 못하는 상황이 발생하기도 한다.

물론 위 코드를 다음과 같이 변경할 수도 있다.

  <%@ page contentType="text/html; charset="euc-kr" %>
  <jsp:useBean id="count" scope="session" class="SessionCountBean" />
  <%
     SessionCountBean count = (SessionCountBean)session.getAttribute("count");
     count.increase();
  %>
  <html>
  <head><title>방문회수</title></head>
  <body>
  지금까지 <jsp:getProperty name="count" property="pageCount" />
  만큼 이 페이지에 방문하셨습니다.
  </body>
  </html>

위 예제는 자바빈 컴포넌트 관련 JSP 태그를 사용한 것이다. 이전 예제에 비해 확실히 스크립트 코드의 양이 준것을 알 수 있다. 하지만, 여전히 JSP 페이지에서 로직과 관련된 부분을 처리하고 있으며 좀더 복잡한 로직을 갖는 웹 어플리케이션을 개발해야 할 경우 JSP 페이지는 로직과 프리젠테이션이 복잡하게 뒤섞이는 스파게티 코드가 될 것이다.

지금까지 살펴본 바와 같이 JSP 페이지가 모든 역할을 맡는 모델 1 구조의 경우는 JSP의 기본 목적인 로직과 프리젠테이션의 분리를 제대로 할 수 없음을 알 수 있다. 물론, 기본의 CGI나 서블릿에 비교해보면 많은 발전이 있지만 ASP나 PHP와 비교해볼 때 별다른 장점을 보이고 있지 못하다.

 
관련링크:

프로바이더 최범균 ( madvirus@madvirus.net ) :
최범균씨는 최근에 출간된 'JSP Professional'(가메출판사)을 이동훈씨와 함께 저술하였으며, 현재 e-마켓플레이스 티페이지 글로벌에서 근무하고 있다. 한빛출판사의 'Java and XML'을 번역하기도 하였다.

4개의 커멘트가 등록되어 있습니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 백성용 헬로우보이
출처 : http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=32089

오늘도 개발자들은 고객의 요구사항에 따라 프로그램을 개발한다. 개발하는 틈틈이 최신 기술을 공부하면서 더 효율적이고 생산적으로 개발하는 방법을 익히고자 한다. 필자는 이 글을 통해 앞만 보고 열심히 일하는 개발자들에게 거시적인 관점에서 우리들의 주변 환경이 어떻게 돌아가고 있는지를 되돌아볼 시간을 마련해 주고자 한다.

한 용 희 woom33@korea.com롯데정보통신 정보기술연구소에 재직 중이며, 닷넷 기반의 여러 프로젝트에 참여했다. 현재 Microsoft Visual C# MVP이며 MSDN 세미나 강사로도 활동 중이다. 처음에는 2D, 3D 게임 프로그래머로 시작하여 SQLServer 튜닝, 응용 애플리케이션 개발에 이르기까지 다양하게 경험하였으며, 주요 관심사는 DB와 애플리케이션의 연동 부분이다.


어렸을 적부터 필자는 개발하는 일 자체가 즐거웠다. 내가 어떤 것을 만들 수 있다는 것 자체가 매우 흥분되고 재미있는 일이었다. 기존의 장난감에는 창의력을 불어 넣을 수 없었다. 레고 블록과 같이 모양이 정해진 부품으로는 완전히 새로운 창작물을 만들 수 없었다. 하지만 프로그래밍은 훨씬 더 새로운 작품을 만들 수 있어서 좋았다. 필자는 그렇게 프로그래밍의 재미라는 것을 느꼈고 그 순간부터 커서 개발자가 될 것이라고 마음에 꿈을 가졌다.

프로그래밍의 재미를 알고부터는 시간이 나면 틈틈이 프로그래밍을 했다. 이러한 재미가 직업이 되면서부터 일은 필자에게 돈을 버는 수단인 동시에 재미를 느끼는 수단이 되었다. 하지만 언젠가부터 개발이라는 작업이 내가 원하는 것만 만들 수 있는 것도 아니었고, 내가 원하는 시간에 내 마음대로 혼자서 할 수 있는 것도 아닌 그런 존재가 되었다. 개발이라는 것이 결코 혼자만의 예술 작품이 아니었던 것이다.

필자는 대학 때까지 열심히 프로그래밍 기술을 익히고 기술 공부에 매진했다. 그러다가 지금의 SI 업체에 입사하게 되었는데, 그때 인사과장님이 하시던 말씀이 생각난다. “앞으로 여러분은 회사에 들어가서 열심히 업무를 배워야 합니다. 기술도 중요하지만 앞으로 하게 될 회사 업무(인사, 구매, 회계, 생산, 영업 등)를 파악하는 것이 더 중요합니다. 고객이 원하는 것을 알아야만 개발도 효과적으로 할 수 있기 때문입니다.” 그동안 기술 공부만 열심히 해왔던 필자에게는 충격적인 말이었다. 내가 지금껏 ‘헛공부’를 했단 말인가?

개발자로서 선택할 수 있는 다양한 진로가 존재한다. 대표적인 것이 바로 IT 제조업과 IT 서비스업이다. IT 제조업은 제품을 만드는 것이므로 기술 자체가 중요할지 모르겠다. 하지만 IT 서비스업은 서비스를 만드는 것이므로 그 해당 업무를 잘 아는 것이 더 중요했던 것이다.

필자는 회사에 들어가서 제일 먼저 회계 업무를 맡았다. 개발하는 것 자체에는 아무런 문제가 없었지만 현업이 요구하는 것을 개발하는 데 문제가 생겼다. 회계 업무를 모르다 보니 현업이 하는 말을 알아들을 수 없었고, 현업이 원하는 바를 이해해서 원하는 것을 정확하게 만들 수 없었다. 이때부터 회계 공부를 시작했다. 당장 회계학과 대학생들이 본다는 『회계원리』라는 책을 사서 공부를 시작했다. 하지만 처음부터 회계용어가 눈에 들어올 리가 없었다. 난생 처음 듣는 어려운 용어가 많았다. 그때마다 회계과 직원들의 도움을 받고, 스스로 다른 책도 찾아가면서 회계 공부를 했다.

처음에는 어렵기만 했던 회계 용어가 차츰 눈에 익고, 점점 업무 영역도 확대되어 갔다. 회계, 그룹웨어, 인사 등 여러 업무를 조금씩 하다가 이제는 직접 개발하기보다는 관리, 기획하는 업무의 비중이 점점 늘어가는 것을 느끼게 되었다. 바야흐로 개발자로서 선택의 갈림길이 나타난 것이다. 계속 개발자로 남느냐? 아니면 관리직으로 가느냐?


한국에서 개발자로 살아가기
개발자로 살아가다 보면 국내에서는 개발자로 살아가기가 힘들다는 소리를 여기저기서 많이 듣게 된다. 지난해 8월 자바개발자협의회(JCO)는 한국의 개발자 1,891명을 대상으로 설문 조사를 실시한 바 있다. 그 결과에 따르면 국내 소프트웨어 개발자의 72.2%는 45세까지(특히 58%는 40세까지)만 개발 업무를 계속하겠다고 응답했다. 대다수가 개발자의 수명을 40세 초반으로 바라보고 있는 셈이다. 경력 분포 면에서는 10년 이상된 개발자가 전체의 9.5% 밖에 되지 않아서 대부분이 경력이 짧은 초중급 개발자로 이뤄져 있음을 알 수 있다. 그만큼 숙련된 개발자가 부족하다는 의미이다.

개발자의 근무 실태를 보면, 응답자의 85%는 주2회 이상 야근을 하며 매일 야근하는 개발자도 28%나 되었다. 하지만 이들에게는 초과 근무 수당이 없는 경우가 대다수였다. 한 예로 노동부에서 2007년 6월부터 서울 지역 IT 업체 104곳을 점검한 결과, 93개 업체가 근로자의 수당을 미지급해서 시정 명령을 받았다고 한다.  

과거 2005년도에는 포털 서비스인 다음의 토론광장(아고라)에 ‘영재들아, 제발 IT로 오지마라’라는 글이 게재되어 한창 화제가 되기도 했다. 이 글은 한국 IT 개발 환경의 문제점을 적나라하게 표현해서 IT 종사자들 사이에서 대단한 관심을 끌었다. 이런 우울한 글을 보고 나면 정말 개발자로 남아서 살아가야 하는지 자신이 없다. 유독 한국의 개발자만 이렇게 고생하는 것은 아닐까?


임금 수준으로 본 우리 IT
이럴 때에는 선진국의 사례를 살펴보면 도움이 된다. 필자는 우리나라보다 IT 산업이 성숙한 미국의 자료를 찾아보기로 했다. PayScale이라는 연봉 정보 공개 사이트에는 각 직업의 연봉 정보가 공개되어 있다. 먼저 미국의 연봉 정보를 보자.



<화면 1>에서 뜻밖에도 고급 개발자(Sr. Software Engineer / Developer / Programmer)의 연봉이 최고 수준으로 나와 있음을 볼 수 있다. 이 사이트에는 한국의 연봉 정보도 함께 소개되어 있다. 비록 등록된 정보가 얼마 없어 절대적인 기준으로 삼기는 어렵지만 그냥 참고용으로는 볼만 하다. <화면 2>는 한국의 연봉 정보를 나타낸 그래프다.



이 자료에는 구체적인 수치가 나와 있진 않지만, 개발자의 임금 수준이 꽤 낮은 수준임을 짐작할 수 있다. 한국소프트웨어진흥원에서 한미일 3국의 소프트웨어 개발자 월평균 임금(2003년 기준)을 조사한 자료가 있는데 그 결과는 <표 1>과 같다.

<표 1>은 해당 국가의 물가를 고려하지 않고 임금을 단순 비교한 자료이므로 이것으로 절대적인 비교는 어렵지만 대략적으로 보면 미국의 임금이 한국의 3배에 이른다. 단순 절대 임금의 비교보다는 상대적 임금 수준을 비교하기 위해 임금 프리미엄이라는 지수를 사용한다. 임금 프리미엄이란 현재 임금에서 기회 임금을 뺀 것으로 그 직업의 상대적인 임금 수준을 측정할 때 유용하다. 즉, 내가 IT 직업을 가짐으로써 다른 직업을 가졌을 때보다 더 많이 받는 수준이 임금 프리미엄이다.

정보통신정책연구원에서 2003년도 발간된 자료를 보면 한국 IT 산업 근로자의 임금 프리미엄은 10%, IT 직종 종사자의 임금 프리미엄은 IT 전문직이 61%, 준 전문직이 11%, 생산직이 3%였다. 반면에 미국 IT 산업 종사자의 임금 프리미엄은 110.8%로 나와 있다. 즉, 미국에 비해서는 그만큼 한국의 IT 직종 종사자가 상대적으로 임금을 덜 받고 있음을 알 수 있다.


근로 환경은 어떤가?
ww.esj.com이라는 사이트에 가보면 미국 IT 종사자에 대한 설문조사 결과를 볼 수 있다. 가장 최근의 자료는 2007년 10월에 조사한 것으로, 이 가운데 먼저 직업 만족도를 보면 <표 2>와 같다.




미국의 IT 종사자들은 갈수록 직업 만족도가 떨어지기는 하지만 대체적으로 직업에 만족하고 있음을 알 수 있다. 그럼 이번에는 직업 안정성에 대해 알아보자. 이는 자신의 직장에서 퇴직 당하지 않고 얼마나 안정적으로 일할 수 있는지에 대한 설문조사 결과이다.


<표 3>을 보면 대체적으로 직업 안정성이 높음을 알 수 있다. 마지막으로 주당 근무 시간에 대한 설문 조사 결과는 <표 4>와 같다.


아무래도 직원보다는 관리자가 더 업무를 오래하는 편인데, 직원들의 주당 근무 시간은 평균 43시간으로 조사됐다. 9시 출근 6시 퇴근이라 하고 점심시간 1시간을 제외하면 일일 근무시간은 8시간이다. 이는 주5일제라고 할 때 주당 40시간이 된다. 미국에서 43시간이라는 것은 결국 어느 정도 야근을 한다는 것을 의미한다.

그럼 우리나라의 경우는 어떨까? 2004년 전국IT산업노동조합연맹이 실시한 실태조사에서 소프트웨어 종사자들의 주당 평균 근로시간은 57.8시간이었다. 이는 미국과 비교해 보면 많은 차이를 나타내는 결과다. 결국 한국에서 개발자로 살아가는 것이 힘들다고 말하는 데에는 이런 이유가 있는 셈이다.


산업 분류에 따른 IT 전망
|앞서 말한 바와 같이 IT 산업은 크게 IT 제조업과 IT 서비스업으로 나눌 수 있다. 이중 우리나라는 IT 제조업의 비중이 매우 높은 편이다. <표 5>는 2005년도 IT 부문별 부가가치 및 고용 비중을 나타낸 것이다.


한편 미국의 경우는 <표 6>과 같다.


미국의 경우 IT 서비스업의 부가가치가 더 크고 인력도 더 많은 것으로 조사됐다. 하지만 우리나라의 경우에는 IT 제조업의 부가가치가 더 크고 인력도 더 많은 것으로 나타났다. 우리나라는 아직까지 IT 산업에 있어서 제조업의 비중이 더 크다는 의미다.

미국의 경우 IT 서비스업의 부가가치나 인력이 더 많은 것은 서비스 부문에서 IT를 활용한 전산화가 생산성 향상을 가져왔기 때문이다. 미국도 1995년 이전에는 제조업의 생산성이 서비스업보다 더 높았다. 하지만 IT 기술이 발전한 1995년 이후에는 IT 기술을 활용한 서비스 분야의 전산화를 통해서 생산성이 크게 향상됐다.

반면에 우리나라의 경우 2000년대 들어 IT 생산 부문은 노동생산성 향상에 크게 기여하고 있으나, IT 이용 서비스의 경우에는 오히려 기여도가 하락하고 있다.

<그림 1>을 보면 우리나라는 생산성을 높이는 데 있어 IT 이용 서비스업의 기여도가 떨어지고 그 비중 또한 작다. IT 제조업의 경우 각종 IT 제품을 통해서 생산성 향상을 가져오는데 반해, IT 서비스업의 경우는 소프트웨어를 통해 업무의 효율화를 가져와야 하는데 그 비중이 작다는 것이다.


우리 개발자의 미래
과거 미국의 경제가 일본에 추월당했다는 소리가 있었지만 미국은 IT를 통한 기술 발전에 힘입어 생산성의 향상을 가져왔고 다시금 경제에 활력을 되찾았다. IT 산업이 경제 성장의 밑거름이 된 것이다. 특히 IT 서비스업의 경우 IT 제조업보다 더 큰 부가가치와 고용 증대 효과를 가져왔다.



앞으로 우리나라도 경제가 더욱 성장하기 위해서는 미국이 그랬던 것처럼 각 산업에서의 생산성 증가가 이뤄져야 한다. 이러한 생산성 증대는 앞으로는 IT 기술을 통해 이뤄질 것이다. 특히 그동안 등한시되었던 IT 서비스를 통한 서비스업의 생산성 증가가 앞으로 중요한 이슈가 될 것이다. 이에 따라 IT 서비스업을 담당하는 소프트웨어 개발자가 앞으로는 더욱 많이 필요해지고 중요한 인력이 될 것으로 보인다.

앞서 살펴본 것처럼 우리나라에서 개발자에 대한 처우는 미국에 비해 그리 좋은 상황은 아니다. 하지만 앞으로 우리가 미국의 모델을 따라 IT 빅뱅을 통한 경제발전을 이룩한다면 소프트웨어 개발자는 그 주요한 원동력이 될 것이다. 필자는 그때쯤이면 우리나라의 개발자도 미국의 그들처럼 대우받지 않을까라는 나름대로 긍정적인 희망을 가져본다.


참고 자료
1. 자바개발자협의회 커뮤니티, http://www.jco.or.kr
2. 미국 연봉 정보 사이트, http://www.payscale.com
3. SW인력 임금수준의 국제 비교 - 한국소프트웨어진흥원(박능윤), http://www.software.or.kr
4. IT 인력의 취업률, 전공종사율, 임금수준에 대한 연구 - 정보통신정책연구원, http://www.kisdi.re.kr
5. Enterprise System, http://www.esj.com
6. 주력성장산업으로서 IT 산업에 대한 평가와 시사점 - 한국은행, http://www.bok.or.kr

이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 백성용 헬로우보이
스프링 프레임워크 개요
스프링은 그 이름 자체로도 많은 의미를 내포하고 있다. 봄! 이 얼마나 설레는 단어인가? 봄이라는 이름만으로도 무거운 J2EE의 사용으로 지친 개발자들에게 이제 겨울이 끝나고 새로운 계절이 돌아오고 있음을 함축적으로 표현해내고 있다. 스프링은 로드 존슨이 쓴 「Expert one-on-one J2EE Design and Development」란 책에서 소개된 소스코드를 기반으로 2003년 2월 오픈소스로 시작된 프로젝트이다. 스프링이 추구하는 바는 크게 두 가지이다.

[1] 복잡하고 무거운 J2EE 기술의 사용을 쉽고 가볍게 만들어주고, 자연스럽게 검증된 최상의 실천 사례들을 구현하도록 함으로써 좋은 프로그램이 작성될 수 있도록 유도한다.

[2] 기존의 잘 알려진 기술들을 프레임워크 내에서 일관된 방법으로 쉽게 사용할 수 있도록 돕는다.

이를 위해 스프링은 다른 프레임워크와는 차별화된 다음과 같은 특징을 가진다.

◆ 스프링은 EJB를 사용하건 하지 않건 관계없이 비즈니스 객체들을 효과적으로 구성하고, 관리하는 방법을 제공하는 데 초점을 맞춘다.

◆ 스프링은 계층화된 아키텍처를 갖고 있으며, 그 중 어떤 부분도 독립적으로 사용될 수 있도록 모듈화되어 있다. 뿐만 아니라 각각의 모듈은 일관된 방법으로 사용할 수 있기 때문에 한번 익숙해지고 나면 사용이 무척 쉽다.

◆ 스프링은 전체 프로젝트의 설정을 관리할 수 있는 일관된 방법을 제공함으로써, 개발자들이 각종 프로퍼티 파일을 작성하지 않도록 유도한다. 이것은 IoC라는 스프링의 특징 때문인데, 객체들간의 의존성이 따로 관리됨으로써 비즈니스 로직이 EJB로 개발되었건 일반 자바 객체로 개발되었건 동일한 방법으로 해당 로직을 이용할 수 있는 이점도 추가된다.

◆ 스프링 기반으로 작성된 애플리케이션은 스프링의 API에 의존하지 않는다. 이것은 어떤 애플리케이션 서버와도 쉽게 연동되도록 하며, 심지어 스프링을 사용하지 않았을 때조차도 비즈니스 로직의 재사용이 가능해지는 요인이 된다.

◆ 스프링은 AOP 지원을 통해 주요 비즈니스 로직과 시스템 전반에 걸친 기능 모듈을 완벽히 분리해내도록 도와준다.

◆ 스프링은 작성된 코드에 대한 유닛 테스트를 쉽게 할 수 있도록 도와준다.

스프링의 기능과 사용 시나리오
현재 스프링은 1.0 버전이 출시된 상태이다. 스프링 홈페이지(www.springframework.org)에서 spring-framework-1.0.2-with-dependencies.zip 파일을 다운받기 바란다. 이 파일은 의존성 있는 관련 라이브러리가 모두 포함된 버전이다. 스프링의 전체 기능은 크게 7개의 모듈로 구성된다(<표 1>).

<그림 2> 스프링의 기능 요소


<표 1> 스프링의 기능 요소

스프링 배포 파일의 압축을 풀면 dist란 디렉토리가 나타난다. 그 안에 있는 spring.jar가 앞에서 언급한 스프링의 모든 기능을 포함하는 파일이다. 각각의 기능 중 필요한 부분을 따로 사용할 경우를 위해 패키지별로 묶은 별도의 JAR 파일이 함께 제공된다.

스프링을 이용한 일반적인 형태의 웹 애플리케이션은 <그림 3>과 같은 구조를 가진다. 톰캣, 웹로직, 웹스피어, JBoss를 포함한 어떤 웹 애플리케이션 서버에서도 동작된다. IoC 컨테이너의 핵심인 Core 패키지와 AOP 지원을 위한 AOP 패키지, 기능을 구현한 빈 객체에 대한 접근을 제공하는 Context 패키지는 반드시 포함되어야 한다. 대부분 데이터베이스 처리를 수행하기 때문에 DAO 패키지도 일반적으로 포함된다.

스프링 애플리케이션은 일반적으로 ORM 솔루션을 채택하고, 특히 하이버네이트와 궁합이 잘 맞기 때문에 하이버네이트를 설치하고 ORM 패키지를 이용하는 경우가 일반적이다. 그 위에서 Web 패키지를 기반으로 Web MVC 패키지를 이용해서 애플리케이션을 개발한다.

<그림 3> 시나리오 1. 완전한 형태의 스프링 웹 애플리케이션

만일 스프링의 Web MVC 패키지를 이용하지 않고, 스트럿츠나 웹워크를 제어 계층에 사용하고 싶다면 Web MVC를 스트럿츠가 대체하는 <그림 4> 외에 같은 구조로 웹 애플리케이션이 개발될 것이다.

<그림 4> 시나리오 2. 서드파티 WAF와 ORM을 연계한 웹 애플리케이션

이 밖에도 EJB를 사용하는 경우 AbstractEnterpriseBean이라는 POJO를 이용해서 EJB를 스프링에서 관리하도록 하고, SlsbInvoker를 이용해서 EJB에 대한 접근 경로를 제공하는 EJB 사용 유형이 있다. 또한 웹 서비스를 포함한 다른 애플리케이션과 연동하는 경우를 위해 Remote 패키지가 제공되기도 한다.

스프링은 그 자체로도 한 권의 책을 쓸 수 있을 만큼 방대한 기술이다. 그 모두를 짧은 연재를 통해 소개한다는 것은 불가능하다. 그러한 이유로 스프링에 대한 소개는 이쯤에서 마무리하고, 스프링을 이해하기 위해 반드시 알아야 하는 IoC(Inversion of Control) 컨테이너의 개념과 AOP(Aspect Oriented Programming)를 간단히 설명하고, 2개의 작은 예제를 소개하는 것으로 이번 글을 마무리하겠다. 부족한 설명은 필자들이 운영하는 VSSH 포럼이나, 스프링 관련 기사 및 자료들을 정리한 리소스 맵을 통해 여러분 스스로 익혀나가기를 바란다.

IoC 컨테이너와 AOP
스프링은 다른 프로젝트에서 개발된 컴포넌트를 조립해서 응집력 있는 애플리케이션의 개발이 가능하도록 도와주는 IoC 컨테이너이며, 다른 말로 경량급 컨테이너(Lightweight Container)라고도 한다. IoC 컨테이너의 또 다른 종류로는 PicoContainer와 아파치의 아발론, 그리고 HiveMind 등이 있다.

그 중에서 현재 가장 널리 사용되고 있는 것은 스프링과 PicoContainer이다. IoC 컨테이너는 다른 컨테이너와 달리 애플리케이션 코드와 컨테이너간의 의존성을 최소화하는 것이 특징이다.

IoC 컨테이너가 컨테이너에 대한 의존성을 최소화하면서 컴포넌트를 엮어주는 일을 수행하는 밑바탕에는 제어 역행화(Inversion of Control)라는 개념이 깔려 있다. 제어 역행화는 리팩토링의 저자이기도 한 마틴 파울러의 홈페이지에 잘 정의되어 있다.

제어 역행화라는 용어는 직관적이지 못하기 때문에, 또 다른 말로 연관성 삽입(Dependency Injection)이라고도 불려진다. 연관성 삽입 패턴은 컴포넌트의 설정을 그것의 사용에서 분리해야 한다는 원칙(The principle of separating configuration from use)에서 출발한다. 그러한 원칙을 위한 또 다른 사례는 J2EE 패턴 중 서비스 로케이터(Service Locator) 패턴이다.

의존성 삽입 패턴을 이해하기 위해 간단한 예를 하나 살펴보도록 하자. 어느 특정 감독이 만든 영화를 검색해서 그 결과를 전달해주는 컴포넌트를 사용하는 것이다. 코드에서 보여지는 findAll()이라는 메쏘드를 가지는 finder 객체가 필요하단 사실을 알 수 있다.

일반적으로 이럴 때 기능 확장을 위해 MovieFinder와 같은 인터페이스를 작성하게 된다.

public interface MovieFinder {
    List findAll();
}

영화 정보가 콜론으로 구분된 CSV 파일에 기록되어 있다면, MovieFinder 인터페이스를 구현한 ColonDelimitedMovieFinder 클래스가 필요할 것이다. 또한 그 정보는 MovieLister에 생성자를 이용해서 초기화될 것이다.

class MovieLister...
    private MovieFinder finder;
    public MovieLister() {
        finder = new ColonDelimitedMovieF