본문 바로가기

Java For All

struts2 Action 설정하기

web.xml
이 파일이 웹 애플리케이션의 배치스크립터(DD) 라는건 다 알고 있는 사실입니다.
Struts2가 요청을 받아들일 수 있도록 DD에 필터를 등록해야 합니다.
<filter>
    <filter-name>struts</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
    
<filter-mapping>
    <filter-name>struts</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

struts-default.xml
이 파일의 위치는 프레임워크 코어 라이브러리인 struts2-core-버전.jar 안에 포함되어 있습니다.
즉 사용자가 작성해야 하는 파일이 아니고 미리 정의되어 있는 파일입니다. 그렇다면 왜 이 파일을 여기서 언급 하는 걸까요? 그 이유는 사용자가 작성해야 할 struts.xml 파일의 설정을 상당히 줄일 수 있도록 모든 디폴트 값들이 설정되어 있기 때문입니다. 역시 제로 컨피규레이션 지향답습니다.
이 파일의 DTD는 바로 아래에서 소개될 사용자가 설정해야 하는 struts.xml 파일의 DTD와 동일합니다. 
따라서 struts.xml과 설정 방법이 동일 하므로 직접 열어 보면 큰 도움이 됩니다. 설정되어 있는 내용들 중
가장 중요한 것은 struts-default 라는 이름을 가진 패키지 입니다. 
이 파일에 대해선 여기까지만 설명하고 아래 내용들을 보면 이래서 중요하구나 라는걸 느낄수 있을 것 입니다.

struts.xml
이 파일은 웹 애플리케이션내의 처리 흐름을 설정 합니다.
<include />
Struts1에서는 하나의 struts-config.xml 파일만 지원했지만 Struts2에서는 여러개의 설정파일을 역할별로
나눠서 설정할 수 있습니다. 그리고 <include /> 요소를 이용해 하나의 설정파일에 포함 시킵니다.
파일의 위치는 ClassPath상의 경로입니다.
<struts>
    <include file"struts-default.xml" />
    <include file"jjaeko/struts-m1" />
    <include file"jjaeko/struts-m2" />
    
    ...
    
    <include file"jjaeko/struts-m3" />
    <include file"jjaeko/struts-m4" />
</struts>
이 처럼 하나이상의 파일을 포함할 수 있습니다. ... 은 다른 요소이며 순서대로 포함됩니다.
Java 클래스가 묵시적으로 Object 클래스를 상속하듯이 struts.xml 파일도 묵시적으로 struts-default.xml
파일을 포함합니다. 따라서 첫번째 <include file="struts-default.xml" /> 생략 가능 합니다.

<bean />
<bean /> 요소에 설정된 클래스는 프레임워크의 컨테이너에 의해 생성되어 내부 프레임워크 객체에 주입됩니다. 대부분의 애플리케이션에서 이 요소를 사용할 일은 없으므로 이런게 있다는 것만 알고 넘어가겠습니다.

<constant />
이 요소는 상수를 설정할 때 쓰입니다. 상수는 Struts2의 작동 방식을 결정짓는 값 입니다.
상수의 설정은 struts.xml 뿐만 아니라 struts.properties, web.xml 등.. 에서도 할 수 있습니다.
<constant /> 요소의 속성은 다음과 같습니다.
- name : 상수의 이름을 지정 합니다.
- value : 상수의 값을 지정 합니다.
설정 가능한 상수는 struts2-core-버전.jar 의 org.apache.struts2 패키지의 default.properties 파일에 나와 있는데 자세한 상수 설정에 대해서는 Struts2 상수설정 에서 다루겠습니다.

<package />
패키지는 result, interceptor, action 등을 논리적인 단위로 그룹화 하는데 사용 합니다.
struts.xml 파일 설정중에 대부분의 설정은 패키지 안에서 이루어 집니다.
먼저 속성에 대해 알아보겠습니다.
- name(필수) : 패키지를 구분하는 이름입니다. 패키지의 이름은 유일해야 하며 다른 패키지에서 상속할 때
이 이름을 사용 합니다.

- extends(옵션) : 다른 패키지를 상속할 때 해당 상속받을 패키지의 이름을 지정합니다. 상속받게 되면 부모 패키지의 모든 설정을 물려 받고 동일한 설정이 존재하면 오버라이드 합니다. 대부분 Struts2에서 제공하는 디폴트 값을 사용하기 위해 struts-default.xml 파일에 정의되어 있는 strtus-default 패키지를 상속하게 됩니다.

