1. 스트럿츠는왜필요한가? Model 1 Model 1 방식의웹어플리케이션이란, 한개의 JSP 에서모든비지니스로직 ( 데이터베이스쿼리, 업데이트등실제업무작업 ) 을수행하고, 그결과를바로출력하는방식이다. 현재가장쉽게많이사용되는방식의웹프로그래밍모델이다. 이방식은아주단순한웹

Similar documents
I T C o t e n s P r o v i d e r h t t p : / / w w w. h a n b i t b o o k. c o. k r

다른 JSP 페이지호출 forward() 메서드 - 하나의 JSP 페이지실행이끝나고다른 JSP 페이지를호출할때사용한다. 예 ) <% RequestDispatcher dispatcher = request.getrequestdispatcher(" 실행할페이지.jsp");

혼자서일을다하는 JSP. 이젠일을 Servlet 과나눠서한다. JSP와서블릿의표현적인차이 - JSP는 <html> 내에서자바를사용할수있는수단을제공한다. - 서블릿은자바내에서 <html> 을작성할수있는수단을제공한다. - JSP나서블릿으로만웹페이지를작성하면자바와다양한코드가

04장

PowerPoint Template

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

작성자 : 김성박\(삼성 SDS 멀티캠퍼스 전임강사\)

2장 변수와 프로시저 작성하기

중간고사

<4D F736F F F696E74202D203130C0E52EBFA1B7AF20C3B3B8AE205BC8A3C8AF20B8F0B5E55D>

Microsoft PowerPoint - CSharp-10-예외처리

14-Servlet

JAVA 프로그래밍실습 실습 1) 실습목표 - 메소드개념이해하기 - 매개변수이해하기 - 새메소드만들기 - Math 클래스의기존메소드이용하기 ( ) 문제 - 직사각형모양의땅이있다. 이땅의둘레, 면적과대각

Eclipse 와 Firefox 를이용한 Javascript 개발 발표자 : 문경대 11 년 10 월 26 일수요일

PowerPoint Presentation

JUNIT 실습및발표

Web Service Computing

gnu-lee-oop-kor-lec06-3-chap7

Data Provisioning Services for mobile clients

<param-value> 파라미터의값 </param-value> </init-param> </servlet> <servlet-mapping> <url-pattern>/ 매핑문자열 </url-pattern> </servlet-mapping> - 위의예에서 ServletC

서블릿의라이프사이클 뇌를자극하는 JSP & Servlet

제이쿼리 (JQuery) 정의 자바스크립트함수를쉽게사용하기위해만든자바스크립트라이브러리. 웹페이지를즉석에서변경하는기능에특화된자바스크립트라이브러리. 사용법 $( 제이쿼리객체 ) 혹은 $( 엘리먼트 ) 참고 ) $() 이기호를제이쿼리래퍼라고한다. 즉, 제이쿼리를호출하는기호

chapter6.doc

Microsoft PowerPoint - chap01-C언어개요.pptx

SOFTBASE XFRAME DEVELOPMENT GUIDE SERIES HTML 연동가이드 서울특별시구로구구로 3 동한신 IT 타워 1215 호 Phone Fax Co

Microsoft PowerPoint - 웹프로그래밍_ ppt [호환 모드]

PowerPoint Presentation

KYO_SCCD.PDF

Research & Technique Apache Tomcat RCE 취약점 (CVE ) 취약점개요 지난 4월 15일전세계적으로가장많이사용되는웹애플리케이션서버인 Apache Tomcat에서 RCE 취약점이공개되었다. CVE 취약점은 W

C# Programming Guide - Types

JAVA Bean & Session - Cookie

Intro to Servlet, EJB, JSP, WS

Microsoft Word - ntasFrameBuilderInstallGuide2.5.doc

PowerPoint Presentation

제8장 자바 GUI 프로그래밍 II

Spring Boot/JDBC JdbcTemplate/CRUD 예제

Spring Boot

JSP 의내장객체 response 객체 - response 객체는 JSP 페이지의실행결과를웹프라우저로돌려줄때사용되는객체이다. - 이객체는주로켄텐츠타입이나문자셋등의데이터의부가정보 ( 헤더정보 ) 나쿠키 ( 다음에설명 ) 등을지정할수있다. - 이객체를사용해서출력의방향을다른

C++ Programming

Microsoft PowerPoint 웹 연동 기술.pptx

<4D F736F F F696E74202D20C1A63038C0E520C5ACB7A1BDBABFCD20B0B4C3BC4928B0ADC0C729205BC8A3C8AF20B8F0B5E55D>

PowerPoint 프레젠테이션

Microsoft PowerPoint - Java7.pptx

- JPA를사용하는경우의스프링설정파일에다음을기술한다. <bean id="entitymanagerfactory" class="org.springframework.orm.jpa.localentitymanagerfactorybean" p:persistenceunitname=

파일로입출력하기II - 파일출력클래스중에는데이터를일정한형태로출력하는기능을가지고있다. - PrintWriter와 PrintStream을사용해서원하는형태로출력할수있다. - PrintStream은구버전으로가능하면 PrintWriter 클래스를사용한다. PrintWriter

PowerPoint 프레젠테이션

기술문서 작성 XXE Attacks 작성자 : 인천대학교 OneScore 김영성 I. 소개 2 II. 본문 2 가. XML external entities 2 나. XXE Attack 3 다. 점검방법 3 라.

슬라이드 1

TP_jsp7.PDF

<4D F736F F F696E74202D20C1A632C8B8C7D1B1B9BDBAC7C1B8B5BBE7BFEBC0DAB8F0C0D32D496E E D56432E BC8A3C8AF20B8F0B5E55D>

<property name="configlocation" value="classpath:/egovframework/sqlmap/example/sql-map-config.xml"/> <property name="datasource" ref="datasource2"/> *

PowerPoint Presentation

Cluster management software

JavaGeneralProgramming.PDF

var answer = confirm(" 확인이나취소를누르세요."); // 확인창은사용자의의사를묻는데사용합니다. if(answer == true){ document.write(" 확인을눌렀습니다."); else { document.write(" 취소를눌렀습니다.");

JAVA PROGRAMMING 실습 08.다형성

Data Provisioning Services for mobile clients

어댑터뷰

MVVM 패턴의 이해

Microsoft PowerPoint 세션.ppt

Microsoft PowerPoint - chap06-1Array.ppt

Cookie Spoofing.hwp

로거 자료실

뇌를 자극하는 JSP & Servlet 슬라이드

쉽게

금오공대 컴퓨터공학전공 강의자료

쉽게 풀어쓴 C 프로그래밍

Microsoft PowerPoint - chap06-2pointer.ppt

본 강의에 들어가기 전

Data Provisioning Services for mobile clients

Microsoft PowerPoint Android-SDK설치.HelloAndroid(1.0h).pptx

PowerPoint 프레젠테이션

표준프레임워크로 구성된 컨텐츠를 솔루션에 적용하는 것에 문제가 없는지 확인

<322EBCF8C8AF28BFACBDC0B9AEC1A6292E687770>

쉽게 풀어쓴 C 프로그래밊

OCW_C언어 기초

<4D F736F F F696E74202D20C1A63236C0E520BED6C7C3B8B428B0ADC0C729205BC8A3C8AF20B8F0B5E55D>

PowerPoint Presentation

q 이장에서다룰내용 1 객체지향프로그래밍의이해 2 객체지향언어 : 자바 2

API STORE 키발급및 API 사용가이드 Document Information 문서명 : API STORE 언어별 Client 사용가이드작성자 : 작성일 : 업무영역 : 버전 : 1 st Draft. 서브시스템 : 문서번호 : 단계 : Docum

JVM 메모리구조

Microsoft PowerPoint - java1-lab5-ImageProcessorTestOOP.pptx

Microsoft PowerPoint - 7강.pptx

Microsoft PowerPoint - 04-UDP Programming.ppt

PowerPoint 프레젠테이션

PowerPoint 프레젠테이션

2009년 상반기 사업계획

. 스레드 (Thread) 란? 스레드를설명하기전에이글에서언급되는용어들에대하여알아보도록하겠습니다. - 응용프로그램 ( Application ) 사용자에게특정서비스를제공할목적으로구현된응용프로그램을말합니다. - 컴포넌트 ( component ) 어플리케이션을구성하는기능별요

UI TASK & KEY EVENT

PowerPoint 프레젠테이션

PowerPoint Presentation

뇌를 자극하는 JSP & Servlet 슬라이드

iii. Design Tab 을 Click 하여 WindowBuilder 가자동으로생성한 GUI 프로그래밍환경을확인한다.

A Hierarchical Approach to Interactive Motion Editing for Human-like Figures

PowerPoint 프레젠테이션

Data Provisioning Services for mobile clients

임베디드시스템설계강의자료 6 system call 2/2 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

2) 활동하기 활동개요 활동과정 [ 예제 10-1]main.xml 1 <LinearLayout xmlns:android=" 2 xmlns:tools="

Javascript

- 목차 - - ios 개발환경및유의사항. - 플랫폼 ios Project. - Native Controller와플랫폼화면연동. - 플랫폼 Web(js)-Native 간데이터공유. - 플랫폼확장 WN Interface 함수개발. - Network Manager clas

Transcription:

Struts 처음배우기 손권남 kwon37xi@yahoo.co.kr http://kwon37xi.egloos.com 기준버전 : Struts 1.2.7 작성일 : 2005/06/14 ~ 목차 1. 스트럿츠는왜필요한가?...2 2. 스트럿츠기반웹어플리케이션구성하기...5 3. 스트럿츠어플리케이션시작하기...7 4. 제대로사용해보기...15 5. 스트럿츠커스텀태그들...28 6.RequestProcessor 상속하기...29 7.Tiles 이해하기...31 8.Validator 사용하기...36 9. 예외처리하기 (Exception Handling)...45 10. 어플리케이션로그남기기 (Logging)...51 11.ActionForm 에대한정리...53 12.ActionForward 에대한정리...55 13.ActionMapping 에대한정리...57 14.Action 에대한정리...59 15.PlugIn 에대한정리...62 16. 이제뭘하지?...63 1

1. 스트럿츠는왜필요한가? Model 1 Model 1 방식의웹어플리케이션이란, 한개의 JSP 에서모든비지니스로직 ( 데이터베이스쿼리, 업데이트등실제업무작업 ) 을수행하고, 그결과를바로출력하는방식이다. 현재가장쉽게많이사용되는방식의웹프로그래밍모델이다. 이방식은아주단순한웹어플리케이션 (JSP 10 개미만?) 에서는빠르고단순하게프로그램을짤수있어편하지만어플리케이션이커지게되면그복잡도가크게증가하여디버깅이어렵고, 한번수정할것이생기면수정할위치를찾기도어려울뿐만아니라, 단순히비지니스로직이바뀌는것임에도화면출력부분의수정이필요하게된다 ( 그반대로디자인변경을위해프로그램로직을바꿔야하는경우도많다 ). 이로인해유지보수비용과시간이증가하게되고, 자바웹프로그래머의밤샘의원흉이된다. 단순한예로, Model 1 방식으로짠프로그램은한개의 JSP 가작업을수행하는자바코드 ( 스크립틀릿 ) 과화면에출력되는 HTML 을포함하며보통수천줄에달하게되는데, 거기에다자바코드가 HTML 코드구석구석에숨어있기때문에오류가발생할경우오류를고치는시간보다는오류가발생한부분을찾는데거의모든시간을소모하게되어버린다. 오류를찾는다하여도로직과디자인코드가함께있어소스코드변경하기가녹록치않다. 또한, 한개파일의프로그램과디자인이섞여있기때문에, 디자이너와프로그래머의업무경계가불명확해진다. 디자이너가디자인하나바꿀때마다프로그래머가없으면아무것도변경하지못하는사태가발생하게된다. 아니면디자이너스스로프로그램을짤줄알아야하거나. 삽화 1 Model 1 개요 Model 2 그래서등장한것이 Model 2 이다. 모델 2 란실제프로그램수행부분과화면에결과를뿌려주는부분을서로분리하는것이다. 간단하게모든요청을서블릿이받고, 비지니스로직을서블릿에서수행하고, 결과출력을위해 JSP 로포워딩하도록해도어플리케이션복잡도가많이감소하게된다. 하지만그렇게할경우서블릿의갯수가지나치게늘어나고, web.xml 이복잡하고어지러워지게된다. 그래서모델 2 그중에서도 Model 2 를 MVC(Model-View-Controller) 패턴으로설계하는것이주류로자리잡게되었다. 2

