Summary : 지난 강좌에서는 스트러츠의 Configuration 파일을 설정하는 과정에 대하여 살펴보았다. 이번 강좌에서는 스트러츠에서 중심이 되는 클래스를 살펴봄으로서 스트러츠에 대하여 더 깊이 있게 이해하고자 한다. 스트러츠에서 핵심이 되는 클래스가 몇개 되지 않기 때문에 각 클래스에 대하여 이해한다면, 스트러츠를 유용하게 사용할 수 있을 것으로 생각된다.
처음 스트러츠를 접한 개발자들은 무엇을 먼저 해야할지 망설이는 경우가 많다. 스트러츠 API를 보면 "왜 그리 많은 패키지와 클래스들이 있는지..", "이것을 언제 다 익힌단 말인가?" 하고 어려워하는 개발자들이 많다. 하지만 스트러츠가 Model 2에 기반하고 있기 때문에 Model 2개발 방식에 대하여 이해하고 있는 개발자들이라면 그 원리를 파악하기란 생각보다 어렵지 않다. Model 2에 대하여 이해하지 못하고 있는 개발자라면 스트러츠 두번째 강좌를 통하여 Model 2에 대하여 더 깊이 있게 이해한 후 이 강좌를 보면 더 좋을 것이다. 스트러츠 별거 아니다라는 생각이 들 것이다.
먼저 스트러츠의 중심을 이루고 있는 클래스들의 클래스 다이어그램을 통하여 스트러츠의 전체적인 그림을 한번 그려 보도록 하겠다.
스트러츠에서 중심이 되는 클래스를 위와 같이 표현해 보았다. 위 클래스 다이어 그램에서 각각의 클래스가 담당하고 있는 역할에 대하여 간략하게 살펴보도록 하겠다.
각각의 클래스를 Model 2강좌에서 설명했던 게시판 예제와 비교해 가면서 설명한다. Model 2강좌의 게시판 예제와 비교해 가면서 설명하면 복잡한 스트러츠의 구조가 생각보다 쉽게 이해될 수 있을 것이다. Model 2강죄의 게시판예제는 http://www.javajigi.net/javajigi/client/lecture/lecture_print.jsp?no=5#3를 참조하기 바란다.
게시판 예제에서 가장 중심이 되는 클래스는 BoardServlet였다. HttpServlet을 상속하는 이 클래스는 모든 Request를 받아 처리하는 역할을 한다. 모든 Request를 이곳에 중앙 집중적으로 모이도록 하는 역할은 web.xml에서 *.m2를 BoardServlet에 Mapping함으로서 가능했다. 스트러츠에서도 BoardServlet과 같은 역할을 하는 것이 있다. 설명하지 않아도 알겠지만 위 클래스 다이어 그램에서 ActionServlet이 모든 Request를 받아 처리하는 역할을 한다. 물론 강좌 3에서 살펴보았듯이 web.xml에 *.do를 ActionServlet에 Mapping함으로서 확장자가 .do인 모든 Reqeust를 ActionServlet에서 처리하도록 하였다.
BoardServlet에서는 단지 하나의 게시판에 해당하는 Request를 처리하기 때문에 BoardServlet하나만으로 충분하다. 하지만 스트러츠는 하나의 Framework으로서 게시판 뿐만 아니라 Application에서 발생하는 모든 Request를 받아서 처리하도록 하였다. 많은 일을 ActionServlet에 집중시키다보니 ActionServlet의 덩치가 너무 커졌다.(스터러츠 버전 1.0에서는 ActionServelt하나에서 모두
처리했다.) 덩치가 너무 커지다 보니까 클래스를 분리하게 되었다. 분리된 클래스가 RequestProcessor클래스이다. 따라서 BoardServlet에서 담당했던 전체적인 조율의 몫을 스트러츠에서는 ActionServlet과 RequestProcessor에서 분담하여 처리하게 되었다.
게시판 예제에서 Action인터페이스에 해당하는 역할을 하는 것이 스트러츠에서는 Action클래스이다. 게시판의 Action 인터페이스는 단지 execute()메써드만을 가지지만 스트러츠에서는 execute()메써드 이외에도 몇개의 추가 메써드를 제공한다.
게시판 예제에서는 사용자가 요구한 작업을 완료후 forward할 위치를 String으로 반환하였다. 스트러츠에서는 게시판 예제는 Forward URL에 해당하는 부분을 ActionForward라는 클래스를 두어 담당하도록 하였다.
게시판 예제와 관련된 클래스들은 위 4개이다. 나머지 3개는 게시판 예제에서는 볼 수 없는 부분이다. 나머지 3개의 클래스에 대하여 설명하면 다음과 같다.
게시판 예제에서는 Forward할 정보들을 Action을 상속하여 구현하는 클래스의 소스코드에 직접 입력하였다. 이 같이 할 경우 Forward URL이 바뀌거나 할 경우 소스코드를 수정후 새로 컴파일해야하는 불편함이 있다. 따라서 스트러츠에서는 모든 정보들을 struts-config.xml에 저장하여 관리하도록 하였다. Application이 시작할 때 struts-config.xml의 모든 정보를 읽어 관리하는 역할을 담당하는 클래스가 ActionMapping클래스이다. ActionMapping클래스처럼 Application의 Action클래스, Forward URL, Exception등의 정보를 관리하는 클래스가 존재하기 때문에 여러개의 Servlet이 없이 하나의 ActionServlet만 있어도 되는 것이다.
사용자가 입력하거나 페이지에 의하여 전달되는 모든 정보는 Request객체에 저장되어 넘어온다. 이 같이 Request에 저장되어 있는 정보들을 자바빈 클래스에 저장하여 개발자가 추출하기 편하도록 지원하는 클래스가 ActionForm클래스이다.
Application을 개발하다 보면 무수히 많은 에러가 발생한다. 이 같이 Application에서 발생하는 에러를 저장하여 사용자에게 전달하는 역할을 하는 클래스의 중심에 ActionErrors클래스가 있다.
지금까지 스트러츠에서 중심이 되는 클래스에 대하여 간략하게 살펴보았다. 생각보다 많지 않은 클래스들이기 때문에 이 핵심 클래스에 대하여만 이해하더라도 스트러츠의 많은 기능을 제대로 사용하는데 도움이 될 것으로 생각된다.
계속되는 강좌에서는 지금까지 간략하게 살펴본 각각의 클래스에 대하여 세부적으로 살펴보겠다. 하지만 모든 클래스에 대하여 살펴보기는 힘들것으로 생각되어 스트러츠의 중심이 되는 클래스인 ActionServlet과 RequestProcessor에 대하여 집중적으로 설명하도록 하겠다. 이 두개의 클래스가 나머지 클래스들을 관리하는 역할을 하기 때문에 두개의 클래스만 제대로 이해한다면 스트러츠 전체를 이해할 수 있을 것이다. 나머지 클래스들에 대해서는 스트러츠의 더 많은 기능을 다룰때 같이 살펴보도록 하겠다.
스트러츠를 이용하여 생성한 Application의 모든 Request는 모두 ActionServlet으로 중앙 집중화된다. 왜 한곳으로 집중화시키는지 아직 명확하지 않을 것으로 생각된다. 그것에 대한 이유는 계속되는 강좌와 예제를 통하여 실감할 수 있을 것으로 생각된다.
ActionServlet은 이처럼 모든 Request를 다른 놈으로 전달되기 전에 가로채어 무엇을 하는 것일까? Model 2를 이해한 개발자들은 대략적으로 무엇을 할지 생각나는 것들이 있을지도 모른다. 그리고 이 강좌를 처음보는 개발자들도 위에서 살펴본 내용을 바탕으로 무엇을 할지 대충 생각할 수 있을 것이다.
스트러츠에서 ActionServlet은 단지 Request를 가로채는 역할만 할 뿐이다. 앞에서 말했듯이 ActionServlet이 너무 커져 뚱뚱한 클래스가 되다 보니 RequestProcessor와 두개로 분리하였다. 실질적인 모든 작업은 RequestProcessor가 담당한다.
ActionServlet의 doGet과 doPost에서는 단지 process()에 작업을 위임한다.
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); }
doGet()과 doPost()에 의하여 호출된 process() 또한 무지 간단하다.
protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { RequestUtils.selectModule(request, getServletContext()); getRequestProcessor(getModuleConfig(request)).process (request, response); }
process() 또한 두줄로 끝난다. 사실 ActionServlet에 다른 메써드도 존재하지만 ActionServlet은 이것이 전부이다. process() 내부를 살펴보면 사용할 Module을 선택한 다음, getRequestProcessor(getModuleConfig(request))에서 RequestProcessor를 선택하여 모든 작업을 RequestProcessor의 process()로 위임하고 있다. 따라서 ActionServlet에서 하는 일은 생각보다 많지 않다. 대부분의 작업은 RequestProcessor에서 한다.
스트러츠 Framework은 많은 정보들을 struts-config.xml에 저장하고 있기 때문에 ActionServlet이 처음에 로드될 때 Initialize하는 부분이 있다. 매번 struts-config.xml의 정보를 읽을 경우 실행속도에 문제가 되기 때문에 ActionServlet이 처음 실행될 때 Initialize과정을 통하여 메모리에 캐쉬하게 된다. 이외에도 web.xml의 초기화 인자를 받아 초기화하는 과정이 ActionServlet의 init() 메써드에서 초기화 된다.
ActionServlet의 init() 메써드 내부를 살펴보면 다음과 같다.
public void init() throws ServletException { initInternal(); initOther(); initServlet(); // Initialize modules as needed getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this); ModuleConfig moduleConfig = initModuleConfig("", config); initModuleMessageResources(moduleConfig); initModuleDataSources(moduleConfig); initModulePlugIns(moduleConfig); moduleConfig.freeze(); Enumeration names = getServletConfig().getInitParameterNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); if (!name.startsWith("config/")) { continue; } String prefix = name.substring(6); moduleConfig = initModuleConfig (prefix, getServletConfig().getInitParameter(name)); initModuleMessageResources(moduleConfig); initModuleDataSources(moduleConfig); initModulePlugIns(moduleConfig); moduleConfig.freeze(); } destroyConfigDigester(); }
각각의 초기화 과정에 대하여 간략하게 살펴보면 다음과 같다.
initInternal()는 스트러츠 내부에서 사용하는 메세지들을 초기화한다.
initOther()는 스트러츠 Framework의 config파일, debug등 web.xml에서 전달하는 초기화인자를 이용하여 초기화한다.
initServlet()은 web.xml의 Servlet Mapping정보를 초기화한다.
initModuleConfig()는 각각의 module정보를 초기화한다.
initModuleMessageResources(), initModuleDataSources(), initModulePlugIns()는 각 모듈별 메세지 Resuource, DataSource정보, 플러그인 정보등을 초기화한다.
모듈개념에 대하여 모르는 개발자도 많은 것으로 생각된다. 작은 Application일 경우 하나의 모듈을 이용하여 개발이 가능하다. 그러나 Application이 커질 경우 여러개의 모듈이 존재하게 된다. struts-config.xml가 기본이 되는 하나의 모듈에 대한 정보를 가지고 있다고 가정하자. 만약 Application이 너무 크기 때문에 유저관리에 대한 부분은 다른 모듈로 분리하기로 했다면 struts-config-user.xml로 따로 관리할 수 있다. init()메써드를 보면 각 모듈별로 메세지 리스스, 데이터소스, 플러그인들을 독립적으로 관리하기 때문에 초기화시에도 각각의 모듈별로 초기화 과정을 진행함을 알 수 있다.
ActionServlet에서는 중간에서 가로챈 Request에 대한 모든 작업을 RequestProcessor에 전담시키고 있다. 따라서 사용자가 요청한 Request가 처리되는 과정을 보려면 RequestProcessor내부의 process()를 보아야 할 것이다.
RequestProcessor내부의 process()는 상당히 복잡하다. 너무도 많은 일을 처리하기 때문에 개발자들이 처음 접할 때 이해하기 힘들어 할 것으로 생각된다. 하지만 이 부분만 이해한다면 스트러츠의 50%이상을 이해할 수 있다고 필자는 장담한다. 따라서 다음에 보게 될 process()내부는 꼭 이해하기 바란다.
public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Wrap multipart requests with a special wrapper request = processMultipart(request); // Identify the path component we will use to select a mapping String path = processPath(request, response); if (path == null) { return; } if (log.isInfoEnabled()) { log.info("Processing a '" + request.getMethod() + "' for path '" + path + "'"); } // Select a Locale for the current user if requested processLocale(request, response); // Set the content type and no-caching headers if requested processContent(request, response); processNoCache(request, response); // General purpose preprocessing hook if (!processPreprocess(request, response)) { return; } // Identify the mapping for this request ActionMapping mapping = processMapping(request, response, path); if (mapping == null) { return; } // Check for any role required to perform this action if (!processRoles(request, response, mapping)) { return; } // Process any ActionForm bean related to this request ActionForm form = processActionForm(request, response, mapping); processPopulate(request, response, form, mapping); if (!processValidate(request, response, form, mapping)) { return; } // Process a forward or include specified by this mapping if (!processForward(request, response, mapping)) { return; } if (!processInclude(request, response, mapping)) { return; } // Create or acquire the Action instance to process this request Action action = processActionCreate(request, response, mapping); if (action == null) { return; } // Call the Action instance itself ActionForward forward = processActionPerform(request, response, action, form, mapping); // Process the returned ActionForward instance processActionForward(request, response, forward); }
위 소스를 보면 알 수 있듯이 process() 내부에서 처리하는 작업이 세보지 않아도 많음을 알 수 있다. 지금부터 처리하는 작업의 각각에 대하여 살펴보도록 하겠다.
1. processMultipart()는 request의 컨텐트타입이 multipart/form-data인지를 체크하여 multipart/form-data일 경우 새로운 Request Wrapper를 만드는 과정이다.
2. processPath()는 Request의 URI로부터 Path를 추출하여 반환한다. 만약 http://www.javajigi.net/javajigi/test.do가 호출되었다면 processPath()에 의하여 반환되는 path는 "javajigi"가 된다.
3. processLocale()는 Request의 Locale을 결정한다. 그리고 생성된 Locale 객체는 사용자의 HttpSession에 저장되어 사용되게 된다. 스트러츠 Framework은 이 같이 Locale 객체를 이용하여 국제화를 지원하는 것을 가능하게 한다.
4. processContent()는 Request의 컨텐트 타입과 인코딩을 결정한다. 컨텐트 타입은 Configuration 설정이나 JSP페이지를 통하여 결정될 수 있다. 디폴트 컨텐트 타입은 text/html이다.
5. processNoCache()는 struts-config.xml설정파일의 <controller>설정에서 nocache attribute가 true로 설정되어 있을 경우 호출된다. 만약 true라면 response객체의 header에 Pragma, Cache-Control, Expires가 추가되게 된다. 많은 개발자들이 Cache때문에 고생한적이 있을 것이다. 따라서 Cache를 없애기 위하여 모든 페이지 앞부분에 다음을 추가한 경험이 있을 것으로 생각된다.
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "No-cache");
response.setHeader("Expires", "1");
스트러츠 Framework에서는 struts-config.xml설정파일에 단지 true로 설정해주면 Cache가 적용되지 않는다.
6. processPreprocess()는 디폴트로 true를 반환한다. method내부를 보면 다음과 같다.
protected boolean processPreprocess(HttpServletRequest request,
HttpServletResponse response) {
return (true);
}
processPreprocess()는 Servlet 2.3에서 제공하는 Servlet Filter와 같은 역할을 한다. Request가 처리되기 전에 처리할 작업이 있다면 processPreprocess()에서 처리하면 된다. processPreprocess()의 구현은 RequestProcessor를 상속하는 새로운 클래스를 만들어 processPreprocess()를 오버라이드하면 된다. 만약 모든 Request가 사용자가 로그인한 상태여야 한다면 processPreprocess()에서 사용자의 세션이 있는지를 확인하는 작업이 들어가면 좋을 것으로 생각된다.
7. processMapping()는 요청한 request에서 사용할 ActionMapping객체를 생성하는 과정이다. 만약 Path정보에 해당하는 Mapping정보가 없으면 에러가 포함되 response가 반환된다.
8. processRoles()는 사용자가 요청한 request를 처리할 수 있는 role이 있는지를 판별하는 과정이다. role이 존재한다면 과정이 계속 진행되겠지만
그렇지 않을 경우 에러를 발생시키며 처리과정이 중단된다.
9. processActionForm()는 ActionMapping에 ActionForm이 설정되어 있다면 ActionForm 인스턴스를 생성한다. 생성된 인스턴스는 설정파일에서 설정한 Scope에 맞도록 저장하는 과정이다. 만약 새로운 ActionForm 인스턴스를 생성할 필요가 있다면 reset() method가 호출된다.
10. processPopulate()는 request에서 전달되는 인자를 앞에서 생성된 ActionForm 인스턴스에 저장한다.
11. processValidate()는 설정파일에서 validate attribute가 true로 설정되어 있다면 ActionForm의 validate method를 호출하는 역할을 한다. request에 의하여 전달된 인자들의 유효성을 체크하는 과정이다.
12. processForward()는 스트러츠 설정파일의 action태그에서 forward나 include attribute가 설정되어 있다면 RequestDispatcher의 forward(), include() 메써드를 호출한다. 설정파일에 이 forward, include가 설정되어 있다면 request의 처리는 여기서 끝나고 해당 URL로 전달되게 된다.
13. processActionCreate()는 메써드 이름에서도 알 수 있듯이 request에 해당하는 Action 인스턴스를 생성한다. Action 인스턴스가 이미 존재하는지를 체크하여 존재한다면 재사용하게 된다.
14. processActionPerform()는 앞에서 생성한 Action 인스턴스의 execute()메써드를 호출하여 Action인스턴스를 실행시킨다.
15. processActionForward()는 Action 인스턴스의 execute()를 실행한
결과 반환된 ActionForward에 해당하는 URL로 forward시키는 과정이다.
와 힘들다. 너무도 많은 일을 한다. 하지만 각각의 과정을 이해하는 것은 어렵지 않을 것으로 보인다. RequestProcessor의 process() 내부에서 스트러츠의 전체적인 조율을 맡아서 하고 있다. request에 의해서 전달되는 정보를 이용하여 struts-config.xml에서 해당 객체를 생성한 다음 최종적으로 Action의 execute()를 호출하여 원하는 작업을 실행 후 반환되는 결과에 따라 해당 페이지로 전달되게 된다.
내용만 복잡하지 전체적인 그림을 그려보면 생각보다 어렵지 않음을 알 수 있다. 한번 읽어서 이해되지 않는다면 반복하여 각각의 처리과정에서 무엇을 하는지 명확히 이해하기 바란다. 제공되는 전체 소스를 이용하여 각 처리과정에 해당하는 세부 메써드 내부에 대하여도 한번 살펴보기 바란다.
지금까지 스트러츠 Framework의 중심이라고 할 수 있는 MVC에서 Controller에 해당하는 클래스들에 대하여 살펴보았다. 스트러츠에서 특히 중요한 부분인 만큼 확실히 이해할 수 있었으면 한다. 위에서 설명한 클래스들의 관계에 대하여 완변하게 이해하고 있다면 스트러츠의 다양한 기능들을 활용하는데 큰 도움이 될 것이다.
작성자 : OSS:박재성
작성일 : 2005년 2월 20일
문서이력 :