Controller

Controller 는 MVC의 개요에서 언급했듯이 Model과 View 사이의 중개자 (mediator/translator) 역할을 한다. Model과 View를 분리함으로써 Single domain model 기반의 다양한 프리젠테이션을 만들 수 있게 되고, 모든 클라이언트의 request가 Controller를 통하게 되므로 보안, 로깅 등 여러 공통 기능들을 제고할 수 있다. Anyframe Web 에서 제공하는 기능들이 대부분 여기에 해당한다.

Controller Structure
J2EE 의 front controller 패턴을 따라 하나의 컨트롤러를 사용한다. 이 패턴은 보안, 국제화, 로깅과 같은 서비스를 컨트롤러 에서 집중해서 처리할 수 있는 이점이 있다.
  • 클라이언트 요청을 가로채는 역할
  • 특정 업무 처리에 각 요청을 매핑하는 역할
  • 비즈니스 로직으로부터 처리된 결과를 모아 클라이언트가 사용 가능하게 만드는 역할
  • 현재 상태와 업무 처리 결과를 바탕으로 뷰를 결정하여 클라이언트에 보여주는 역할

위 그림은 Struts 의 controller 에 해당하는 클래스들의 간단한 클래스 다이어그램이다.
Controller의 구성요소

ActionServlet

ActionServlet의 역할
  • 사용자의 request를 처리하는 일련의 절차를 대표함 (Front Controller)
  • 리소스의 초기화와 clean-up 을 담당

초기화 프로세스
  • web.xml로부터 서블릿 이름과 서블릿 매핑 정보 저장
  • 프레임워크가 사용할 DTD 등록 (설정 파일 검증에 활용됨)
  • struts-config.xml로부터 설정 정보를 읽고 ApplicationConfig클래스의 객체 생성
  • 메시지 리소스 로드, 초기화
  • 플러그인 초기화
  • 생성된 객체를 ServletContext에 저장

실행 시(ActionServlet 인스턴스가 HttpRequest를 받을때)
  • Request의 prefix에 기반하여 해당되는 서브 어플리케이션을 찾음
  • RequestProcessor를 찾아 process()를 호출
ShutDown 시
  • RequestProcessor의 destroy()호출
  • <plug-in>의 destroy()호출 (있다면)
  • <data-sources>의 close()호출 (있다면)

RequestProcessor

RequestProcessor는 요청처리를 개발자가 수정할 수 있도록 하기 위해 Struts1.1에서부터 추가되었다. 어플리케이션 모듈 각각의 요청 처리를 갖도록 하기 위해 ActionServlet과 RequestProcessor 클래스를 분리하여 요청처리를 담당하게 하며, 기본적인 기능에 더하여 override 를 통해 수정 가능한 다양한 메소드를 포함한다. (ex. Anyframe Web에서는 RequestProcessor를 기반하여 DefaultRequestProcessor 로 확장)
RequestProcessor의 역할
  • request 로부터 데이터를 받아서 ActionForm을 생성
  • Action의 execute() 실행
  • ActionForm 전달
  • Configuration에 정의된 대로 forward나 redirect 수행
  • 어플리케이션의 configuration정보 유지

process() 메소드의 역할
  • ActionForm 을 찾아 populate()를 수행
  • Validation을 수행
  • Action 에 대한 권한이 있는지 체크하는 processRoles()를 실행
  • Action을 찾아 execute()를 호출하고 forward 함