- namespace(옵션) : 네임스페이스는 액션 설정의 고유 접두어로 사용되는데 요청 URL의 중간경로로 생각하면 됩니다. URL 형식 -> http://localhost:8080/[컨텍스트]/[네임스페이스]/[액션]
네임스페이스는 사용자 정의 네임스페이스 외 디폴트 네임스페이스와 루트 네임스페이스 두가지가 있습니다.
디폴트 네임스페이스는 값이 "" 빈문자열이거나 명시를 안할경우 이며 고유 네임스페이스에서 해당 액션을 찾지 못한다면 디폴트 네임스페이스에서 액션을 찾습니다.
루트 네임스페이스는 값이 "/" 이며 루트 네임스페이스에서 액션을 찾지 못하면 디폴트 네임스페이스에서 액션을 찾습니다.

- abstract(옵션) : 패키지를 추상패키지로 만듭니다. 추상패키지는 액션이 없는 패키지 입니다. 만약
액션이 없고 abstract 속성이 true가 아닐경우 이패키지를 상속하면 에러가 납니다. 따라서 액션이 없는
패키지는 반드시 abstract 속성을 true로 설정해야 합니다. 참고로 사용자가 기본적으로 상속하는 struts-default 패키지도 액션이 없기 때문에 추상패키지로 선언되어 있습니다.

- externalReferenceResolver : 이건 뭔지 모르겠습니다.

속성뿐 아니라 6개의 하위 요소를 포함하는데 하위 요소로써 포함되는 순서는 다음과 같습니다.
- <result-types />
- <interceptors />
- <default-interceptor-ref />
- <default-action-ref />
- <global-results />
- <global-exception-mappings />
- <action />
이 6개의 요소들은 바로 아래에서 각각 순서대로 알아보도록 하겠습니다. 
각 요소마다 존재하는 하위요소에 대해서도 알아 보겠습니다.

<result-types />
<result-types/> 요소는 하위요소인 <result-type /> 요소를 이용하여 result-type 을 설정할 때 쓰입니다.
그런데 struts-default.xml 파일에 이미 모든 result-type이 정의 되어 있어서 <package /> 요소의 extends 속성으로 struts-default 를 상속받으면 딱히 직접적으로 사용할 일이 없습니다.
다음은 struts-default.xml 의 struts-default 패키지에 정의된 result-type중 일부분 입니다.
여러가지 형태로 클라이언트에게 응답할 수 있군요~ 이것 말고도 엄청 많습니다.
<result-types>
    <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
    <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
    <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
    <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>
    <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>
    <result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" />
    <result-type name="plaintext" class="org.apache.struts2.dispatcher.PlainTextResult" />
    ...... 이하 여러개의 <result-type> 정의
</result-types>
여기서 눈여겨 볼것은 name이 dispatcher이고 default속성이 true 되어 있는 요소입니다.
default로 정의된 result-type은 <result /> 요소 선언시 type을 명시하지 않을 경우 적용되는 값입니다.
<result /> 요소의 설정방법은 아래의 <action />요소에서 설명하겠습니다.

<interceptors />
Interceptor는 AOP와 같은 개념으로 액션 실행 전후로 실행되는 모듈입니다.
파라미터값을 자동으로 저장, 로깅, 파일 업로드, 유효성 검사, 보안 등 다양한 Interceptor가 존재 합니다.
이 모든 Intercpetor는 struts-default.xml 파일에 모두 정의가 되어 있습니다. 
따라서 <interceptors /> 요소는 <interceptor-stack /> 요소를 이용해 사용자 정의 스택을 구성할 경우를 
제외 하고는 사용할 일이 없으며 단지<interceptor-ref /> 요소를 이용해 가져다 쓰기만 하면 됩니다.
다음은 struts-dafault.xml에 정의된 <interceptors /> 요소중 일부분 입니다.
<interceptors>
    <interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
    <interceptor name="logger" class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/>
    <interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>
    <interceptor name="validation" class="org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor"/>
    ...... 이하 여러개의 <intercpetor /> 정의

    <interceptor-stack name="basicStack">
        <interceptor-ref name="exception"/>
        <interceptor-ref name="servletConfig"/>
        ...... 이하 여러개의 <interceptor-ref /> 정의 
    </interceptor-stack>

    <interceptor-stack name="defaultStack">
        <interceptor-ref name="exception"/>
        <interceptor-ref name="alias"/>
        ...... 이하 여러개의 <interceptor-ref /> 정의
    </interceptor-stack>
    
    ...... 이하 여러개의 <interceptor-stack /> 정의 
</interceptors>
<interceptor-stack /> 요소는 <interceptor-ref /> 요소를 이용하여 이미 정의된 인터셉터들을 여러개씩 묶어서 하나의 스택으로 만들 때 쓰입니다. 