삽화 2 Model 2 개요 1. Model 2 는사용자의요청을 Controller 가받는다. 컨트롤러는사용자의요청을분석하여할일이뭔지를판단하고, 실제로비지니스로직을수행하는자바클래스를호출한다. 2. 비지니로로직을수행하는부분을모델이라고부른다. 이모델에서데이터베이스쿼리및업데이트등을수행한다. 그리고는그수행결과를컨트롤러가 request 나 session, application 웹서블릿스코프객체들에게 setattribute() 메소드를이용해저장한다. 3. 그리고모델은실행을종료한뒤에컨트롤러로복귀한다. 4. 컨트롤러는설정파일을통해서모델의실행결과를 HTML 로뿌려줄 View( 즉, JSP 페이지 ) 로포워딩을한다. 5. View JSP 는 getattribute() 로컨트롤러가저장한수행결과를얻어와이를화면에출력해준다. 이렇게해서 Model-View-Controller 로각각의역할을구분한것이 Model 2 이다. MVC 패턴의구현 그렇다면 MVC 패턴으로프로그램을어떻게짜는가하는문제가남았다. MVC 패턴에서 Model 부분은자바클래스와자바빈이맡는다. Model 은데이타베이스등을액세스하는자바클래스와그결과데이타를저장하는 Java Beans 로구성되는것이다. View 는 JSP 가맡는다. JSP 는뭐다아니까넘어가기 ~ Controller 가문제이다. 컨트롤러는 Servlet 을기반으로직접개발자가만들어서사용할수도있고이미많이나와있는오픈소스프레임워크를사용해도된다. 스트럿츠? 스트럿츠 (http://struts.apache.org) 는이 MVC 패턴에서 Controller 역할을하는웹어플리케이션프레임워크로서가장유명하고널리쓰이는것이다. 스트럿츠의작동구조를 MVC 에대입하여비교해보자. 3

삽화 3 스트럿츠개요 스트럿츠의구조는 MVC 패턴그림에비해약간복잡해보이지만사실 MVC 패턴의범주를벗어나지않는다. 웹컨테이너안의작은직사각형부분에있는 ActionServlet, RequestProcessor, ActionForm, Action 이모두스트럿츠에해당하는부분이며, 모두컨트롤러의역할을하게된다. Model 과 View 는 MVC 패턴소개에서본대로컨트롤러인스트럿츠와는상관없이움직이게된다. ActionServlet : 어떤 Action( 비지니스로직을호출하는역할을하는자바클래스 ) 를사용할지여부등스트럿츠전반에대한설정을저장하고있는 struts-config.xml 파일을읽어저장하는 Servlet 클래스이다. RequestProcessor : 사용자의요청을받으면, struts-config.xml 설정에따라실제로호출할 Action 을선택하고, request 와 response 서블릿객체를이용해서미리선행작업을하는역할을한다. Action 을선택하는방법은바로요청 URL 에있다. 예를들어사용자가 http://localhost:8080/login.do 를호출했다면 /login.do 를인지하고 strutsconfig.xml 에서 /login 이라는이름 (.do 제외 ) 으로지정된행동지침관련설정 (<action>) 을찾아서그에따라비지니스로직을실행시킨다. ActionForm : 사용자가 GET 혹은 POST 방식으로넘긴파라미터들을저장하고분석하여알맞은값이들어왔는지여부등을판단 ( 유효성검사 ) 하는 Java Bean 이다. ActionForm 은있어도되고없어도되며 ActionForm 에서각파라미터가유효성검사를통과하지않으면 Action 을호출하지않는다. Action : 비지니스로직을호출하는부분이다. 실질적으로자바웹서블릿과같은역할을한다. 비지니스로직은 Action 에서직접수행하지말고따로 Model 전용클래스로만들어서파라미터들을넘겨호출만하는형태로작성한다. 스트럿츠는이이외에도아주다양한기능을수행한다. 내가생각하는스트럿츠의단점은기능이너무다양하다는것이다. 그리고그로인한설정파일의복잡도도높은편이다. 개발자는그모든기능을익히려고욕심을부리다가아무것도못하는포기하는상태에빠질수도있다. 이문서는꼭필요하다고생각되는기능들로만각단위별예제와함께설명한다. 그외의더많은다양한기능들은잘짜여진책을참고로하여학습하도록한다. 4

2. 스트럿츠기반웹어플리케이션구성하기 http://struts.apache.org 에서스트럿츠프레임워크바이너리를다운로드하면된다. 웹어플리케이션디렉토리구조로새어플리케이션을구성한다. WEB-INF/lib 디렉토리에스트럿츠바 Batang 이너리압축파일에있는 struts.jar 와기타 lib/*.jar 파일들을넣는다. WEB-INF/config/struts-config.xml 파일을생성한다. WEB-INF/tlds/ 에스트럿츠바이너리파일에포함된각 *.tld 파일을복사한다. struts-config.xml 과 *.tld 파일은 WEB-INF/ 디렉토리에두어도된다. WEB-INF/web.xml 파일을스트럿츠를사용할수있도록수정한다. 스트럿츠바이너리파일을보면 struts-blank.war 파일이있는데, 이파일은기본적인스트럿츠설정이된상태의웹어플리케이션구조를미리만들어둔예제파일이다. 스트럿츠어플리케이션을새로만들기시작할때압축을풀어서사용하면위의설정이다된상태가된다. 단, config 디렉토리와 tlds 디렉토리를따로구분하지는않아뒀기때문에따로생성하고파일을각각의위치에옮기기를권장한다 ( 개인적으로 WEB-INF/ 디렉토리에많은파일두는것을싫어한다. 스트럿츠를위한기본적인 web.xml 구성. 아래는 J2EE 1.3, JSP 1.2, Servlet 2.3 스펙에기반한 web.xml 파일이다. 모든스트럿츠어플리케이션은다음과같은내용의 web.xml 을가지고있어야만한다. <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <display-name> 스트럿츠어플리케이션 </display-name> <!-- ActionServlet 를등록한다. --> <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.actionservlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/web-inf/config/struts-config.xml</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>2</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>2</param-value> </init-param> <!-- ActionServlet 은이웹어플리케이션시작시에함께시작되어야한다. --> <load-on-startup>1</load-on-startup> </servlet> <!-- *.do 로끝나는모든 URL 패턴은 ActionServlet 을거쳐서수행되어야한다. --> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- 웹으로접속한사용자가 JSP 파일로직접접근할수없게한다. --> <security-constraint> <web-resource-collection> <web-resource-name>preventviewingjsps</web-resource-name> <description> 웹으로접속한사용자가 JSP 파일로직접접근할수없도록한다.</description> <url-pattern>*.jsp</url-pattern> 5

<http-method>get</http-method> <http-method>post</http-method> </web-resource-collection> <auth-constraint> <role-name></role-name> </auth-constraint> </security-constraint> </web-app> <servlet-mapping> 의 <url-pattern>*.do</url-pattern> 에의해서현재웹컨텍스트의.do 로끝나는모든요청이 ActionServlet 을거쳐서가게된다. ActionServlet 으로진입한사용자의요청은 ActionServlet 의파라미터로지정한 struts-config.xml 의설정에따라지정된작업을수행하게된다. <security-constraint> 부분으로인해서어떠한사용자도 *.jsp 파일에직접접근할수없다. 모두스트럿츠컨트롤러를통해서만 JSP 파일에접근할수있도록제약한다. struts-config.xml 의기본구성 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <struts-config> <data-sources /> <form-beans /> <global-exceptions /> <global-forwards /> <action-mappings /> <controller /> <message-resources /> <plug-in /> </struts-config> struts-config.xml 파일은위와같은구성에각요소별로상세설정이더들어가게된다. 기본설정요소들은항상위의순서대로작성되어야한다. 예를들어 <action-mappings /> 관련설정은항상 <global-forwards /> 보다뒤에나와야만한다. 각요소들중불필요한것을일부러빈값으로놔둘필요없이그냥아예없애버려도된다. 하지만일단기입하면위의순서에따라야만한다 ( 이것은 XML DTD 의특징이며 Servlet Spec 2.3 이하의 web.xml 에서도마찬가지로적용된다 ). 이문서에서는 <data-sources /> 설정을제외한모든설정을사용한다. 6

3. 스트럿츠어플리케이션시작하기 웹어플리케이션의컨텍스트패스는각어플리케이션별로다르다. 컨텍스트패스가들어가는부분은 ContextPath 로해놓았다. 이부분은각자의상황에맞게바꾼다. 단순히 JSP 페이지로포워딩하기 스트럿츠에서는기본적으로모든 JSP 가직접사용자에의해액세스되는것을금기시하고있다. 그러므로 Model 부분이필요없이단순히화면에 HTML 만출력하는 JSP 도컨트롤러를거쳐서사용자에게전달되도록하는것이좋다. 그리고우리는이미 web.xml 에서일반사용자가 *.jsp 페이지를호출할수없도록막아버렸다 (<securityconstraint /> 이용 ). 스트럿츠컨트롤러를통하지않고는누구도 *.jsp 를호출할수없다. 여기서는단순한 HTML 만출력하는 JSP 를컨트롤러를거쳐서가도록만들어본다. WEB-INF/config/struts-config.xml 을다음과같이작성한다. <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <!-- 앞으로는상단제외하고예제를싣는다. --> <struts-config> <action-mappings> <action path="/welcome" forward="/welcome.jsp"/> </action-mappings> </struts-config> 이예제에서는 Model 을호출하는 Action 부분을따로작성하지않고단지지정된 JSP 로포워딩하도록하고있다. <action> 요소에서 path= /Welcome : URL 에 http://localhost:8080/contextpath/welcom.do 처럼호출하면지정된액션을수행한다고지정했다. URL 의.do 는 path 속성에지정하지않는다. <action> 요소에서 forward= /Welcome.jsp : 특별한비지니스로직수행작업을하지않고곧바로 Welcome.jsp 로포워딩을한다. Welcome.jsp 을작성하여웹어플리케이션디렉토리에둔다. <%@page contenttype="text/html; charset=euc-kr"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title>welcome Page</title> </head> <body> <h1> 환영합니다 ~</h1> 환영합니다!<br /> 첫번째 Struts 어플리케이션입니다. </body> </html> 이어플리케이션에서는 Action 이실질적으로아무런역할도수행하지않고바로 JSP 페이지로포워딩을해서내용을출력한다. 그결과를보면다음과같다. 7

첫번째스트럿츠어플리케이션을완성했다. Forward 와 Redirect 의차이 JSP/Servlet 에는현재작업중인페이지에서다른페이지로이동하는두가지방식의페이지전환기능이있다. 하나는 Forward 이고하나는 Redirect 이다. 둘다다른웹페이지로이동하지만행동양태가다르다. Forward : Web Container 차원에서페이지이동만있다. 실제로웹브라우저는다른페이지로이동했음을알수없다. 그렇기때문에, 웹브라우저에는최초에호출한 URL 이표시되고이동한페이지의 URL 정보는볼수없다. 동일한웹컨테이너에있는페이지로만이동할수있다. 현재실행중인페이지와 Forwad 에의해호출될페이지는 request 와 response 객체를공유한다. Redirect : Web Container 는 Redirect 명령이들어오면웹브라우저에게다른페이지로이동하라고명령을내린다. 그러면웹브라우저는 URL 을지시된주소로바꾸고그주소로이동한다. 다른웹컨테이너에있는주소로이동이가능하다. 새로운페이지에서는 request 와 response 객체가새롭게생성된다. Action 을사용하는기본적인예제 이번에는컨트롤러에서모델을호출하는예제를살펴보자. 비지니스로직 (Model) 을호출하는역할을하는 Action 클래스를작성해보도록한다. Action 클래스는 org.apache.struts.action.action 클래스를상속받아서작성하며, 실제프로그램수행은 execute() 메소드에의해이뤄진다. Action 클래스의역할 사용자가 GET/POST 방식으로넘겨준파라미터들을분석한다 ( 이것은 Action 이호출되기전에 ActionForm 에위임할수있다 ). 자신이수행할비지니스로직을구현한 Model Java Class 를호출한다. Model Java 클래스가수행한결과를넘겨받는다. 그결과를 request, session, application 등의스코프에저장하고, View 로사용될페이지를지정한뒤에리턴한다. RequestProcessor 가 Action 에의해지정된페이지로포워딩혹은리다이렉트한다. Action 클래스는기본적으로다음과같은형태로만들어진다. import org.apache.struts.action.action; import org.apache.struts.action.actionmapping; import org.apache.struts.action.actionforward; import org.apache.struts.action.actionform; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; public class ActionClass extends Action { // execute 메소드를필히구현해야한다. 8

public ActionForward execute(actionmapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // 실제작업수행.. return forward; 자, 이제사용자를로그인시키는스트럿츠자바어플리케이션을생성해본다. WEB-INF/config/struts-config.xml 을다음과같이작성한다. <!-- 상단생략 --> <struts-config> <action-mappings> <action path="/welcome" forward="/welcome.jsp"/> <!-- 로그인폼으로이동하는 Forward 액션 --> <action path="/login1/loginform" forward="/login1/loginform.jsp"/> <!-- 로그인을실행하는 Action --> <action path="/login1/login" type="strutsguide.actions.login1action" validate="false" > <!-- 로그인을수행한뒤에성공 / 실패표시를위해이동할 View 페이지 --> <forward name="success" path="/login1/loginsuccess.jsp" redirect="true" /> <forward name="fail" path="/login1/loginfail.jsp" /> </action> </action-mappings> </struts-config> /login1/loginform.jsp 를다음과같이작성한다. <%@page contenttype="text/html; charset=euc-kr"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title> 로그인폼 </title> </head> <body> <h1> 로그인폼 </h1> <!-- 폼의데이터를 "login.do" 로전송한다. --> <form name="login" method="get" action="login.do"> Username : <input name="username" type="text" size="16" /><br /> Password : <input name="password" type="password" size="16" /><br /> <input type="submit" /> </form> </body> </html> /login1/loginsuccess.jsp 를다음과같이작성한다. <%@page contenttype="text/html; charset=euc-kr"%> 9

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <%-- Action 에서 Session 객체에저장한 userinfo 자바빈객체를사용한다. --%> <jsp:usebean class="strutsguide.beans.userinfobean" id="userinfo" scope="session"/> <html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title> 로그인성공 </title> </head> <body> <h1> 로그인성공 </h1> 로그인사용자명 : <jsp:getproperty name="userinfo" property="username"/><br /> 전화번호 : <jsp:getproperty name="userinfo" property="phone"/><br /> 이메일 : <jsp:getproperty name="userinfo" property="email"/><br /> </body> </html> /login1/loginfail.jsp 를다음과같이작성한다. <%@page contenttype="text/html; charset=euc-kr"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title> 로그인실패 </title> </head> <body> <h1> 로그인실패 </h1> 로그인에실패했습니다.<br /> <a href="loginform.do"> 다시로그인 </a> </body> </html> 실제로그인하는역할을하는 Model 클래스인 LogInProcess.java 를작성한다. 이클래스는임의로 HashMap 에사용자정보와비밀번호를넣고, 사용자가 loginform.do 를통해입력한정보를대입하여정확한사용자명과비밀번호라면사용자의정보자바빈객체를리턴하고, 그렇지않다면 null 을리턴한다. 이클래스는객체가여러개생성될이유가없기때문에 Singleton 패턴으로작성하여단한개의객체만생성되도록하였다. package strutsguide.beans; import java.util.hashmap; /** * 로그인을실제로수행하는비지니스로직클래스 */ public class LogInProcess { /** 사용자정보를담고있는해시맵 */ private HashMap userinfos = null; /** 사용자의비밀번호를담고있는해시맵 */ private HashMap userpasswords = null; /** 유일한 LogInProcess 객체 */ private static LogInProcess instance = new LogInProcess(); 10

/** 싱글턴으로오로지객체한개만생성한다. 생성자 */ private LogInProcess() { userpasswords = new HashMap(); userinfos = new HashMap(); // 가상의사용자를추가한다. // 원하는대로추가할수있다. userpasswords.put("kwon37xi", "1234"); userinfos.put("kwon37xi", new UserInfoBean("kwon37xi", "011-222-4444", "kwon37xi@yahoo.co.kr")); userpasswords.put("narae", "n111"); userinfos.put("narae", new UserInfoBean("narae", "010-333-5555", "narae@mymail.xxx")); userpasswords.put("woori", "w222"); userinfos.put("woori", new UserInfoBean("woori", "010-777-9999", "woori@yourmail.ooo")); /** * LogInProcess 의유일한객체를얻는다. */ public static LogInProcess getinstance() { return instance; /** * 로그인을수행하고, 성공하면해당하는사용자의 UserInfoBean 객체를리턴한다. * * @param username 로그인할사용자명 * @param password 비밀번호 * @return UserInfoBean 로그인한사용자의정보. 로그인실패하면 null 리턴 */ public UserInfoBean login(string username, String password) { String userpassword = (String)userPasswords.get(userName); // 사용자가존재하지않으면 null 리턴 if (userpassword == null) { return null; // 비밀번호가일치하지않아도 null 리턴 if (!userpassword.equals(password)) { return null; UserInfoBean userinfo = (UserInfoBean)userInfos.get(userName); return userinfo; 사용자정보를저장하는모델자바빈인 UserInfoBean.java 를작성한다. package strutsguide.beans; public class UserInfoBean { /** 사용자명 */ private String username = null; /** 사용자의전화번호 */ private String phone = null; /** 사용자의이메일 */ private String email = null; 11

/** 기본생성자 */ public UserInfoBean() { // do nothing; /** 사용자정보를받는생성자 */ public UserInfoBean(String username, String phone, String email) { this.username = username; this.phone = phone; this.email = email; public String getusername() { return username; public void setusername(string username) { this.username = username; public String getphone() { return phone; public void setphone(string phone) { this.phone = phone; public String getemail() { return email; public void setemail(string email) { this.email = email; 모델클래스를호출하여실제작업을수행하는액션클래스인 LogIn1Action.java 을작성한다. package strutsguide.actions; import org.apache.struts.action.action; import org.apache.struts.action.actionmapping; import org.apache.struts.action.actionforward; import org.apache.struts.action.actionform; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import javax.servlet.http.httpsession; import strutsguide.beans.userinfobean; import strutsguide.beans.loginprocess; public class Login1Action extends Action { /** * 액션을수행한다. */ public ActionForward execute(actionmapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { LogInProcess loginprocess = LogInProcess.getInstance(); // 사용자가전달한파라미터를저장한다. 12

String username = request.getparameter("username"); String password = request.getparameter("password"); UserInfoBean userinfo = loginprocess.login(username, password); ActionForward forward = null; if (userinfo == null) { // userinfo 가 null 이면로그인이실패한것이다. 실패페이지로이동한다. // 이동할페이지를찾는다. forward = mapping.findforward("fail"); else { // null 이아니면성공페이지로이동한다. 이동하기전에사용자정보를 session 에저장한다. HttpSession session = request.getsession(); session.setattribute("userinfo", userinfo); // 이동할페이지를찾는다. forward = mapping.findforward("success"); // 비지니스로직을마치고 View 페이지로이동하도록지시한다. return forward; 이제, 실행해보자. http://localhost:8080/contextpath/login1/loginform.do 를호출하면 strutsconfig.xml의 <action path="/login1/loginform" forward="/login1/loginform.jsp"/> 에의해 login1/loginform.jsp 으로포워딩되면서프로그램을시작할수있다. 삽화 4 로그인폼 여서서 LogInProcess.java 에지정한올바른사용자명과비밀번호를입력하면 loginform.jsp 의 <form action="login.do"> 에의해 Login1Action 액션클래스로제어가넘어가게된다. Login1Actioin 클래스는사용자의파라미터를분석하고, 그파라미터를 LogInProcess 의 login() 메소드에넘겨올바른사용자인지검사한다. 검사가성공적이면 mapping.findforward( success ); 에의해사용자가다음 struts-config.xml 에지정한성공했을때이동할페이지로포워딩을한다. <!-- 로그인을실행하는 Action --> <action path="/login1/login" type="strutsguide.actions.login1action" validate="false" > <!-- 로그인을수행한뒤에성공 / 실패표시를위해이동할 View 페이지 --> <forward name="success" path="/login1/loginsuccess.jsp" redirect="true" /> <forward name="fail" path="/login1/loginfail.jsp" /> </action> 저기서는성공했을때 success 라는문저열로포워딩할정보를찾으면 /login1/loginsuccess.jsp 페이지로리다이렉트 (redirect= true ) 하도록지정했다. 이렇게리다이렉트를하면다음과같이 jsp 페이지를 URL 에 13

서직접볼수있게된다.( 좋지않다. 다음번에는리다이렉트되는페이지도모두컨트롤러를통해가도록수정해보자.) -- 이부분을작성할때는 web.xml 에 *.jsp 접근금지관련설정을하지않았었다. 그부분을주석처리하고실행해볼것. 삽화 5 로그인성공 실패하면 fail 이라는포워딩정보에의해서 /login1/loginfail.jsp 로포워딩을하게된다. 리다이렉트가아닌포워딩을하므로브라우저에보이는 URL 은변함없이 /login1/login.do 가된다. 삽화 6 로그인실패 저기보면 URL 이 login.do 인것을볼수있다. 그리고노파심에서말하자면 form 의 method 를 GET 으로지정한것은읽는이에게정보전달과정을보여주기위해서일뿐이다. 실제로로그인하는폼처럼비밀번호같은숨겨야할정보가있는폼은 POST 방식으로만들어야한다. 첫번째 MVC 패턴기반의웹어플리케이션을만들어보았다. 14

4. 제대로사용해보기 스트럿츠의기본적인흐름을다익혔다. 이제스트럿츠를본격적으로사용해보자. 아래는사실상스트럿츠의뼈대가되는거의모든기능을사용하는예제이다. 스트럿츠의기본구조를설명할때스트럿츠는 Action 을실행하기전에 ActionForm 이라는것을거치면서사용자가입력한파라미터들이유효한지여부를검사할수있다고했다. ActionForm 에서파라미터들이유효하지않다고판단하면 Action 까지가지않고곧바로오류메시지를보여주며사용자에게다시입력하도록할수있다. 여기서오류메시지를뿌려주는역할이 ActionMessages 의역할이다. ActionMessages 는 ActionForm 에서난오류뿐만아니라 Action 클래스에서난오류나혹은일반메시지도저장하고있다가뷰 JSP 에서출력해줄수있다. ActionForm 의역할은 HTML 폼 ("<input type='text' name='phone'/>" 형식 ) 으로부터받은입력은 ActionForm 빈으로전달되고 (ActionForm 객체에대해 setphone(request.getattribute( phone ) 과같은호출이일어난다 ), 프라퍼티 ( 파라미터 ) 에대한검증이끝나면폼으로부터받은입력값을잘정돈된자바빈데이타로만들어 Action 에전달한다. 주의 : ActionForm 객체를직접이용해서작업을수행 ( 비지니스프로세스수행 - 모델부분 ) 을해서는안된다! - 모델부분은컨트롤러와뷰와완전히분리된상태로작성해야한다. ActionForm 은컨트롤러에속한다. ActionForm 을구현하려면.. org.apache.struts.action.actionform 클래스를상속받아야한다. 각프라터티는 HTML Form 의 Input 의 name 과 (<input name= />) 같은이름을가져야한다. 각프라퍼티별로 Setter 와 Getter 가있으면된다. Setter 와 Getter 가꼭둘다있을필요는없다. 각프라퍼티는되도록 String 과 boolean 형으로만든다. 잘못입력한데이터를다시사용자의입력화면에보여주려면잘못입력한데이타가 String 으로보전되어있어야하기때문이다. ActionForm 의프라퍼티를 int 등의형으로만들면사용자가잘못입력한데이터중에서숫자가아닌부분이모두사라지게되기때문에무엇을어떻게잘못입력했는지알수없게된다. 각프라퍼티들을 ActionForm 객체에채우기전에먼저초기화작업을거치고싶다면 public void reset (ActionMapping mapping, HttpServletRequest request) 메소드를구현해야한다. 파라미터값을 ActionForm 에서 Action 으로전달하기전에유효성검증과정을거치려면 public ActionErrors validate(actionmapping mapping, HttpServletRequest request) 메소드를구현해야한다. (ActionErrors 를리턴한다는것주의 ) struts-config.xml 에다음과같은부분을추가하여, ActionForm 을등록한다. 등록된 ActionForm 은여러 Action 에서사용될수도있다. <struts-config> <form-beans> <form-bean name=" 폼의이름 " type="myproject.form.formclass"/> </form-beans> </struts-config> ActionMessages 의역할은... ActionMessages 의역할은기본적으로컨트롤러 ( 그중에서도 ActionForm 과 Action) 에서수행도중발생한오류나기타메시지들을저장하고있다가뷰단에서보여줄수있도록하는역할이다. 이에대해서는예제를본후살펴본다. 15

이제, ActionForm 과 ActionMessages 를기본적으로사용한예제를만들어본다. 바로전에만들었던 login1 을개선해서로그인을하고, 만약사용자가넘긴 username 과 password 파라미터가빈값이거나공백등을포함한다면 ActionForm 에서오류가발생하여다시로그인폼으로돌아가서잘못된부분에대한에러메시지를출력하도록변경한다. 또한로그아웃기능도만들고, 로그인폼은 POST 방식으로전달되도록하며, 몇몇 JSP 페이지들은 Action 이불필요하더라도모든 JSP 가스트럿츠컨트롤러를통해보여질수있도록설정한다. WEB-INF/config/struts-config.xml 을다음과같이작성한다. 기존의 login1 을위한정보들도그냥남겨두었다. 동일한웹컨텍스트이기때문이다. 굵은글씨가바뀐부분이다. <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <struts-config> <form-beans> <!-- Login2 를위한 ActionForm 설정 --> <form-bean name="login2form" type="strutsguide.forms.login2form"> </form-bean> </form-beans> <global-forwards> <!-- Login2 의로그인폼화면으로이동하기위한 global forward --> <forward name="login2form" path="/login2/loginform.do" redirect="true" /> </global-forwards> <action-mappings> <action path="/welcome" forward="/welcome.jsp" /> <!-- // Login2 를위한 Action Mapping 시작 --> <!-- 로그인폼화면출력액션 --> <action path="/login2/loginform" forward="/login2/loginform.jsp" /> <!-- 로그인을수행하는액션 --> <action path="/login2/login" type="strutsguide.actions.login2action" name="login2form" validate="true" scope="request" input="/login2/loginform.jsp"> </action> <!-- 로그아웃수행액션 --> <action path="/login2/logout" type="strutsguide.actions.logoutaction" /> <!-- // Login2 를위한 Action Mapping 끝 --> <action path="/login1/loginform" forward="/login1/loginform.jsp" /> <action path="/login1/login" type="strutsguide.actions.login1action" validate="false"> <forward name="success" path="/login1/loginsuccess.jsp" redirect="true" /> <forward name="fail" path="/login1/loginfail.jsp" /> </action> </action-mappings> <!-- 16

login2에서메시지출력에사용할프라퍼티파일. strutsguide.resource 패키지의 application.properties 파일임을의미한다. --> <message-resources parameter="strutsguide.resources.application" /> </struts-config> /login2/loginform.jsp 를다음과같이작성한다. <%@page contenttype="text/html; charset=euc-kr"%> <%@taglib uri="/web-inf/tlds/struts-html.tld" prefix="html"%> <%@taglib uri="/web-inf/tlds/struts-bean.tld" prefix="bean"%> <%@taglib uri="/web-inf/tlds/struts-logic.tld" prefix="logic"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html:html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title> 로그인폼 </title> </head> <body> <h1> 로그인폼 </h1> <logic:present name="userinfo" scope="session"> <%-- session 스코프에 "userinfo" 라는 Attribute 가존재하면이부분이실행된다. --%> <b> 이미로그인하셨습니다.</b><br /> 로그인한사용자명 : <bean:write name="userinfo" property="username" scope="session"/><br /> 전화번호 : <bean:write name="userinfo" property="phone" scope="session"/><br /> 이메일 : <bean:write name="userinfo" property="email" scope="session"/><br /> <html:link action="/login2/logout"> 로그아웃하기 </html:link> </logic:present> <logic:notpresent name="userinfo" scope="session"> <%-- session 스코프에 "userinfo" 라는 Attribute 가존재하지않으면이부분이실행된다. --%> <b> 로그인하십시오.</b><br/> </logic:notpresent> <!-- 폼의데이터를 "login.do" 로전송한다. --> <html:form action="/login2/login" method="post" focus="username"> <html:messages id="msg" message="true"> <%-- ActionMessages.GLOBAL_MESSAGE 키로저장된 ActionMessage 객체가없다면이부분은실행되지않는다. --%> <b><bean:write name="msg"/></b> <br /> </html:messages> Username : <html:text property="username"/> <html:messages id="msg" property="invalidusernameerror"> <%-- invalidusernameerror 라는키로저장된 ActionMessage 객체가없다면이부분은실행되지않는다. --%> <b><bean:write name="msg"/></b> </html:messages> <br /> 17

Password : <html:password property="password" redisplay="false"/> <html:messages id="msg" property="invalidpassworderror"> <%-- invalidpassworderror 라는키로저장된 ActionMessage 객체가없다면이부분은실행되지않는다. --%> <b><bean:write name="msg"/></b> </html:messages> <br /> <html:submit value=" 로그인 "/> <html:reset value=" 초기화 " /> </html:form> </body> </html:html> 실제로그인하는역할을하는 Model 클래스인 LogInProcess.java 클래스는기존의것을그대로사용하면된다. 이것이 Action 과비지니스로직클래스를분리했을때의힘이다. 컨트롤러부분이아무리바뀌어도비지니스작동이바뀌지않는다면, 모델의비지니스로직은전혀바꿀필요가없다. 만약이부분을 Action 클래스에서작성했다면, 새로운 login2 를작성하면서이부분도완전히새로작성해야했을것이다. 사용자정보를저장하는모델자바빈인 UserInfoBean.java 도기존것을그대로사용한다. 새로운로그인 Action 인 Login2Action.java 를작성한다. package strutsguide.actions; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import javax.servlet.http.httpsession; import org.apache.struts.action.action; import org.apache.struts.action.actionform; import org.apache.struts.action.actionforward; import org.apache.struts.action.actionmapping; import org.apache.struts.action.actionmessage; import org.apache.struts.action.actionmessages; import strutsguide.beans.loginprocess; import strutsguide.beans.userinfobean; import strutsguide.forms.login2form; /** * Login2 에서로그인을실행한다. */ public class Login2Action extends Action { public ActionForward execute(actionmapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { LogInProcess loginprocess = LogInProcess.getInstance(); // 이미기본적인파라미터검증이끝난 ActionForm 객체를가져온다. Login2Form login2form = (Login2Form) form; // 사용자가전달한파라미터를저장한다. String username = login2form.getusername(); String password = login2form.getpassword(); UserInfoBean userinfo = loginprocess.login(username, password); ActionForward forward = null; 18

if (userinfo == null) { // userinfo 가 null 이면사용자가존재하지않거나비밀번호가잘못된것이다. ActionMessages messages = new ActionMessages(); // 글로벌메시지를추가한다. // 이것은 <html:messages id="msg" message="true"> 에의해 JSP 에서출력된다. messages.add(actionmessages.global_message, new ActionMessage( "error.invalidlogin")); // request 객체에메시지추가 savemessages(request, messages); // 원래입력화면으로돌아가도록설정한다. // <action input="" /> 에서 input 에설정된페이지를의미한다. return mapping.getinputforward(); // null 이아니면성공페이지로이동한다. 이동하기전에사용자정보를 session 에저장한다. HttpSession session = request.getsession(); session.setattribute("userinfo", userinfo); // 이동할페이지를찾는다. // <forward name="login2form" /> 을찾아서포워딩한다. forward = mapping.findforward("login2form"); return forward; loginform.jsp 가전달해주는파라미터를 Login2Action 에가기전에데이터를저장하고유효성을검증하는 Login2Form.java ActionForm 클래스를작성한다. package strutsguide.forms; import javax.servlet.http.httpservletrequest; import org.apache.struts.action.actionerrors; import org.apache.struts.action.actionform; import org.apache.struts.action.actionmapping; import org.apache.struts.action.actionmessage; /** * Login2Action 을위한 Action Form * */ public class Login2Form extends ActionForm { /** 사용자명 */ private String username = null; /** 비밀번호 */ private String password = null; /** * 사용자명설정하기 * @param username 사용자명 */ public void setusername(string username) { this.username = username; /** 19

* 사용자명리턴하기 * return 사용자명 */ public String getusername() { return username; /** * 비밀번호설정하기 * @param password 비밀번호 */ public void setpassword(string password) { this.password = password; /** * 비밀번호리턴하기 */ public String getpassword() { return password; /** * 사용자명과비밀번호를올바르게입력했는지여부를검사한다. * * validate() 가호출되기전에이미 setusername() 과 setpassword() 가호출되어 * username 과 password 멤버변수에값을설정한상태이다. * * @param mapping 액션매핑객체 * @param request HTTP Request 객체 * @return 에러여부 */ public ActionErrors validate(actionmapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors(); // 사용자명을입력하지않았거나공백을포함하고있을경우 if (username == null username.length() == 0) { errors.add("invalidusernameerror", new ActionMessage("error.invalidUsername", " 사용자명을입력해주세요.")); else if (username.indexof(" ") >= 0 username.indexof("\t") >= 0 username.indexof("\n") >= 0) { errors.add("invalidusernameerror", new ActionMessage("error.invalidUsername", " 사용자명은공백을포함할수없습니다.")); // 비밀번호를입력하지않았을경우 if (password == null password.length() == 0) { errors.add("invalidpassworderror", new ActionMessage("error.invalidPassword")); /* * ActionErrors errors 객체가 null 이거나아무런 ActionMessage 객체도포함하고있지않으면 * 오류가발생하지않았다고가정한다. */ return errors; 로그아웃역할을하는 LogoutAction.java 를작성한다. package strutsguide.actions; import org.apache.struts.action.*; 20

import javax.servlet.http.*; /** * 로그아웃한다. */ public class LogoutAction extends Action { public ActionForward execute(actionmapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getsession(); // 세션정보를삭제한다. session.invalidate(); // <forward name="login2form" path="/login2/loginform.do" /> 이정보에따라 // 포워딩을한다. return (mapping.findforward("login2form")); 오류메시지들을저장하고있는프라퍼티파일을생성하자. 프라퍼티파일은 /WEB- INF/classes/strutsguide/resources/application.properties 로생성한다. error.invalidusername= 잘못된사용자명입니다. {0 error.invalidpassword= 비밀번호를입력하지않았습니다. error.invalidlogin= 로그인사용자명이존재하지않거나비밀번호가일치하지않습니다. 헌데, *.properties 파일은원칙적으로한글을포함할수없다. 저것을그대로복사해서넣으면한글이다깨져버린다. 일단한글로작성하고 Java 에함께포함된 native2ascii 유틸리티를이용해다음과같이변환해서넣어야만한다. 줄이나뉘었을경우합쳐서한줄에두어야만한다. error.invalidusername=\uc798\ubabb\ub41c \uc0ac\uc6a9\uc790\uba85\uc785\ub2c8\ub2e4. {0 error.invalidpassword=\ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud558\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. error.invalidlogin=\ub85c\uadf8\uc778 \uc0ac\uc6a9\uc790\uba85\uc774 \uc874\uc7ac\ud558\uc9c0 \uc54a\uac70\ub098 \ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. 자, 이제한번실행하면서이어플리케이션의흐름을따라가보자. /login2/loginform.do 를호출하면된다. 삽화 7 로그인정보입력화면 로그인정보를입력하는화면으로, /login2/loginform.do 를호출하면 struts-config.xml 의 <action path="/login2/loginform" forward="/login2/loginform.jsp" /> 에의해 /login2/loginform.jsp 를호출하게 21

된다. 여기서올바르지않은정보를입력하고 로그인 버튼을누르게되면다음과같은오류가발생하게된다. 삽화 8 username 과 password 를형식에맞지않게입력 사용자명과비밀번호를입력하고서 로그인 버튼을누르면제일먼저 <form-bean name="login2form" type="strutsguide.forms.login2form"/> 과아래액션설정에의해 ActionForm 인 Login2Form 에전달된다. 아래에서굵은글씨로된부분모두가 Action 이사용할 ActionForm 에대한설정이다. <action path="/login2/login" type="strutsguide.actions.login2action" name="login2form" validate="true" scope="request" input="/login2/loginform.jsp"> </action> /login2/login.do Action 이호출되면액션을수행하기전에먼저 login2form 이라는 <form-bean> ActionForm 설정을찾아서파라미터값을설정 (name= login2form ) 하고유효성검증을하며 (validate= true ) ActionForm 객체를 request 스코프에저장하고 (scope= request ) ActionForm 의유효성검증에서에러가발생하면 /login2/loginform.jsp 로포워딩해서다시입력을받으라 (input= /login2/loginform.jsp ) 는의미이다. 그상태에서 Login2Form.setUsername() 과 Login2Form.setPassword() 메소드가자동으로호출되어파라미터 username 과파라미터 password 의값이설정되게된다. 그직후 Login2Form.validate() 메소드가호출되어 username 과 password 값의유효성을검사한다. 여기서는 username 이빈값이거나공백을포함할수없고패스워드가빈값일수없다는규칙이있으며, 저그림에보이는입력은그규칙을모두위반한다. validate() 메소드는위반한규칙에대한안내메시지를 ActionErrors 객체에 ActionMessage 객체를만들어저장하고, 그것을리턴한다. 이상태에서 RequestProcessor 는 Login2Form.validate() 의리턴값인 ActionErrors 객체를검사한다. ActionErrors 객체가 null 이거나아무런 ActionMessage 객체도포함하고있지않으면그대로 Action 을호출하지만이번경우에는에러메시지가존재하기때문에 Action 을전혀호출하지않고바로 input= /login2/loginform.jsp 에따라 /login2/loginform.jsp 로포워딩을하게된다. 저위그림에서주소창에나온최종 URL 은 /login2/loginform.jsp 가아니라 /login2/login.do 라는것을볼수있다. 포워딩한것이기때문이다. 이와같이오류가발생해서다시입력폼으로돌아올때스트럿츠는 ActionForm 객체를 scope= request 에따라 request 스코프에저장한다. 그리고입력폼에돌아와서는잘못입력했던값들을화면에다시보여주게된다. 그리고그옆에개발자가 ActionErrors 객체에저장한오류메시지들도함께출력해준다. 다음화면은형식에맞는사용자명과비밀번호를입력했지만사용자명이존재하지않거나비밀번호를틀리게입력하고 로그인 을하고난뒤의상황이다. 22

삽화 9 비밀번호가틀렸거나사용자명이존재하지않음 이번경우에는 ActionForm 에서유효성검증을통과했다. 그리고서 Action 을실행했다. Action 을실행하면서비밀번호가잘못됐기때문에 ActionMessages 객체에사용자가존재하지않거나비밀번호가틀렸다는메시지를저장하고는 return mapping.getinputforward(); 과 input= /login2/loginform.jsp 에의해서다시입력화면으로포워딩을하고에러메시지와기존에입력했던사용자명을출력하게된다. 아니뭔가이상하다? Action 에서는에러메시지를 ActionMessages 객체에저장하고, ActionForm.validate() 에서는 ActionErrors 객체에저장해서리턴한다. 왜다르지? 조금만뒤에서알아보자. 자, 이제제대로된사용자명과비밀번호를입력해보자. 그러면다음과같이사용자정보를출력하고로그아웃링크와다시로그인할수있는폼을볼수있게된다. 삽화 10 정상적으로로그인됨 위화면을보면최종 URL 이 /login2/loginform.do 인것을볼수있다. 그것은 login.do 에서정상적으로로그인을마치면 forward = mapping.findforward("login2form"); 에의해 login2form 이라는 <forward> 를찾아서포워딩을하게되는데, login2form 은다음과같이지정되어있다. <global-forwards> <!-- Login2 의로그인폼화면으로이동하기위한 global forward --> <forward name="login2form" path="/login2/loginform.do" redirect="true" /> </global-forwards> <global-forwards> 는특정액션에종속적이지않은포워드정보들을모아놓는곳이다. 여기서지정된 <forward> 들은모든 Action 에서사용할수있다. 단, 각개별액션매핑에이미 <global-forwards> 에지정된 <forward> 와동일한이름 (name 속성 ) 의 <forward> 가존재할경우항상액션매핑에정의된지역포워드정보가우선적으로선택된다. 23

여기서 login2form 포워드를지정했고, redirect= true 설정에의해포워딩이아닌리다이렉트로작동하게되는것이다. 최종적으로 로그아웃하기 링크 (/login2/logout.do) 를누르면 <action path="/login2/logout" type="strutsguide.actions.logoutaction" /> 에의해서로그아웃액션인 LogoutActon 을호출하게되고세션에있는사용자정보를삭제하여로그아웃한다음에바로위에서처럼 login2form 포워드를타고로그인하기전의첫화면으로돌아가게된다. 그런데, 프로그램의흐름은다설명했지만, 어떻게오류메시지들이출력되고, 잘못입력했던사용자명이입력화면에다시나타나는지등과 /login2/loginform.jsp 에나오는 <html:form> 등의이상한태그에대해서는설명하지않았다. 이제그러한스트럿츠의 HTML 폼과오류메시지출력등에관한라이브러리와커스텀태그라이브러리에대해서간단하게알아보자. 커스텀태그 JSP 에서는커스텀태그가나오기전까지스크립틀릿 (Scriptlet) 이라고하여자바코드를직접 JSP 페이지에코딩하는방법으로출력할내용들을제어했다. 이제는커스텀태그덕분에 JSP 에서자바코드를완전히몰아내는게가능하고그렇게할것을널리권장하고있다. 커스텀태그는자바코드를미리클래스로만들고 *.tld 파일에태그이름과그태그를만날때실행할자바클래스를지정해놓고서그태그를만나면실제로는지정된자바클래스가실행되는것이다. 헌데실질적으로커스텀태그가있다고해서자바코드를없앨수는없다. Model 1 방식으로설계를했을경우에는자바코드 ( 스크립틀릿 ) 를완전히없앨수없다. 복잡한비지니스로직을 JSP 내에서커스텀태그로모두대체한다는것은거의불가능하고가능하다해도그렇게하려고하면오히려코드의복잡도를증가시키기때문에어쩔수없이자바코드를쓰게된다. 하지만 Model 2 로설계하게되면문제가달라진다. 복잡한비지니스로직이모두 Action 같은자바클래스로들어가버리고 JSP 에서는순수하게출력만하게된다. 순수하게출력만할경우의로직은그다지복잡하지않으며스트럿츠의커스텀태그와 JSTL 만으로도그모든로직을커버할수있다. 커스텀태그를사용해서얻어지는이점은굉장히많다. 일일이설명하지않겠다. 그냥가능하다면무조건커스텀태그를사용하고자신의 JSP 파일에서자바코드를몽땅몰아낼수록유지보수성이높아진다는것만알아두고, Model 2 에서는이게충분히가능하므로적극적으로활용하자. 스트럿츠는기본적으로 Html, Logic, Bean, Nested, Tiles 커스텀태그를제공하며각기하는역할이다다르다. 여기서는이모든것을설명하지않는다. 다른스트럿츠전문서적을참조할것. 스트럿츠에서는 /WEB-INF/tlds/*.tld 파일의경로를 uri 로지정하여커스텀태그를사용한다고 JSP 에설정할수있다. 다음과같다. <%@taglib uri="/web-inf/tlds/struts-html.tld" prefix="html"%> <%@taglib uri="/web-inf/tlds/struts-bean.tld" prefix="bean"%> <%@taglib uri="/web-inf/tlds/struts-logic.tld" prefix="logic"%> <%@taglib uri="/web-inf/tlds/struts-tiles.tld" prefix="tiles"%> prefix 는사실아무렇게나줘도상관없지만위와같이주는것이보편적이다. 그냥지키는게낫다. uri 의경우 *.tld 파일에서 <uri>...</uri> 지정된 http://struts.apache.org/... 로시작되는값을줘도상관없다. 이방식을사용할경우서블릿스펙 2.3 이전 ( 톰캣 3.x) 에서는 web.xml 에 *.tld 파일과그에매칭될 uri 정보를작성해줘야했으나서블릿스펙 2.3 이상 ( 톰캣 4.x 이상 ) 에서는그럴필요가없다. 그리고이경우에 tld 파일을따로 WEB-INF/tlds 디렉토리에복사할필요도없다. 웹컨테이너가 struts.jar 파일에있는 *.tld 파일정보를자동으로인식한다. 이문서에서는 *.tld 파일의경로를기준으로삼겠다. *.tld 파일의경로를기준으로삼을경우에는 web.xml 에서특별히뭘해줄필요가없다. 단지 WEB-INF/tlds 디렉토리에 *.tld 파일을복사해두기만하면된다. http://struts.apache.org/userguide/index.html 이페이지에서각태그의용법을익힐수있다. HTML 폼을스트럿츠커스텀태그로만들기 - 폼의값이 ActionForm 에의해자동으로채워짐 /login2/loginform.jsp 를보면일반 HTML 태그가아니라 <html: 어쩌구 > 하는태그들을이용해서이뤄짐을 24

볼수있다. 실제 /login2/loginform.do 를실행하고서소스보기를해보면다음과같은 HTML 이자동으로생성되어있음을볼수있다. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <base href="http://localhost:8080/strutsguide1/login2/loginform.jsp"> <title> 로그인폼 </title> </head> <body> <h1> 로그인폼 </h1> <b> 로그인하십시오.</b><br/> <!-- 폼의데이터를 "login.do" 로전송한다. --> <form name="login2form" method="post" action="/strutsguide1/login2/login.do;jsessionid=56ba04512188fa92077a907127cf1d34"> Username : <input type="text" name="username" value=""> <br /> Password : <input type="password" name="password" value=""> <br /> <input type="submit" value=" 로그인 "> <input type="reset" value=" 초기화 "> </form> <script type="text/javascript" language="javascript"> <!-- var focuscontrol = document.forms["login2form"].elements["username"]; if (focuscontrol.type!= "hidden" &&!focuscontrol.disabled) { focuscontrol.focus(); // --> </script> </body> </html> 위와같이 JSP 에입력한적이없는정보들이커스텀태그의작동에의해자동으로입력되었음을볼수있다. <html:html> <html> 태그를의미한다. <html:base/> <base href= /> 태그를자동생성해준다. 스트럿츠처럼가상의 URL 을 JSP 에매핑시켜사용할경우, 이미지파일등의상대경로가 JSP 기준이아닌가상 URL 경로에의해결정되게된다. 그것을막고 JSP 의경로에의해다른리소스의상대경로를지정할수있게해주는것이다 <html:base/> 의역할이다. <html:form action="/login2/login" method="post" focus="username"> <form> 태그를생성한다. action 은 <form> 태그에서와는달리 struts-config.xml 의 <action path= /> 의 path 에해당하는값을의미한다. method 는 GET 과 POST 두가지방식이다. focus 는폼이출력된뒤에포커스를둘입력박스를의미한다. action= /login2/login 은자동으로 struts-config.xml 의 <action path= /login2/login name= login2form /> 의 name 속성을통해서 <form-bean name= login2form... /> 의정보를읽어들인다. 그리고 ActionForm 의 validate() 에서에러가발생하면, ActionForm 객체가 request 혹은 session 등 <action scope= /> 에설정한스코프에저장되고, <html: 어쩌구 > 태그들은해당액션폼빈의값을각 <input> 요소들에자동으로설정해주게된다. <html:text property="username"/> 25

<input type= text > 를생성해준다. property= username 은 <input> 에 name= username 을추가하고 ActionForm 객체가 session 혹은 request 에존재할경우 ActionForm 에서 getusername() 해서가져온값을이 input 박스의 value 로설정하겠다는의미가된다. <html:password property="password" redisplay="false"/> <input type= password /> 를생성해준다. property 는 <html:text> 와마찬가지이다. 단, redisplay= false 는 ActionForm 이스코프에존재하더라도화면상에 value= 설정을하지않도록해준다. 비밀번호니까당연히하면안된다. <html:submit value=" 로그인 "/> <input type= submit > 버튼을생성한다. <html:reset value=" 초기화 " /> <input type= reset > 버튼을생성한다. 이이외에도 HTML 의 FORM 과관련된거의모든태그가 <HTML: 어쩌구 > 커스텀태그로존재하며스트럿츠프레임워크와유기적으로작동한다. 에러혹은일반메시지출력하기 스트럿츠는 ActionForm 의 validate() 메소드에서 ActionErrors 를리턴하거나 Action 에서 savemessages() 메소드를이용해 JSP 에서출력해줄에러혹은일반메시지를설정할수있다. 메시지를저장하는클래스는두가지종류로나뉘었다. ActionErrors 와 ActionError 그리고 ActionMessages 와 ActionMessage 이다. 이두종류의메시지저장방식은사실상동일한역할을하고동일한방식으로작동하는데단지에러메시지냐일반메시지냐에따라구분한것이다. 이제부터는이중에서 ActionErrors 와 ActionError(deprecated 됨 ) 는사용을권장하지않는다. 앞으로는 ActionMessages 와 ActionMessage 만사용하도록한다. ActionMessages 는메시지들을키값을할당하여저장하는역할을한다. ActionMessage 는메시지그자체이며 *.properties 로저장된리소스번들로부터메시지를가져다필요한값을더채워넣은것이다. 바로위에서 ActionErrors 와 ActionError 를사용하지말라고했는데한가지문제가있다. ActionForm 의 validate() 메소드의리턴형이 ActionErrors 라는것이다. 이미이메소드는몇년전부터사용되었기때문에함부로바꾸면스트럿츠버전업에문제가생긴다. 이때문에 ActionError 는 deprecated 되었지만 ActionErrors 는 deprecated 될수없었다 ( 하지만앞으로나올버전에서는 deprecated 될수도있다고한다 ). ActionForm.validate() 에서는 ActionErrors 를메시지저장소로사용하고실제메시지객체는 ActionMessage 로생성하며, 일반 Action 에서는오류메시지와일반메시지모두 ActionMessages 와 ActionMessage 를이용해서생성하도록한다. ActionForm.validate() 와 Action 에서사용하는예는이미보았지만하나하나따라가며설명해보자. 먼저에러메시지의내용을저장하고있을 *.properties 를생성해야한다. 그리고그 properties 파일을 struts-config.xml 에설정해야한다. <message-resources parameter="strutsguide.resources.application" /> 보다싶이자바의패키지구조와같게설정하고맨마지막에.properties 는적지않는다. 위와같이설정하면스트럿츠는 /WEB-INF/classes/strutsguide/resources/application.properties 를찾아서메모리에올려두게된다. 그리고는 ActionErrors 혹은 ActionMessages 객체를생성한다 (ActionForm.validate() 메소드에서는물론 ActionErrors 객체로생성해야한다 ). ActionMessages messages = new ActionMessages(); 이제오류가발견되면 ActionMessage 객체를하나하나생성해서 messages 객체에저장하면된다. messages.add("invalidusernameerror", new ActionMessage("error.invalidUsername", " 사용자명을입력해주세요.")); 첫번째인자 invalidusernameerror 는메시지의키값이다. JSP 파일에서이키를이용해서지정된에러메 26

시지를원하는위치에출력할수있다. 두번째인자는 ActionMessage 의객체이다. ActionMessage 객체의첫번째인자는 application.properties 에있는프라퍼티키값이다. ActionMessage 는프라퍼티파일에서해당키의실제문자열 ( 여기서는 잘못된사용자명입니다. {0 ) 을가져온다. 그리고는 {0 을그다음인자로대체한다. 즉, 최종문자열은 잘못된사용자명입니다. 사용자명을입력해주세요. 가되는것이다. {0 처럼대체문자열을지정하는것은최대 4 개까지가능하다. 즉 {0, {1, {2, {3 을프라퍼티파일의값으로지정할수있다. 또한 ActionMessage 의객체생성시그에따라최대 4 개까지의대체문자열을생성자에서제공해줄수있다. 이렇게메시지를저장하고서 ActionForm.validate() 는 return messages 를함으로써, Action 의경우에는 savemessages(messages) 를함으로써그다음에오는 JSP 페이지에이메시지들을제공할수있게되는것이다. 메시지를제공받은 JSP 페이지에서는다음처럼출력할수있다. <html:messages id="msg" property="invalidusernameerror"> <%-- invalidusernameerror 라는키로저장된 ActionMessage 객체가없다면이부분은실행되지않는다. --%> <b><bean:write name="msg"/></b> </html:messages> <html:messages> 에서 property= 는메시지를 add 할때지정한키를의미한다. 그키에해당하는메시지가있다면그메시지를 msg 라는변수에저장하라는것이 id= msg 의의미이다. 이렇게저장된 msg 변수를 <bean:write name= msg /> 가출력을시켜준다. 위와같이메시지에키를지정하여저장할수도있지만, 그렇지않고특별한키값없이메시지를저장할수도있다. messages.add(actionmessages.global_message, new ActionMessage( error.invalidlogin")); 위와같이 ActionMessages.GLOBAL_MESSAGE 를키로주면된다.. ActionMessages 가아닌 ActionErrors 의경우에는 ActionErrors.GLOBAL_ERROR 를키로줘야한다. 그리고출력은 <html:messages id="msg" message="true"> <%-- ActionMessages.GLOBAL_MESSAGE 키로저장된 ActionMessage 객체가없다면이부분은실행되지않는다. --%> <b><bean:write name="msg"/></b> <br /> </html:messages> 위와같다. message= true 는이것이 ActionErrors 가아니라 ActionMessages 의메시지임을의미한다. ActionErrors 의경우에는 message= false 로지정한다. 동일한키로여러개의메시지를저장할수있는데, <html:messages> 는 id= msg 에지정된 msg 변수에각메시지를저장해서내부의 <b><bean:write name="msg"/></b> <br /> 를반복호출한다. 여기서는간단히만언급하자면 *.properties 를통해여러나라말로메시지를작성할수있고, 웹브라우저에설정된언어에따라서로다른언어의메시지를보여주는것이가능하다 (i18n; 국제화기능 ). 27

5. 스트럿츠커스텀태그들 <bean:write> Action 은 Action 의실행결과를 request[session,application].setattribute() 등으로스코프에저장해서뷰인 JSP 로전달한다고했다. 이것은대부분 <bean:write name= attribute 이름 property= 프라퍼티이름 /> 으로출력가능하다. <bean:write> 에서 property 를생략하면 name 에지정된객체를그냥출력한다. 하지만 property= email 처럼지정하면 name 에지정된객체에서.getEmail() 을실행하여그결과값을출력한다. <bean:write name= userinfo property= address.postcode /> 처럼지정했다면, userinfo.getaddress ().getpostcode() 와같은코드를실행해서그결과를출력한다. <bean:write> 는기본적으로 HTML 의특수문자들 (&, <, > 등 ) 을 &, < 등처럼바꿔서출력해준다. 그렇게하지않고있는그대로출력하고자할때는 <bean:write filter= false /> 에서처럼 filter 속성을 false 로지정하면된다. <html:rewrite> <html:rewrite> 는 HTML 작성에서매우유용하게사용될수있다. 일반적으로 HTML 페이지에 <img src= /image/button.gif > 와같이지정하면절대로안된다. 웹컨텍스트의경로가 / 일경우에는상관없지만만약에라도웹컨텍스트경로가 /strutsexample 과같은식으로바뀐다면저기서 src 에지정한 URL 은아무의미가없어진다. 실제로요청해야할 URL 이 /strutsexample/image/button.gif 로바뀌어버리기때문이다. 그렇다고상대경로로지정하면매우일이귀찮아지고잘못입력할확률도높아진다. 이럴때 <html:rewrite page= /> 를사용하면웹컨텍스트경로의변경을자동으로반영해준다. 즉, 전자의경우에는 /image/button.gif 를그대로출력했다가웹컨텍스트가 strutsexample 로바뀔경우에는 /strutsexample/image/button.gif 로출력했다가하는것을알아서해주는것이다. 위의경우에는 <img src= <html:rewrite page= /image/button.gif /> > 처럼사용하면된다. src 속성을잘보면값을작은따옴표로감쌌음을볼수있다. 이유는 <html:rewrite page= > 에서큰따옴표가사용되었기때문이다. 서로겹치지않게해줘야한다. 웹컨텍스트의이름이바뀌는문제와 form 의 action 속성혹은 img 의 src 속성등에 / 로시작하는절대경로지정문제는많은초보웹개발자들이실수하는부분이다. 내글이시원찮아이해가잘안된다면꼭이에관한사항을다른이들에게물어서라도확인하고이해하고넘어가야한다. 28

6. RequestProcessor 상속하기 MVC 패턴에서 Controller 가있기때문에좋은점이많다. 그중의한가지는모든액션을수행하기전에실행해야할어떤작업이있을때그작업을 Controller 한곳에시켜두면다른모든액션에일괄적으로적용된다는것이다. 이와같이 Controller 에서사용자가원하는코드를집어넣을수있는방법이 RequestProcessor 를상속받아서원하는기능을구현하는것이다. RequestProcessor 는스트럿츠의기본컨트롤러이다. RequestProcessor 는 request 와 response 등의객체를차례대로여러메소드에통과시켜전처리작업을수행하고, Action 을호출하는과정을수행한다. RequestProcessor 의그러한과정의메소드들중에서사용자가주로오버라이드해서사용하는것들이있는데, processpreprocess : 범용전처리훅. Action 을호출하기전에 request 와 response 객체에이거저거필요한일들을여기서해준다. processroles : Action 을호출하기전에, 이 Action 을호출하려면특정한롤에속해야하는지를검사한다. 이것을통해단순히스트럿츠설정파일만을이용해서사용자가특정페이지에접속할수있는권한이있는지검사할수있다. 사용자가상속받아만든 RequestProcessor 지정하기 struts-config.xml 에다음설정을해주면된다. <controller processorclass="strutsguide.controller.newrequestprocessor"/> processorclass 속성이자신이작성한새로운 RequestProcessor 클래스를등록하면된다. <controller> 는 <action-mapping> 아래에위치한다. 위치가틀리면작동하지않는다. 각요소별자세한위치는 struts-blank.war 에있는 struts-config.xml 을참조하면알수있다. processroles() 메소드오버라이드 기본적인오버라이드형태 protected boolean processroles( HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws IOException, ServletException { // 현재수행할 Action 이요구하는 Role 의목록을얻는다. String roles[] = mapping.getrolenames(); // request 나 session 객체의정보를통해서 // 현재사용자가 roles[] 에해당하는지검사한다. // 해당 Action 을수행할수있는 Role 에속하면 return true; // 아니면 return false; 현재 Action 의수행에필요한 Role 목록은 struts-config 의 action 에서설정한다. <action-mappings> <action roles="admin,manager" 29

... /> </action-mappings> 따로예제를살펴보지는않겠다. processpreprocess 오버라이드 이메소드는사실상잡다한일들을다해주면된다고봐도된다. 여기서는우리나라개발자들이폼에서넘어온한글파라미터값을제대로인식시키기위해 JSP 에필수적으로사용하는 request.setcharacterencoding(); 작업을수행해보자. NewRequestProcessor 컨트롤러작성 package strutsguide.controller; import java.io.unsupportedencodingexception; import org.apache.struts.action.requestprocessor; import javax.servlet.http.*; /** * RequestProcessor 를상속받아한글인코딩부분을설정한다. */ public class NewRequestProcessor extends RequestProcessor { /** * 모든 Action 이실행되기전에 request 와 response 에실행되어야할사항들을실행한다. */ protected boolean processpreprocess(httpservletrequest request, HttpServletResponse response) { try { // HTTP 파라미터의인코딩을설정한다. request.setcharacterencoding("euc-kr"); catch (UnsupportedEncodingException e) { // do nothing; return true; 이제부터는이컨트롤러를사용하는모든액션과 JSP 에 request.setcharacterencoding( euc-kr ) 을전혀해줄필요가없다. 모든사용자의요청의 request 와 response 객체가이컨트롤러의 processpreprocess() 를거쳐가기때문에자동으로된다. 이것을실제사용한예는 Validator 부분에서볼수있다. 그리고, 뒤에서 Tiles 와함께이것을사용하게될텐데, Tiles 를사용할경우에는 RequestProcessor 가아니라 org.apache.struts.tiles.tilesrequestprocessor 를상속받아야만한다. 30

7. Tiles 이해하기 Tiles 를왜써? Tiles 는웹페이지의상단이나하단, 메뉴와같은반복적으로사용되는부분들에대한정보를한곳에모아두도록해주는프레임워크이다. <jsp:include> 나 <%@include%> 등을이용해비슷한역할을할수있지만 Tiles 가훨씬뛰어나다. JSP Include 방식의문제점은매 JSP 페이지마다모두동일한페이지들을 include 한다고표시를해야한다. 만약 include 하는페이지의이름이바뀌기라도한다면그에해당하는모든페이지를다수정해야만한다. Tiles 는반복사용되는부분을설정파일에한번만설정하고, 계속해서변하는부분만을따로지정할수있다. 반복사용되는부분에변화가있더라도설정파일의한부분만바꾸면된다. Tiles 의기본 Tiles 는기본적으로두가지로이루어진다. 레이아웃 (Layout) 과 Tiles Definition. 레이아웃 : 모든페이지에기본적으로표시될 HTML 태그들과반복적으로사용되는부분들이어디에위치해야할지를정의한 JSP 파일이다. Tiles Definition : 레이아웃에서반복적으로사용되는부분들의실제 HTML 혹은 JSP 파일을지정하는설정이다. XML 혹은 JSP 로설정할수있는데, 보통은 XML 을사용한다. Tiles 를사용할꺼야 struts-config.xml 에다음과같이 Tiles Plugin 을설정한다. <plug-in classname="org.apache.struts.tiles.tilesplugin"> <set-property property="definitions-config" value="/web-inf/config/tiles-defs.xml"/> <set-property property="definitions-debug" value="2"/> <set-property property="definitions-parser-details" value="2"/> <set-property property="definitions-parser-validate" value="true"/> </plug-in> <set-property property="definitions-config" value="/web-inf/config/tiles-defs.xml"/> 이부분이 Tiles Definition 설정파일을지정하는부분이다. 이제타일즈를사용할수있게되었다. Layout 만들기 Layout 은화면에출력할내용의뼈대를구성하는 JSP 파일이다. 다음예제를 "/tiles/classiclayout.jsp" 라는이름으로작성한다. <%@page contenttype="text/html; charset=euc-kr"%> <%@taglib uri="/web-inf/tlds/struts-tiles.tld" prefix="tiles"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title><tiles:getasstring name="title" /></title> </head> <body> <h1><tiles:getasstring name="title"/></h1> <table border="1" width="100%"> <tr> <td colspan="2"><tiles:insert attribute="header"/></td> </tr> <tr> 31

<td width="140"><tiles:insert attribute="menu"/></td> <td><tiles:insert attribute="body"/></td> </tr> <tr> <td colspan="2"><tiles:insert attribute="footer"/></td> </tr> </table> </body> </html> <%@taglib uri="/web-inf/tlds/struts-tiles.tld" prefix="tiles"%> 를통해 Tiles 커스텀태그를사용하겠다고설정한다. <tiles:insert attribute="footer"/> 를통해서이커스텀태그가위치한부분에 Tiles Definition(tilesdefs.xml) 에서 "footer" 라는이름으로지정한 JSP 혹은 HTML 등을출력해주겠다고표시한다. <tiles:getasstring name="title"/> 는 Tiles Definition 에서 "title" 로지정한값을화면에그대로문자열로출력하라는뜻이다. 이레이아웃 JSP 파일을적당한위치에저장 ( 여기서는 /tiles/classiclayout.jsp) 한다. Tiles Definition : 레이아웃에실제내용채워넣기 위의레이아웃은헤더, 메뉴, 바디, 푸터로구성되어있다. 여기서헤더, 메뉴, 푸터는항상동일하고바디부분만계속바뀐다. 기존 JSP Include 방식에서는항상동일한헤더, 메뉴, 푸터에대한 Include 를바디가바뀌는부분마다모두선언해야했다. 하지만, Tiles 는그렇지않다. 타일즈의 Definition 을다음과같이 "/WEB-INF/config/tiles-defs.xml" 이라는이름으로만든다. <?xml version="1.0" encoding="euc-kr"?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/tiles-config_1_1.dtd"> <tiles-definitions> <definition name=".layout-main" path="/tiles/classiclayout.jsp"> <put name="title" value=" 타일즈사용하기 ~"/> <!-- 레이아웃에서 <tile:getasstring> 으로그대로문자열로출력되는값 --> <put name="header" value="/tiles/header.jsp"/> <put name="menu" value="/tiles/menu.jsp"/> <put name="body" value="/tiles/body.jsp"/> <put name="footer" value="/tiles/footer.jsp"/> </definition> <definition name=".layout-menu1" extends=".layout-main"> <put name="body" value="body-menu1.jsp"/> </definition> <definition name=".layout-menu2" extends=".layout-main"> <put name="body" value="body-menu2.jsp"/> </definition> </tiles-definitions> <definition> : 레이아웃내용채우기설정의한단위이다. 한개의레이아웃에대해여러개의 definition 들이존재할수도있다. <definition name= /> : definition 이름. 다른이름들과동일하면안된다. 이름을통해각단계를나타내기위해점 (.) 을사용한다. <definition path= /> : 이 definition 에서지정한 JSP 혹은 HTML 파일들이실제로삽입될레이아웃 JSP 파일을지정한다. <put> : 레이아웃에삽입될 JSP 혹은 HTML 파일을지정한다. <put name= /> : 레이아웃에서사용된각부분별명칭. <put value= /> : 실제로삽입될내용이있는 JSP, HTML 등. 32

definition 확장하기 Tiles 의장점은바로다른 definition 을확장하여바뀌지않는부분은부모 definition 에설정하고바뀌는부분 ( 바디 ) 만자식 definition 에설정하여, 자식 definition 을사용해서레이아웃에삽입되어들어갈내용을지정할수있다는것이다. <definition extends= 부모 definition 의 name /> 을이용해서한다. 여기서는 "body" 로지정된부분의 JSP 만계속바뀌므로그것만바꿔지정한다. 부모 definition 과자식 definition 모두다사용될수있다. Tiles 사용하기 이제설정을마쳤으니, 실제출력할부분을만들수있다. 그전에 struts-config.xml 에서지금만든데피니션을 Action 의 Forward 로지정해야한다. <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <struts-config> <action path="/tiles/main" forward=".layout-main"/> <action path="/tiles/menu1" forward=".layout-menu1"/> <action path="/tiles/menu2" forward=".layout-menu2"/> </action-mappings> <plug-in classname="org.apache.struts.tiles.tilesplugin"> <set-property property="definitions-config" value="/web-inf/config/tiles-defs.xml"/> <set-property property="definitions-debug" value="2"/> <set-property property="definitions-parser-details" value="2"/> <set-property property="definitions-parser-validate" value="true"/> </plug-in> </struts-config> 각 Tiles Definition 을 <action forward=""> 에서 forward 의값으로지정하거나, <forward path=""> 에서 path 의값으로지정할수있다. 여기서는가장단순하게 <action forward=""> 에제만본다. 실질적으로 definition 의개수만큼의 forward 가필요하다. 이제는실제로화면에추가될 JSP 파일들을생성한다. header.jsp, footer.jsp, menu.jsp 는세가지 definition 이모두공통으로사용하므로한개씩만만들고, body 부분에대한 JSP 는세개모두만든다. header.jsp <%@page contenttype="text/html; charset=euc-kr"%> <h2 align="center">struts Tiles 예제입니다.~</h2> footer.jsp <%@page contenttype="text/html; charset=euc-kr"%> <div align="center" style="background: yellow;"> 우리회사 </div> menu.jsp <%@page contenttype="text/html; charset=euc-kr"%> <%@ taglib uri="/web-inf/tlds/struts-html.tld" prefix="html"%> 33

<ul> <li><html:link action="/tiles/main" > 첫화면 </html:link> <li><html:link action="/tiles/menu1"> 메뉴 1</html:link> <li><html:link action="/tiles/menu2"> 메뉴 2</html:link> </ul> body.jsp <%@page contenttype="text/html; charset=euc-kr"%> <b> 첫화면바디 ~</b> body-menu1.jsp <%@page contenttype="text/html; charset=euc-kr"%> <br /> <br /> 메뉴 1 이랍니다 ~ body-menu2.jsp <%@page contenttype="text/html; charset=euc-kr"%> <br /> <br /> 메뉴 2 랍니다 ~ 실제화면출력모양 주메뉴 (/tiles/main.do) 메뉴 1 (/tiles/menu1.do) 메뉴 2 (/tiles/menu2.do) 34

35

8. Validator 사용하기 지금까지 ActionForm 에저장된값의유효성을검사할때 validate() 메소드를사용하였는데, 이렇게할경우두종류이상의 ActionForm 이동일한규칙을가진프라퍼티를가지고있을때도각각의 validate() 에동일한유효성검증코드를넣어야하는문제가있고, 유효성검증규칙이바뀔때마다 validate() 의코드를수정해서재컴파일해야한다는문제가있다. 그래서스트럿츠 Validator( 유효성검사기 ) Framework 이나왔다. Validator 프레임워크는설정 XML 파일을이용해서각 ActionForm 프라퍼티들의유효성을검사할수있게해준다. ActionForm 에 validate() 를만들필요가없다. Validator 는두개의설정파일을가진다. valdator-rules.xml : 검증규칙을선언한다. 예를들면 주민등록번호는 xxxxxx-1xxxxxx 혹은 xxxxxx- 2xxxxxx 처럼앞에 6 자리뒤에 1 혹은 2 로시작하는 7 자리숫자로구성된다 라는식의규칙만을저장하고있는것이다. 여기서선언된규칙을여러 ActionForm 을검증하는데사용할수있게된다. validation.xml : 각 ActionForm 의어떤프라퍼티가 validator-rules.xml 에설정된어떤규칙을만족해야하는지를설정한다. 예를들면 UserInfoForm 의 juminbunho 프라퍼티는 validator-rules.xml 의 juminbunho 규칙을지켜야한다. 는식의설정을하게된다. validator-rules.xml <validator> 요소를통해서규칙을선언할수있다. 여기서는새로운검증규칙을어떻게선언하는지는설명하지않는다. 기본적으로알아야할것은 <validator name= msg= /> 에서 name 과 msg 속성이다. name 속성 : 검증규칙의이름. validation.xml 에서이이름을통해액션폼의프라퍼티에적용할규칙을지정한다. msg 속성 : struts-config.xml 에서지정한리소스번들.properties 파일에서가져올오류메시지의키를지정한다. ActionForm.validate() 에서오류메시지를 ActionErrors 에담아서리턴하듯이 Validator 는리소스번들프라퍼티파일에있는메시지를 ActionMessages 에담아서리턴한다. 스트럿츠 Validation 의기본예제 validator-rules.xml 파일을보면이미사용가능한유용한검증규칙들이선언되어있다. 그것들만사용해도별문제없다. (required, minlength, maxlength,...) 또한기본검증규칙들을위한리소스번들메시지를자신의리소스번들프라퍼티파일에추가할필요가있는데, 기본적으로는영문메시지가 validator-rules.xml 에예제로들어있다. 그걸번역한게아래이다. # Struts Validator Error Messages errors.required={0( 이 ) 가필요합니다. errors.minlength={0( 은 ) 는최소한 {1 문자이상이어야합니다. errors.maxlength={0( 은 ) 는최대 {1 문자이하이어야합니다. errors.invalid={0( 이 ) 가유효하지않습니다. errors.byte={0( 은 ) 는 byte 형이어야합니다. errors.short={0( 은 ) 는 short 형이어야합니다. errors.integer={0( 은 ) 는 integer 형이어야합니다. errors.long={0( 은 ) 는 long 형이어야합니다. errors.float={0( 은 ) 는 float 형이어야합니다. errors.double={0( 은 ) 는 double 형이어야합니다. errors.date={0( 은 ) 는날짜형이아닙니다. errors.range={0( 은 ) 는 {1 에서 {2 사이값이어야합니다. errors.creditcard={0( 은 ) 는유효하지않은신용카드번호입니다. errors.email={0( 은 ) 는유효하지않은이메일주소입니다. 물론 native2ascii 를이용해서변환해서넣어야한다. 여기예제에서는스트럿츠 1.2.7 의 struts-blank.war 파일의 WEB-INF 디렉토리에포함된 validatorrule.xml 을 /WEB-INF/config/ 에복사한다. 그내용을한번쯤들여다보자. required, minlength,.. 등의규칙이선언되어있음을볼수있다. 36

validation.xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN" "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"> <form-validation> <global> <!-- 우편번호상수 123-456 형태 --> <constant> <constant-name>postalcode</constant-name> <constant-value>^\d{3-\d{3$</constant-value> </constant> </global> <formset> <!-- userinfoform 폼빈에대한유효성검증규칙 --> <form name="userinfoform"> <!-- userinfoform 의 name( 이름 ) 프라퍼티에대한검증규칙 --> <field property="name" depends="required"> <!-- arg 는에러메시지출력시 {0, {1,.. 등에채워줄메시지의키이다. --> <arg key="userinfoform.username" /> </field> <!-- userinfoform 의 address( 주소 ) 프라퍼티에대한검증규칙 --> <field property="address" depends="required"> <arg key="userinfoform.address" /> </field> <!-- userinfoform 의 postcode( 우편번호 ) 프라퍼티에대한검증규칙 --> <field property="postcode" depends="required,mask"> <!-- mask 규칙에서오류가발생했을때출력할메시지지정 --> <msg name="mask" key="userinfoform.postcodeerror"/> <arg key="userinfoform.postcode" /> <var> <var-name>mask</var-name> <var-value>${postalcode</var-value> <!-- 상수사용 --> </var> </field> <field property="email" depends="required,email"> <!-- email rule 에있는에러메시지가아닌아래에서 key 로지정한에러메시지를출력한다. --> <arg key="userinfoform.email" /> </field> </form> </formset> </form-validation> 위는이번예제에서사용할 validation.xml 파일이다. 이파일을 /WEB-INF/config 디렉토리에작성하여저장한다. <global><constant/></global> 에서 constant 는반복적으로사용되는문자열을상수로선언하는것이다. 여기서는 postalcode 라는이름으로상수를만들고정규표현식문자열을값으로지정하였다. 이것을 <form> 부분에서 ${postalcode 라는식으로사용하게된다. 실제로 ActionForm 의프라퍼티와검증규칙의연결은 <formset><form>..</form></formset> 에서시작된다. name 속성 : struts-config.xml 에서지정한 ActionForm 의이름이다. 37

<form><field>...</field></form> 부분에서액션폼의각필드와검증규칙을연결한다. property 속성 : 액션폼의프라퍼티이다. depends 속성 : validator-rules.xml 에선언된각검증규칙의이름이다. 여기서지정된검증규칙을통과해야만유효성검사를통과한것이다. 검증규칙이여러개필요할때는쉼표로구분한다. 순서대로검증이실행된다. <msg> 요소 : validator-rules.xml 에지정된에러메시지가아닌다른에러메시지를사용하기위해지정하는요소 name 속성 : 어떤검증규칙에서에러가발생했을때출력할메시지인지지정한다. depends 속성에있는값들중하나여야한다. key 속성 : 리소스번들프라퍼티의어떤키의값을에러메시지로사용할지를결정한다. <arg> 요소 : 오류발생시오류메시지의 {0, {1,.. 부분에삽입할메시지들을리소스번들프라퍼티에서가져오도록지정한다. key 속성 : 리소스번들프라퍼티의키 <msg> 와 <arg> 요소의 resource 속성 : resource 속성을 false 로두면리소스번들에서키에해당하는값을검색해서가져오는것이아니라 key 속성에지정된문자열을그대로사용한다. <var> 요소 : 유효성검증기에넘길파라미터이다. 일반적으로파라미터가필요한경우는많지않은데, mask 규칙의경우에는파라미터가필요하다. mask 규칙은액션폼의프라퍼티가파라미터로넘겨준정규표현식 (regular expression) 과일치하는지검사하는규칙이다. <var-name> 요소 : 파라미터이름 <var-value> 요소 : 파라미터값 여기 validation.xml 에서는많은리소스번들값들을생성했다. 아래는이것들을포함한최종 application.properties 파일의내용이다. error.invalidusername= 잘못된사용자명입니다. {0 error.invalidpassword= 비밀번호를입력하지않았습니다. error.invalidlogin= 로그인사용자명이존재하지않거나비밀번호가일치하지않습니다. # 메시지출력시상 / 하단에출력될머릿말과꼬릿말 message.header=<ul> message.footer=</ul> # Struts Validator Error Messages errors.required={0( 이 ) 가필요합니다. errors.minlength={0( 은 ) 는최소한 {1 문자이상이어야합니다. errors.maxlength={0( 은 ) 는최대 {1 문자이하이어야합니다. errors.invalid={0( 이 ) 가유효하지않습니다. errors.byte={0( 은 ) 는 byte 형이어야합니다. errors.short={0( 은 ) 는 short 형이어야합니다. errors.integer={0( 은 ) 는 integer 형이어야합니다. errors.long={0( 은 ) 는 long 형이어야합니다. errors.float={0( 은 ) 는 float 형이어야합니다. errors.double={0( 은 ) 는 double 형이어야합니다. errors.date={0( 은 ) 는날짜형이아닙니다. errors.range={0( 은 ) 는 {1 에서 {2 사이값이어야합니다. errors.creditcard={0( 은 ) 는유효하지않은신용카드번호입니다. errors.email={0( 은 ) 는유효하지않은이메일주소입니다. # messages for Validation userinfoform.username= 사용자명 userinfoform.address= 주소 userinfoform.postcode= 우편번호 userinfoform.postcodeerror={0 가형식에맞지않습니다. \ 우편번호는 123-456 과같은형식을따릅니다. userinfoform.email= 이메일주소 38

struts-config.xml 작성하기 Validator 를위한 struts-config.xml 의추가사항은다음과같다. 아래내용을그대로복사하면안된다. <form-bean> 은 <form-beans> 안에, <action> 은 <action-mapping> 안에, 그리고 <plug-in> 은 <action-mappings> 이나 <message-resources> 보다더뒤의다른 plug-in 과같은위치에두어야한다. <!-- Validator 테스트용액션폼 --> <form-bean name="userinfoform" type="strutsguide.forms.userinfoform"> </form-bean> <!-- Validator 용사용자정보입력 / 보여주기화면 --> <action path="/validator/inputuserinfo" forward="/validator/inputuserinfo.jsp"/> <!-- 입력된사용자정보를 Validator 로검증한뒤에 request 스코프에저장하고 userinfo.jsp 로포워딩하는액션 --> <action path="/validator/adduserinfo" type="strutsguide.actions.adduserinfoaction" name="userinfoform" scope="request" validate="true" input="/validator/inputuserinfo.jsp" > <forward name="success" path="/validator/viewuserinfo.jsp"/> </action> <plug-in classname="org.apache.struts.validator.validatorplugin"> <set-property property="pathnames" value="/web-inf/config/validator-rules.xml,/web-inf/config/validation.xml"/> </plug-in> 스트럿츠에게 Validator 를사용하겠다는의사표시를하려면 ValidatorPlugIn 을설정해야한다. 바로 <plugin> 부분이다. <set-property> 를이용해서 validator-rules.xml 과 validation.xml 파일의경로를알려줘야만한다. 먼저 /validator/inputuserinfo.do 로접속해서폼을채워넣으면 UserInfoForm 액션폼에값을채우고, 그값들을 Validator 가유효성검증을거친다음유효성검증을통과하면 /validator/adduserinfo.do 액션을수행하고서 /validator/viewuserinfo.jsp 에서그대로출력하는구조이다. 액션폼의 validate() 메소드를호출하는대신그것을 Validator 프레임워크가가로채는것빼고는다를것이없다. UserInfoForm 액션폼 Validator 를위해액션폼을만들때는 ActionForm 클래스를상속받지않고 ValidatorForm 클래스를상속받아야한다. 그리고당연히 validator() 메소드를만들필요가없다. Validator 프레임워크에의해자동으로수행될것이므로. package strutsguide.forms; import org.apache.struts.validator.validatorform; /** * Validator 테스트를위한사용자정보입력액션폼 */ 39

public class UserInfoForm extends ValidatorForm { /** 이름 */ private String name = null; /** 주소 */ private String address = null; /** 우편번호 */ private String postcode = null; /** 이메일 */ private String email = null; public String getaddress() { return address; public void setaddress(string address) { this.address = address; public String getemail() { return email; public void setemail(string email) { this.email = email; public String getname() { return name; public void setname(string name) { this.name = name; public String getpostcode() { return postcode; public void setpostcode(string postcode) { this.postcode = postcode; // validate() 메소드는구현하지않는다. 모두 required 규칙을만족해야하며우편번호의경우에는 123-456 처럼 숫자3개-숫자3개 형태의정규표현식 (^\d{3-\d{3$) 을 mask 검증규칙에적용해서유효성을검사한다. mask 검증규칙은지정된정규표현식과프라퍼티의값이일치하는지검사하는것이다. 이메일주소의경우에도 email 검증규칙을만족하는지검사한다. (validation.xml 참조 ) /validator/inputuserinfo.jsp 작성하기 이 JSP 페이지는단순히사용자로부터이름과주소, 우편번호그리고이메일주소를입력받는다. Validator 에의한검증이실패하면다시이페이지로돌아와서오류메시지와직전에입력했던내용들을폼에채워서보여준다. <%@ page contenttype="text/html; charset=euc-kr"%> <%@ taglib uri="/web-inf/tlds/struts-html.tld" prefix="html"%> <%@ taglib uri="/web-inf/tlds/struts-bean.tld" prefix="bean"%> 40

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html:html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title>validator 테스트 - 사용자정보입력폼 </title> <html:base/> </head> <body> <h3> 사용자정보를입력해주세요.</h3> <!-- 오류메시지출력부분 --> <html:messages id="msg" header="message.header" footer="message.footer"> <li><b><bean:write name="msg" /></b></li> </html:messages> <html:form action="/validator/adduserinfo" method="post" focus="name"> 이름 : <html:text size="20" property="name" /><br /> 주소 : <html:text size="80" property="address"/><br /> 우편번호 : <html:text size="7" property="postcode" maxlength="7" /><br /> 이메일 : <html:text size="20" property="email"/><br /> <html:submit value=" 입력 "/> <html:reset value=" 재입력 "/> </html:form> </body> </html:html> 오류메시지에보면 header 와 footer 속성이있는데, 이것은오류메시지들을출력하기전에상단과하단에추가할텍스트를의미하는메시지리소스번들키를뜻한다. 여기서는 <ul> 과 </ul> 로지정한것이다. AddUserInfoAction 액션작성하기 package strutsguide.actions; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import org.apache.struts.action.action; import org.apache.struts.action.actionform; import org.apache.struts.action.actionforward; import org.apache.struts.action.actionmapping; import strutsguide.forms.userinfoform; /** * 입력된사용자정보가 Validator 로검증이끝난뒤에이액션에오게된다. * 이액션에서는단순히각값을 request 에속성으로저장한다. */ public class AddUserInfoAction extends Action { public ActionForward execute(actionmapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserInfoForm uiform = (UserInfoForm)form; // ValidateActionForm 에서정보추출 String name = uiform.getname(); String address = uiform.getaddress(); String postcode = uiform.getpostcode(); String email = uiform.getemail(); 41

// 각정보를 request 스코프에저장한다. request.setattribute("name", name); request.setattribute("address", address); request.setattribute("postcode", postcode); request.setattribute("email", email); return (mapping.findforward("success")); 당연히사용자의입력값이 Validator 를통과했을때만액션을수행한다. 여기서 Action 은사실상아무기능도없이단순하게사용자의입력값을 request 스코프에 Attribute 로저장한다. 그리고는 viewuserinfo.jsp 로포워딩을한다. /validator/viewuserinfo.jsp 작성하기 <%@ taglib uri="/web-inf/tlds/struts-bean.tld" prefix="bean"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html:html> <head> <meta http-equiv="content-type" content="text/html; charset=euc-kr"> <title>validator 테스트 - 사용자정보출력 </title> <html:base/> </head> <body> <h3> 입력받은사용자정보 </h3> <ul> </ul> </body> </html:html> <li><b> 이름 </b> : <bean:write name="name"/></li> <li><b> 주소 </b> : <bean:write name="address" /></li> <li><b> 우편번호 </b> : <bean:write name="postcode"/></li> <li><b> 이메일 </b> : <bean:write name="email"/></li> request 에저장된 Attribute 들을 <bean:write> 를이용해서출력한다. 간단하다 ~! 화면을다시보면서흐름을따라가보자 첫화면 /validator/inputuserinfo.do 로접속해보자. 42

보다시피아무것도없다. 이름과주소만입력하고 입력 버튼을눌러보면다음처럼오류메시지가나오면서기존에입력했던값들이다시그대로보인다. 첫번째오류화면 여기서보면한글이안깨졌는데, 원래는깨져야한다 (?). 우리는 JSP 페이지나 Action 등어디에서도 request.setcharacterencoding( euc-kr ) 을해준적이없기때문이다. 하지만이예제에서한글이안깨진이유는몇챕터전에새롭게작성한 RequestProcessor 를상속받은 Controller 클래스때문이다. ( 실제로여기서는 TilesProcessor 를상속받았다. 바로앞의타일즈예제때문이다.) 우편번호와이메일은아예입력을안했을때와엉뚱한값을입력했을때의메시지가다른것을확인해보자. 우편번호와이메일잘못입력했을때 43

우편번호는원래 111-222 처럼입력해야하는데숫자로만입력했고이메일은 @ 대신에 # 을입력했다. 위처럼아까와는다른형식의메시지가나온다. 이유는처음에나온에러메시지는 required 검증규칙에서발생한에러메시지이고그다음것은각각 mask 와 email 검증규칙에서발생한에러메시지이기때문이다. 그리고특히우편번호의경우에는 validation.xml 에서 <msg> 태그를이용해서 mask 의기본에러메시지가아닌다른에러메시지를사용하도록했는데그것이출력되었음을볼수있다. 이제정상적으로입력했을때는? 모든값을정상적으로입력하면 adduserinfo.do 가 /validator/viewuserinfo.jsp 로포워딩되어각입력값을보여주게된다. 44

9. 예외처리하기 (Exception Handling) Action 을수행하는도중예외가발생했을때예외를사용자의웹브라우저에바로출력해버리지않고처리하는과정을거칠수있게해준다. 특정 Action 만을위한예외핸들러는 <action> 의자식노드로 <exception> 을추가하여설정하고, 전역적으로사용될예외핸들러는 <gloabal-exceptions> 에추가한다. <exception> <exception type="org.apache.struts.webapp.example.expiredpasswordexception" key="expired.password" path="/changepassword.jsp" handler="exceptionhandlerclass" /> type : 예외클래스를의미한다. key : 리소스번들프라퍼티에서가져올메시지의키문자열 hander : 예외를처리할예외핸들러클래스. ExceptionHandler 를상속받아야만하며, 예외핸들러를거친다음에 path 에지정된페이지로이동한다. path : 예외처리후사용자에게보여줄 JSP 페이지경로. path 지정시 path 에지정된페이지로포워딩하며, 그페이지에서는메시지리소스번들로부터 key 에해당하는메시지를찾아출력해준다. handler 없이 path 만지정해도실제로는 hander= org.apache.struts.action.exceptionhandler 를지정한것과동일하게, 디폴트예외핸들러로서 ExceptionHandler 를수행한뒤에 path 에지정된 JSP 페이지로이동한다. path 속성을생략했다면디폴트예외핸들러가 ActionMapping 의 input 프라퍼티에지정된페이지로포워딩시킨다. 기본 ExceptionHandler 는 JSP 로이동하기전에 org.apache.struts.globals.exception_key 에지정된문자열을키로이용해 request 에 Exception 객체를저장한다. 또한 key 로지정된메시지를 ActionMessage 객체로저장해서 JSP 에넘겨준다. JSP 페이지에서는그 Exception 객체와메시지를가져다사용할수있다. 예외핸들러의등록 다음처럼 struts-config.xml 에디폴트핸들러로 Exception 예외를처리하는예외처리설정을등록한다. Exception 은모든예외의부모클래스이기때문에 Action 에서발생한예외중에서다른예외핸들러에의해잡히지않는모든예외들이지금지정한예외처리설정에의해처리되게된다. 그리고는무작정예외만을발생시키는 /exception/throwexception.do 액션을등록한다. <global-exceptions> <!-- 예외핸들러가등록되지않은 Action 에서발생한모든예외에대한처리 --> <exception type="java.lang.exception" key="exception.all" path="/exception/exception.jsp" /> </global-exceptions> <!-- 무작정예외만발생시키는액션을등록한다. --> <action path="/exception/throwexception" type="strutsguide.actions.throwexceptionaction" /> 45

ThrowExceptionAction 아래는항상 Exception, NullPointerException, IOException 을무작위로발생시키기만하는스트럿츠액션이다. package strutsguide.actions; import java.io.ioexception; import org.apache.struts.action.action; import org.apache.struts.action.*; import javax.servlet.http.*; /** * 일부러예외를발생시켜기본 global exception 핸들러를사용해본다. */ public class ThrowExceptionAction extends Action { public ActionForward execute(actionmapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { long currenttime = System.currentTimeMillis(); int extype = (int)(currenttime % 3); if (extype == 0) { throw new Exception(" 일반 Exception 발생."); else if (extype == 1) { throw new NullPointerException("NullPointerException 발생."); else if (extype == 2) { throw new IOException("IOException 발생."); // 무조건예외를발생시키므로여기까지오지도못한다. return null; /exception/exception.jsp 액션에서발생한예외는예외핸들러를거쳐 <exception path="/exception/exception.jsp"/> 로지정된아래의 JSP 로전달되게된다. 아래에서는스크립틀릿을사용하여예외의메시지를출력했는데, 실제프로그램은이런식으로스크립틀릿은사용하는것은피해야하겠다. 이건그냥이렇게하는것도가능하다는예제일뿐이다. <%@ page contenttype="text/html; charset=euc-kr"%> <%@ page import="org.apache.struts.globals" %> <%@ taglib uri="/web-inf/tlds/struts-html.tld" prefix="html"%> <%@ taglib uri="/web-inf/tlds/struts-bean.tld" prefix="bean"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title> 예외처리페이지 </title> </head> <body> <h2> 예외핸들러가전달한에러메시지목록 </h2> <html:messages id="error" header="message.header" footer="message.footer"> <li><bean:write name="error"/></li> 46

</html:messages> <br /> <h2>exception 객체로부터가져온예외메시지 </h2> <blockquote style="background: wheat;"> <% Exception ex = (Exception)request.getAttribute(Globals.EXCEPTION_KEY); if (ex == null) { out.println("ex == null"); else { out.println(ex.getmessage()); %> </blockquote> </body> </html> <html:messages> 를이용해서 <exception key=""/> 의 key 로지정된메시지를출력해준다. 그리고스크립틀릿부분에서는 Globals.EXCEPTION_KEY 로 request 스코프에저장된예외객체의메시지를받아다출력해준다. application.properties 에추가 리소스번들 application.properties 에다음을추가한다. # for exception handler exception.all= 오류가발생했습니다. exception.sghandler={0 : 오류가발생했습니다. 오류메시지 : {1 수행화면 아래화면은 struts-config.xml 에서 <global-exceptions> 이하부분을없앤상태 ( 즉, 예외핸들러를지정하지않은상태 ) 에서의 /exception/throwexception.do 호출화면이다. 47

보는것과같이이럴경우에는웹컨테이너 ( 여기서는 Tomcat) 에의해예외메시지와예외스택트레이스가화면에출력되게된다. 실제웹어플리케이션에서는이런식으로일반사용자들이예외스택트레이스를보게되는일이있어서는안된다. 다음화면은 <globa-exceptions> 를지정한상태에서의화면이다. 예외핸들러지정시 ExceptionHandler 를상속받은예외핸들러클래스의 execute() 메소드를수행한다. execute() 메소드는예외에대한처리를수행한다음에 Action 과마찬가지로 ActionForward 를반환하면그것으로이동하게된다. public ActionForward execute( Exception ex, ExceptionConfig ae, ActionMapping mapping, ActionForm forminstance, HttpServletRequest request, HttpServletResponse response) throws ServletException { //... return 액션포워드 ; 사용자정의예외핸들러의등록 이번에는 <global-exceptions> 가아니라, <action> 의로칼예외핸들러로등록해보자. 이예외핸들러는해당 <action> 에서만작동하게된다. 그리고전역적으로지정된동일한예외를처리하는예외핸들러가있을경우에는로칼예외핸들러가우선적으로실행된다. 여기서는 IOException 과 NullPointerException 에대해서만예외핸들러를등록했다. 그렇기때문에이두가지를제외한다른모든예외는자동으로전역핸들러로전달되어처리되게된다. 기존에등록했던 /exception/throwexception.do 액션설정부분을아래와같이살짝바꿔주자. <!-- 일부러예외를발생시킨다. --> <!-- 주석처리 <action path="/exception/throwexception" type="strutsguide.actions.throwexceptionaction" /> --> <action path="/exception/throwexception" type="strutsguide.actions.throwexceptionaction" > 48

<!-- NullPointerException 과 IOException 을처리하는핸들러지정 --> <exception key="exception.sghandler" handler="strutsguide.exhandlers.sgexceptionhandler" path="/exception/exception.jsp" type="java.lang.nullpointerexception"/> </action> <exception key="exception.sghandler" handler="strutsguide.exhandlers.sgexceptionhandler" path="/exception/exception.jsp" type="java.io.ioexception"/> SGExceptionHandler 작성하기 ExceptionHandler 를상속받아만든 SGExceptionHandler 는다음과같다. package strutsguide.exhandlers; import java.util.date; import org.apache.struts.action.*; import org.apache.struts.config.exceptionconfig; import javax.servlet.servletexception; import javax.servlet.http.*; import org.apache.commons.logging.*; /** * 스트럿츠 ExceptionHandler 예제. * Action 에서 try/catch 로잡지못하고그대로 throw 된예외들이 * struts-config.xml 의 <exception> 설정에따라이곳으로 * 전달된다. */ public class SGExceptionHandler extends ExceptionHandler { Log log = LogFactory.getLog(SGExceptionHandler.class); /** * Action 에서예외가발생하면 <exception> 설정에따라아래를실행한다. */ public ActionForward execute(exception ex, ExceptionConfig config, ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException { // 발생한예외시간을포함해서새로운문자열로구성한다. String [] args = new String[2]; long currenttimemillis = System.currentTimeMillis(); Date currenttime = new Date(currentTimeMillis); // 현재시간을인자로저장. args[0] = currenttime.tostring(); // 오류메시지를인자로저장. args[1] = ex.getmessage(); // 지정된키의메시지를메시지리소스번들에서가져와 {0, {1 을지정된 49

// 인자로대체하여새로운메시지구성. ActionMessage errormessage = new ActionMessage(config.getKey(), args); // 예외정보를표준에러스트림으로출력한다. System.err.println(errorMessage.toString()); ex.printstacktrace(); // 예외정보를가지고포워딩할 JSP 페이지를찾는다. <exception path=""/> 에지정된페이지 ActionForward forward = new ActionForward(config.getPath()); // 예외사항들을 request 에저장한다. storeexception(request, config.getkey(), errormessage, forward, config.getscope()); // 포워딩 ~! return forward; 이 SGExceptionHandler 은예외를전달받게되면 <exception key="exception.sghandler"/> 메시지를가져다가현재시간과 ex.getmessage() 를실행하여예외의메시지를합쳐서 ActionMessage 객체를구성하게된다. 그리고 config.getpath() 를이용해서 <exception path="/exception/exception.jsp"/> 로지정된포워딩할 JSP 주소를얻어다 ActionForward 객체를구성한다. 최종적으로 storeexception() 메소드를실행하여에러메시지를지정된스코프 (config.getscope()) 에저장하고는포워드객체를리턴한다. 이제 IOException 이나 NullPointerException 이발생하면아래와같은화면을보게된다. 하지만, 그냥 Exception 이발생하면 <global-exceptions> 에서지정한예외핸들러로예외를처리하게되기때문에이전예제와같은화면을보게된다. 이번예제에서예외가발생했을때날짜정보등을추가하여 JSP 로보냈는데, 실제웹어플리케이션에서는이렇게하지않는것이좋겠다. Logger 를사용하여예외객체를넘기고오류메시지를출력하고, 사용자는단지친절하게 " 로그인을해주세요." 같은메시지만을받도록해야겠다. 로거의사용법은다음에나오는장에설명되어있다. 또한이화면에보면예외객체가 JSP 로전달되지않은것을볼수있는데 (ex == null), 실제웹어플리케이션에서도예외객체를 JSP 페이지로전달하는일이없어야겠다. 예외에관해프로그래머가알아야할모든정보는예외핸들러에서로거를이용해저장하고사용자는단지사용자에게필요한메시지만보게하자. 50