다음은 RequestProcessor의 메소드들에 대한 설명이다.
method Description
processPreprocess() Servlet 2.3에서 제공하는 Servlet Filter와 같은 역할을 한다. Request가 처리되기 전에 처리할 작업이 있다면 processPreprocess()에서 처리하면 된다.
processMapping() 요청한 request에서 사용할 ActionMapping객체를 생성하는 과정이다. 만약 Path정보에 해당하는 Mapping정보가 없으면 에러가 포함된 response가 반환된다.
processRoles() 사용자가 요청한 request를 처리할 수 있는 role이 있는지를 판별하는 과정이다. role이 존재한다면 과정이 계속 진행되겠지만 그렇지 않을 경우 에러를 발생시키며 처리과정이 중단된다.
processActionForm() ActionMapping에 ActionForm이 설정되어 있다면 ActionForm 인스턴스를 생성한다. 생성된 인스턴스는 설정파일에서 설정한 Scope에 맞도록 저장하는 과정이다. 만약 새로운 ActionForm 인스턴스를 생성할 필요가 있다면 reset() method가 호출된다.
processPopulate() request에서 전달되는 인자를 앞에서 생성된 ActionForm 인스턴스에 저장한다.
processValidate() 설정파일에서 validate attribute가 true로 설정되어 있다면 ActionForm의 validate method를 호출하는 역할을 한다. request에 의하여 전달된 인자들의 유효성을 체크하는 과정이다.
processForward() struts 설정파일의 action태그에서 forward나 include attribute가 설정되어 있다면 RequestDispatcher의 forward(), include() 메소드를 호출한다. 설정파일에 이 forward, include가 설정되어 있다면 request의 처리는 여기서 끝나고 해당 URL로 전달되게 된다.
processActionCreate() 메소드 이름에서도 알 수 있듯이 request에 해당하는 Action 인스턴스를 생성한다. Action 인스턴스가 이미 존재하는지를 체크하여 존재한다면 재사용하게 된다.
processActionPerform() 앞에서 생성한 Action 인스턴스의 execute()메소드를 호출하여 Action인스턴스를 실행시킨다.
processActionForward() Action 인스턴스의 execute()를 실행한 결과 반환된 ActionForward에 해당하는 URL로 forward시키는 과정이다.

RequestProcessor 를 struts-config.xml의 controller element로 설정한 예
<controller contentType="text/html;charset=UTF-8"
                    debug="3"	
                    locale="true"
                    nocache="true"
              processorClass="com.sds.banking.controller.SecureRequestProcessor" />
위와 같이 필요에 따라 RequestProcessor를 확장하여 Controller를 설정하게 된다.

Action

Action 클래스는 비지니스 로직을 호출하고 성공과 실패에 대한 적절한 forward 정보를 설정한다.
Action의 역할
  • Action의 execute()는 클라이언트의 요청과 Business Logic을 연결
  • Action은 MVC 구조에서 Controller와 Model사이를 잇는 역할

Action의 구현
  • 하나의 인스턴스가 그 action으로 매핑된 모든 request를 처리하므로 execute() 메소드를 thread에 안전하게 구현해야 함
  • Action 클래스에서 실행하는 Business Logic은 한 업무 처리에만 관련이 있어야 함
  • execute()는 항상 ActionForward를 리턴해야함
  • 비즈니스 로직을 호출하는 로직만 존재해야함, 비즈니스 로직은 모델 객체에 구현해야함