<default-interceptor-ref />
다음은 struts-default.xml 의 struts-default 패키지에 정의된 <default-interceptor-ref /> 입니다.
<default-interceptor-ref name="defaultStack"/>
<default-interceptor-ref /> 요소가 하는 역할은 바로 위에서 알아본 Interceptor 를 참조하여
패키지내 Interceptor가 설정되지 않은 액션에 대해서 기본적으로 실행될 Interceptor를 설정 합니다.
예를 들어 기본적으로 사용자의 패키지는 struts-default 패키지를 상속받고 액션에서 아무 Interceptor도 설정하지 않았다면 struts-default 패키지에 정의된 <default-interceptor-ref name="defaultStack"/> 요소에 따라 defaultStack으로 묶인 Interceptor들이 설정 됩니다.

<default-action-ref />
사용자가 요청한 액션이 존재하지 않을 경우 실행될 액션을 설정합니다. 500에러를 방지할 수 있겠네요. 
이 요소는 네임스페이스당 하나만 존재할 수 있습니다.
<package name="sample" extends="struts-default">
    <default-action-ref name="defaultAction">
    
    <action name="defaultAction" class="......" />
</package>

<global-results />
<result /> 요소는 <action /> 요소의 하위에 설정 합니다. 하지만 수많은 <action /> 요소에서 똑같은 <result /> 요소를 설정하고 있을 땐 중복 설정으로 이어질 것 입니다. 예를들어 로그인 페이지나 에러페이지 같은 포워딩을 담당하는 <result /> 설정이 그러한 경우 입니다.  
이럴 때 사용하는 것이 바로 <global-results /> 요소 입니다. 간단한 예를 보겠습니다.
<package name="sample" extends="struts-default">
    <global-results>
        <result name="login">/WEB-INF/jsp/login.jsp</result>
        <result name="error">/WEB-INF/jsp/error.jsp</result>
    </global-results>
        
    <action name="sample" class="jjaeko.SampleAction">
        <result name="SUCCESS">/WEB-INF/jsp/sample.jsp</result>
    </action>
</package>
sample 액션에서는 경우에 따라 login과 error을 리턴할 수 있습니다. 이 때 로컬 액션에서 해당 result를 
찾지 못하면 패키지내 정의된 <global-results />에서 찾게 됩니다.

<global-exception-mappings />

이 요소는 하위 요소인 <exception-mapping /> 를 이용해 글로벌한 예외 매핑 설정을 합니다.
예외 매핑이란 액션에서 예외가 발생하면 특정 result를 수행하도록 하는 것입니다.
액션에서 특정 예외가 발생 했을 때 해당 액션의 <exception-mapping /> 요소를 먼저 찾고 만약 해당 액션의 예외 매핑이 없다면 <global-exception-mapping /> 에서 찾습니다. 
<global-results /> 요소는 <result /> 요소의 글로벌한 설정이듯이 <global-exception-mapping /> 요소는
<exception-mapping /> 요소의 글로벌한 설정입니다.
<package name="sample" extends="struts-default">
    <global-results>
        <result name="error">/WEB-INF/jsp/error.jsp</result>
    </global-results>
        
    <global-exception-mappings>
        <exception-mapping exception="SQLException" result="error"/>
    </global-exception-mappings>
        
    <action name="some" class="jjaeko.SomeAction">
        <result name="error">/WEB-INF/jsp/error.jsp</result>
    </action>
</package>
SomeAction 에서는 SQLException 예외를 던지고 some 액션 내에서 예외 매핑을 찾습니다. 그런데 some 액션에는 예외 매핑이 설정되지 않았으므로 <global-exception-mappings> 요소에서 찾게 됩니다.
일치하는 예외가 있고 result는 error 군요. 여기서 result를 찾을 때 some 액션과 global-results에 모두 error가 존재하는 상황에서 우선순위는 some 액션 입니다.

<action />
패키지에서 가장 핵심이 되는 <action /> 요소는 다음과 같은 설정을 할 수 있습니다.
- URL과 액션 클래스를 매핑
- 액션 수행 전 후로 Interceptor 설정
- result 지정
- 예외 지정

먼저 <action /> 요소의 속성부터 알아보겠습니다.
- name(필수) : 액션 이름을 지정합니다. URL 요청의 마지막 .action 을 제외한 부분이 되겠습니다. 예를들면 http://호스트/애플리케이션이름/[네임스페이스]/액션명.action 에서 액션명을 지명하면 됩니다.

- class(필수) : URL 요청에 따라서 실행될 액션클래스를 지정합니다.

- method(옵션) : 옵션 사항으로 값을 지정하지 않았다면 기본적으로 execute() 메서드를 찾습니다. 만약 하나의 액션 클래스에서 여러개의 액션 메서드를 사용하고자 할 때 각 요청별로 method 속성을 이용해 매핑할 수 있습니다. 