Action 인스턴스는 단일 인스턴스로 모든 클라이언트 요청이 같은 인스턴스를 공유하므로 특정 클라이언트의 정보를 Action 클래스의 멤버변수로 저장하는 것은 잘못된 구현이다. 클라이언트의 정보가 필요한 경우 request나 session에 저장토록 하고 특정 클라이언트의 상태를 나타내기 위해서는 execute() 내에서 지역변수를 사용하면 각 스레드별로 영향을 끼치지 않는다.
Action 클래스의 작성은 업무 개발자가 반드시 숙지해야할 부분으로, request로 전달된 사용자 입력 데이터를 추출하여 비지니스 로직을 호출하고 결과로써 돌려진 데이터를 세팅하여 포워드하는 중요한 작업이다.
Action 클래스의 작성 예
public class ConsolelogonAction extends DefaultAction
{
	/** 
	 * Method execute : 로그인을 시도한다. 
	 * 로그인이 성공하면 session 유저정보를 저장한다.
	 */
	public ActionForward execute(
                 ActionMapping mapping,
                 ActionForm form,
                 HttpServletRequest request,
                 HttpServletResponse response)
                 throws Exception
	{

		ServiceContext ctx = ServiceContextFactory.getServiceContext();
		Subject subject = new Subject();

		// ActionErrors errors = new ActionErrors();

		String userid = (String) PropertyUtils.getSimpleProperty(form, "userid");
		String password = (String) PropertyUtils.getSimpleProperty(form, "password");

		ApplicationContext ctx = getWebApplicationContext();
		AuthenticationService authenticationService 
		                     = (AuthenticationService) ctx.getBean("securityService");

		Credential c = new Credential();
		c.setProperty("userid", userId);
		c.setProperty("password", password);

		subject = authenticationService.authenticate(c);
		HttpSession session = request.getSession();

		// 사용자의 정보를 세션에 저장한다.
		session.setAttribute("subject", subject);

		return (mapping.findForward("success"));

	}
}
execute()메소드가 호출되면 userid 와 password 값을 받아오고 authenticate() 비지니스 메소드를 호출하고 결과로써 subject 객체를 돌려받아 session 에 설정 후 success 로 지정된 ActionForward를 리턴하고 있다 . 위에서 ActionError 를 사용하지 않고 Exception 이 발생하면 그냥 throw 한다. 이유는 Struts-config.xml에 Exception 처리에 대한 설정이 있기 때문이다.
위 Action과 관련된 action mapping 예
<action
	attribute="consolelogonForm"
	input="/index.jsp"
	name="consolelogonForm"
	path="/logon"
	type="anyframe.web.struts.common.console.ConsolelogonAction">
	<exception key="error.password.mismatch" path="/index.jsp" 
	    type="javax.security.auth.login.AccountExpiredException" />
	<exception key="error.password.mismatch" path="/index.jsp" 
	    type="javax.security.auth.login.FailedLoginException" />
	<forward name="success" path="/getConfigs.do" redirect="true" />
</action>

Anyframe Web에서는 Spring 과의 손쉬운 통합을 위해 Spring의 ActionSupport (내부적으로 Struts의 Action을 extends 하고 있음)를 extends 한 DefaultAction 을 정의하고 있다. 위의 예와 같이 Spring Bean 형태의 비지니스 서비스를 invoke 하기 위해 필요한 사전 준비를 하는 부분을 포함하고 있으므로 모든 Action 클래스는 DefaultAction을 extends 하여 구현하도록 한다. 사용은 다음과 같이 하면 된다.
public class UserAction extends DefaultAction

ActionForward

ActionForward의 역할
  • JSP 페이지나 서블릿 같은 웹 리소스의 논리적인 추상화
  • 물리적인 리소스에 대한 정보는 설정 파일에 저장

Action 클래스에서 execute() 메소드는 ActionForward 객체를 반환한다. ActionForward는 리소스를 감싸서 어플리케이션과 물리적인 리소스를 분리하며 설정 파일에 forward의 name, path, redirect 속성과 같은 요소들로 정의하고 코드에는 포함하지 않는다. RequestDispatcher는 redirect 요소의 값에 따라 ActionForward의 포워드나 리다이렉트를 실행하게 된다.
Action에서 ActionForward를 반환할 때 설정 파일에 미리 정의된 ActionForward를 알아내기 위해 일반적으로 액션 매핑을 사용한다. 다음 코드는 액션 매핑을 사용하여 논리적 이름에 근거해 ActionForward를 찾는 방법을 보여준다.
return mapping.findForward("success");
ActionMapping의 findForward() 메소드는 논리적인 이름을 찾을 수 없으면 null 을 반환하고 빈 페이지가 화면에 보이게 된다. findForward 의 인자값이 global-forwards나 action 요소에 다 정의되어 있으면 action 요소에 정의된 것이 우선 순위가 있다.

actions Package

Struts 에는 어플리케이션에서 쉽게 통합 할 수 있는 out-of-the-box 형태의 Action 클래스 5개를 포함하고 있으며 이를 이용해 개발 시간을 단축할 수 있다.
org.apache.struts.actions 패키지에 미리 정의되어 있는 Action
  • org.apache.struts.actions.ForwardAction
  • org.apache.struts.actions.IncludeAction
  • org.apache.struts.actions.DispatchAction
  • org.apache.struts.actions.LookupDispatchAction
  • org.apache.struts.actions.SwitchAction

위 그림은 Struts 의 org.apache.struts.actions 패키지에 해당하는 클래스들의 간단한 클래스 다이어그램이다.
org.apache.struts.actions.ForwardAction
단순히 포워딩만 실행하는 Action 클래스를 직접 구현하지 않고 struts에서 미리 구현된 ForwardAction을 사용한다. 이 Action 클래스는 파라미터 속성에 정의된 URI로 포워드를 실행한다.
<action path="/empLoginView"
          type="org.apache.struts.actions.ForwardAction"
          parameter="/system/login.jsp">                            
 </action>
browser 요청이 http://localhost:8080/empLoginView.do이면 /system/login.jsp로 포워드하게 된다.
Action 클래스를 통하지 않고 JSP를 직접 호출하는 것은 바람직하지 못하다. MVC 구조에서 Controller 의 책임을 위반할 뿐만 아니라 Struts 에서 처리하는 중간 단계를 건너 뛰게 되므로 문제가 발생할 수 있다.(ex. 리소스 번들에서 올바른 메시지를 가져오지 못함)
org.apache.struts.actions.IncludeAction
ForwardAction 과 비슷하며 기존에 있던 서블릿 기반 컴포넌트들을 스트럿츠 기반 웹 어플리케이션과 용이하게 통합하기 위해 만들어낸 것이다.
<action
    input="/subscription.jsp"
    name="subscriptionForm"
    path="/saveSubscription"
    parameter="/path/to/processing/servlet"
    scope="request"
    type="org.apache.struts.actions.IncludeAction" />
</action>
org.apache.struts.actions.DispatchAction
각 기능마다 별개의 Action 클래스를 만들게 되면 클래스 수가 많아져 관리가 쉽지 않다. Struts 에는 관련된 기능을 하나의 Action으로 모을 수 있는 DispatchAction을 제공한다. 예를 들면 add, update, view 와 같은 여러 기능을 하나의 Action(extends DispatchAction) 안에 method 로 구현하면 (cf. 일반 Action에서는 execute() 메소드 구현 ) 설정 파일의 parameter 속성값으로 지정한 문자열 키값(아래에서는 "method"로 지정하였음. 다른 문자열을 써도 됨.)으로 메소드를 호출할 수 있게 된다.
public class ShoppingCartAction extends DispatchAction
{

  public ActionForward add (
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) throws Exception{

	// TODO : add 기능 관련 로직

    return mapping.findForward("add");
  }

  public ActionForward update (
		  ...

	// TODO : update 기능 관련 로직

	return mapping.findForward("update");
  }

  public ActionForward search (
	  ...

	// TODO : search 기능 관련 로직

    return mapping.findForward("search");
  }

  public ActionForward view (
	  ...

	// TODO : view 기능 관련 로직

    return mapping.findForward("view");
 }

}
<action
    path="/cart"
    input="/order/shoppingcart.jsp"
    parameter="method"
    scope="request"
    type="com.test.action.ShoppingCartAction"
    validate="false" />
	<forward name="add" path="/order/shoppingcartAdd.jsp" />
	<forward name="update" path="/order/shoppingcartUpdate.jsp" />
	<forward name="search" path="/order/shoppingcartSearch.jsp"/>
	<forward name="view" path="/order/shoppingcartView.jsp" />
</action>
http://localhost:8080/cart.do?method=add&id=2
org.apache.struts.actions.LookupDispatchAction
DipatchAction과 비슷하나 메소드를 찾을 때 parameters 속성에서 찾는 것이 아니라 리소스 번들에 키 값을 이용해 찾게 된다. LookupDispatchAction을 사용하게 되면 하나의 Html Form에 submit 버튼이 여러 개 있는 경우 더 쉽게 처리할 수 있다.
org.apache.struts.actions.SwitchAction
이 클래스는 하나의 어플리케이션 모듈에서 다른 모듈로 스위칭을 지원하고 어플리케이션의 리소스를 컨트롤한다. prefix, page 파라메터를 통하여 어플리케이션 모듈을 전환할 수 있다. 사용 예는 다음과 같다.
<action path="/toModule"
          type="org.apache.struts.actions.SwitchAction"/>
/toModule.do?prefix=/moduleB&page=/index.do

디폴트 모듈로 전환하고자 하는 경우 prefix를 null String으로 설정하여 호출할 수 있다.
/toModule.do?prefix=&page=/index.do