- converter(옵션) : 이건 뭔지 모르겠습니다.

이제 <action /> 요소의 하위 요소에 대해 알아보겠습니다.
<param />
이 요소는 <action /> 요소 뿐만 아니라 <exception-mapping />, <interceptor-ref />, <result /> 요소(default 접두어가 붙은 요소 포함) 의 하위요소로도 사용됩니다.
용도는 각 요소에서 사용되는 클래스의 프로퍼티에 값을 설정하는 것 입니다. 간단한 예를 보겠습니다.
<package name="sample" extends="struts-default">
    <action name="hello" class="jjaeko.HelloAction">
        <param name="message">안녕?</param>
        <result name="k"><param name="location">/WEB-INF/jsp/hello.jsp</param></result>
    </action>
</package>
HelloAction 클래스의 message 프로퍼티에 "안녕?" 이라는 문자열을 설정 합니다.
사용자 요청에 전달된 파라미터와 설정되어 있는 <param /> 요소가 중복될 경우 사용자 요청에 전달된 파라미터가 우선시 됩다. 그런데 <result /> 요소는 뭔가 이상합니다. location 은 무엇일까요? 처음부터 잘 생각해 봅시다.  struts-default에는 dispatcher가 result-type의 디폴트로 설정되어 있기 때문에 <result /> 에서 type을 명시하지 않으면 dispatcher로 설정됩니다. 그리고 location 프로퍼티는 dispatcher 에 존재하는 프로퍼티 입니다. 그동안 <result /> 요소의 하위요소로 <param /> 요소를 사용한적이 없었던 이유는 dispatcher 의 디폴트 프로퍼티가 location 이기 때문 입니다. 따라서 result-type의 디폴트 프로퍼티는 <param />요소를 생략 할 수 있습니다.

<exception-mapping />
이 요소에 대한 설명은 <global-exception-mappings> 에서 했으므로 설명은 생략하고 액션내의 예외 매핑 예제를 바로 살펴보겠습니다.
<package name="sample" extends="struts-default">
    <global-results>
        <result name="error">/WEB-INF/jsp/error.jsp</result>
    </global-results>

    <action name="some" class="jjaeko.SomeAction">
        <exception-mapping exception="SQLException" result="error"/>
        <result name="error">/WEB-INF/jsp/error.jsp</result>
    </action>
</package>
SomeAction 에서 SQLException이 발생하면 some 액션내에 설정되어 있는 예외 매핑을 찾게 되겠습니다.
주의할 것은 예외가 발생되는 액션에서 exception Interceptor 가 설정되어 있어야 합니다.

<interceptor-ref />
액션의 실행 전 후로 실행될 Interceptor 를 지정합니다. 이 요소를 생략할 경우 디폴트로 defaultStack이 설정 됩니다. 각 Interceptor 마다 요구하는 프로퍼티 값이 존재할 수 있는데 위에서 소개한 <param /> 요소를
이용해 프로퍼티명과 값을 설정할 수 있습니다.

<result />
이 요소는 액션 메서드에서 리턴한 문자열의 이름으로 어떠한 형태로 응답을 작성할지 결정 합니다.
- name(옵션) : 액션 메서드에서 리턴한 문자열의 이름을 지정합니다. 디폴트는 success 입니다.
- type(옵션) : struts-default 패키지에 선언된 result-type 을 지정합니다. 디폴트는 dispatcher 입니다.

와일드카드 매핑
<action /> 요소의 method 속성을 이용할 경우 하나의 액션 클래스에 여러개의 메서드를 정의할 수 있습니다.
하지만 동일한 액션 클래스를 사용하는 <action /> 요소를 메서드 수 만큼 선언해야 하는 단점이 있습니다.
하지만 와일드 카드 매핑을 이용하면 단번에 해결 됩니다.
<action name="*User*" class="jjaeko.UserAction" method="{1}User">
    <result>/{2}/{1}User.jsp</result>
</action>
와일드카드(*) 는 여러개를 지정할 수 있으며 method 에서 순서에 따라 {순서} 형태로 설정합니다.
<result /> 요소에서도 역시 {순서} 형태로 설정 할 수 있습니다.
Insert_User_Admin.action 이라는 요청이 오면 UserAction 에서 InsertUser 라는 메서드가 실행 됩니다.
그후 "success" 를 리턴하면 Admin/InsertUser.jsp 페이지로 포워드 됩니다.

디폴트 와일드카드 매핑
액션 클래스가 없는 뷰를 응답해야 할 때 사용 합니다. 단 패키지의 가장 마지막에 존재해야 합니다.
<action name="*">
    <result>/WEB-INF/jsp/{1}.jsp</result>
</action>