Struts2의 대부분의 API는 Xwork2 입니다.
@Inject 애노테이션 또한 Xwork2의 API 입니다.
@Inejct 애노테이션의 역할은 Spring2.5의 @Autowired와 같으며 주입되는 빈은 Xwork2 컨테너에서 관리되는 빈 입니다.

여기까지가 필요한 사전 지식이며 본론으로 들어가서
Spring과 연동하게 되면 SpringObjectFactory를 이용해서 액션 클래스를 생성하게 됩니다.
이 때 만약 Struts2 설정파일의 액션의 class 속성을 Spring에서 관리되는 빈 이름으로 지정하지 않고
QName으로 지정하게 되면 문제가 발생 합니다.
다음은 SpringObjectFactory의 빈생성 메서드 코드 입니다.

public Object buildBean(String beanName, Map extraContext, boolean injectInternal) throws Exception {
   Object o = null;
   try {
     o = appContext.getBean(beanName);
   } catch (NoSuchBeanDefinitionException e) {
     Class beanClazz = getClassInstance(beanName);
     o = buildBean(beanClazz, extraContext);
   }
   if (injectInternal) {
     injectInternalBeans(o);
   }
   return o;
}

일단 beanName 인자는 Struts2 설정파일의 액션의 class 속성 값 입니다.
class 속성값으로 Spring에서 먼저 꺼내보고 예외가 발생하면 Spring에게 생성을 위임합니다.

문제가 되는 부분은 Spring에게 생성을 위임할 때 입니다.
o = buildBean(beanClazz, extraContext);
위 메서드가 수행될 때 injectInternalBeans(o); 이 메서드를 호출하게 되는데
바로 아래보면 또 한번 injectInternalBeans(o); 메서드를 호출 합니다.

그래서 액션클래스에서 @Injection 사용시 빈을 두번 Injection 받게 됩니다.
Posted by 째코

댓글을 달아 주세요


Struts2는 WebWork와 통합되어 있다는 사실은 누구나 알고 있습니다.
WebWork는 XWork를 기반으로한 Web MVC 프레임워크 라는 사실 또한 누구나 알고 있을 것 입니다.
여기서 XWork는 IoC 컨테이너 입니다. 스프링2.5에서 @Autowire 애노테이션으로 의존성을 주입하듯이
XWork에서는 @Inject 라는 애노테이션으로 의존성을 주입합니다.

Struts2는 WebWork와 적절히 통합되어 있습니다.
Struts2의 API 대부분의 setter 메서드에서는 @Inject 애노테이션이 사용 되어지고 있습니다.
@Inject 애노테이션의 속성은 value 와 required 두 가지 입니다.
value는 XWork Container에 존재하는 인스턴스의 이름을 지정하는 속성이며 기본 값은 "default" 라는 문자열 입니다. required 속성은 필수여부인데 true일 경우 Contanier내에 반드시 존재해야 합니다. 만약 존재하지 않으면
예외를 뿜어냅니다.

여기까지가 기본적으로 알고 있어야 하는 간단한 지식이고 이제 본론으로 들어가서...
struts-default.xml 에 보면 Struts2 에서 사용되는 객체들을 <bean /> 요소로 아주 많이 선언해 두었습니다.
모든 핵심 클래스들이 선언되어 있죠... 그리고 위에서 밝혔듯이 Struts2 API는 @Inejct 애노테이션이 마구마구
사용되어 지고 있는 상태 입니다. Struts2가 구동시 <bean /> 요소로 선언된 클래스들을 생성하고 필요한 곳에
주입 됩니다. 아래 몇 가지 예가 있습니다.
<bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="struts" class="org.apache.struts2.dispatcher.mapper.DefaultActionMapper" />
    <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="composite" class="org.apache.struts2.dispatcher.mapper.CompositeActionMapper" />
    <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="restful" class="org.apache.struts2.dispatcher.mapper.RestfulActionMapper" />
    <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="restful2" class="org.apache.struts2.dispatcher.mapper.Restful2ActionMapper" />
위 설정에서는 기본적으로 DefaultActionMapper가 주입 됩니다.
그 밖에도 다른 <bean /> 요소로 선언된 것 중에서 역시 name 속성이 "struts" 인 것만 사용 됩니다.

그래서 저는 추측 했습니다.
개발자가 별도로 <bean /> 요소로 선언할 때 name 속성을 "struts"로 줘야 하는구나...
그리고 ActionEventListener를 당장 만들어서 선언한 뒤 name 값을 "struts"로 주었습니다.
하지만 이 객체는 DefaultActionProxy 에 주입되지 않았습니다.
당연한 결과 였습니다. DefaultActionProxy에는 단지 @Ineject(required=false) 로만 선언되어 있었기 때문에
name 속성이 "struts"로 되어있는 ActionEventListener 객체를 찾지 못하는 것 이었습니다.
(@Inject() 애노테이션의 value 속성의 기본값은 "default" 이기 때문에 "default" 로 찾습니다.)

그런데 뭔가 이상 합니다.
분명 struts-default.xml 에 수많은 <bean /> 요소들의 name 속성은 "struts"로 되어 있고
그 객체들을 필요로 하는 곳에서는 단지 @Inejct() 로만 요청하고 있는데 name 값이 틀림에도 불구하고
어떻게 정상적으로 의존성 주입이 되는 것일까...

처음에 디버깅 할 때는 객체를 찾아오는 부분에서 시작했습니다. 여기서 대부분의 시간을 보냈지만 답을 찾을 수 없었습니다. 그래서 초심으로 돌아가서 FilterDispatcher의 init() 메서드부터 차근차근 보았습니다.
디버깅을 하면서 XWork의 초기화 과정을 발견 했는데 Struts2에서 XWork를 초기화 할 때
XWork의 생명주기 관련 인터페이스인 ConfigurationProvider 의 구현체들을 등록하는 것을 발견 했습니다.
그 중에서 BeanSelectionProvider 가 있는데 이 클래스가 해답 이었습니다.
XWork의 초기화 과정에서 BeanSelectionProvider.register() 메서드가 호출 되는데 이 메서드에서
특정 타입이면서 name 값이 "struts"인 객체들의 name 값을 "default" 로 바꿔 주는 것 입니다.
그래서 정상적으로 의존성 주입이 되고 있었던 것 입니다.

특정 타입의 목록은 다음과 같습니다.
ObjectFactory.class
XWorkConverter.class
TextProvider.class
ActionProxyFactory.class
ObjectTypeDeterminer.class
ActionMapper.class
MultiPartRequest.class
FreemarkerManager.class
VelocityManager.class
Struts2 내에서 @Inject() 애노테이션으로 요청하는 객체들 중
위 타입 이외에 것들은 (UnknownHandler, ActionEventListener) 
<bean /> 요소로 선언 할 때 name 속성은 무턱대고 "struts" 로 지정할 경우 정상적으로 작동 되지 않습니다.
그냥 name 속성을 생략 하거나 동일한 타입이 여러개일 경우 주입하고자 하는 <bean /> 요소에 name 속성을
"default" 로 설정 하면 됩니다.
Posted by 째코

댓글을 달아 주세요

  1. nike free run 2013.04.13 22:48  댓글주소  수정/삭제  댓글쓰기

    ♡ 내가 새라면 너에게 자유를 주고,내가 꽃이라면 너에게 향기 를 주겠지만 나 사람이기에 너에게 사랑을 줄게.


두개의 메서드가 있습니다.
public interface ActionEventListener {
    public Object prepare(Object action, ValueStack stack);

    public String handleException(Throwable t, ValueStack stack);
}

prepare() 메서드는 DefaultActionInvocation 객체가 초기화 될 때 Action 객체도 생성하는 과정이 있는데
Action 객체가 생성되고 바로 호출 됩니다.

handlerException() 메서드는 해당 Action 객체의 메서드가 호출이 실패 했을 경우 호출 됩니다.

ActionEventListener 구현체 역시 struts.xml에 <bean /> 요소로 선언하면
XWork Container에 의해 DefaultActionInvocation 객체에 주입 됩니다.

ps. Struts2는 다 좋은데... Spring 처럼 완벽한 래퍼런스좀 배포했으면 하는 바램이...
Posted by 째코

댓글을 달아 주세요


요즘 "어떤 이유"에 의해서 Struts2 소스를 자세히 살펴보고 있습니다.
흥미로운 것들이 많이 발견 되는데 그 중에 "어떤 이유" 에 영향을 아주 도움이 되는 것을 발견 했습니다.
그것은 com.opensymphony.xwork2.UnknownHandler 라는 인터페이스 입니다.
public interface UnknownHandler {
   
    public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException;
    
    public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) throws XWorkException;

}
Struts2 api 에서 구현체는 당연히 없었습니다. 왜 당연히 없어야 하는 인터페이스인지는 아래 나옵니다.

Struts2는 요청이 들어오면 ActionMapper의 도움을 받아 해당 요청이 Struts2로 들어온 요청인지 그리고
네임스페이스와 액션이름을 알아 냅니다. 좀 더 자세히 말하자면 ActionMapper는 단순히 url을 해석하여 ActionMapping(네임스페이스, 액션, 메서드, 등 정보가 들어있음) 객체를 만들어 줍니다.
실제 설정에 대한 유효성 검사는 없습니다.

그 후 DefaultActionProxy 라는 객체의 prepare() 라는 메서드에서 실질적인 유효성 검증을 하게 됩니다.
config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
getActionConfig() 메서드가 유효성 검증을 하고 유효한 요청이라면 ActionConfig 객체를 리턴하고
유효하지 않다면 null을 리턴합니다. 여기서 null이 리턴이 될 경우 ConfigurationException 을 던집니다.

하지만 ConfigurationException 을 던지기 전 다음 코드가 실행 됩니다.
if (config == null && unknownHandler != null) {
      config = unknownHandler.handleUnknownAction(namespace, actionName);
}
위 코드에 대해서는 따로 설명하지 않아도 되겠죠?

UnknownHandler 인터페이스는 프레임워크를 사용하는 개발자에게 100% 책임이 있던 것이기 때문에
Struts2에는 구현체가 없었던 것 입니다. 일반 유저는 이 인터페이스를 대부분 사용하지 않을 것이고 제 3의 프레임워크 개발자를 위한 용도 같습니다.

UnknownHandler 인터페이스의 구현체를 만든 후 struts.xml에 <bean /> 요소로 선언하면
XWork Container에 의해 DefaultActionProxy 객체의 unknownHandler로 주입 됩니다.


'Struts2' 카테고리의 다른 글

<bean /> 선언시 주의할 점  (1) 2008.10.28
ActionEventListener 인터페이스 발견  (0) 2008.10.27
UnknownHandler 인터페이스 발견  (0) 2008.10.27
method prefix와 ! convention  (0) 2008.10.26
Struts2 아키텍쳐  (2) 2008.01.31
Struts2 설정 - 상수 설정  (0) 2008.01.27
Posted by 째코

댓글을 달아 주세요

Struts2 소스를 보던중에 발견 했습니다.
집에 Struts2 책이 두권 있지만 처음부터 끝까지 보지 않아서 지나친 부분인데 책에도 나와 있습니다.
간단한 사항이지만 책에서는 좀 애매하고 어렵게 기술된거 같습니다.

일반적인 url 입니다.

method prefix가 존재하는 url 입니다.
http://localhost:8080/jjaeko/test.action 
(파라미터 name중 method:getBar 존재)

! 가 붙은 url 입니다.

이게 무슨 내용이냐면
일반적인 url일 경우 서버쪽에 설정된 메서드를 정상적으로 호출합니다.
method prefix가 존재할 경우 DefaultActionMapper가 호출해야할 method를 바꿔 버립니다.
마찬가지로 ! 가 붙은 url도 DefaultActionMapper가 호출해야할 method를 바꿔 버립니다.

우선순위는 !, method prefix, 서버쪽 설정순 입니다.
우선순위에 의해 후보설정들은 무시 됩니다.

ps. 제목에 "! convention" 이라고 쓴 이유는 소스코드상에
// handle "name!method" convention. 라는 주석이 있기 때문입니다.

ps2. ! convention이 작동하려면 struts.enable.DynamicMethodInvocation 상수 값이 true여야 합니다.

'Struts2' 카테고리의 다른 글

ActionEventListener 인터페이스 발견  (0) 2008.10.27
UnknownHandler 인터페이스 발견  (0) 2008.10.27
method prefix와 ! convention  (0) 2008.10.26
Struts2 아키텍쳐  (2) 2008.01.31
Struts2 설정 - 상수 설정  (0) 2008.01.27
Strtus2 설정 - struts.xml  (3) 2008.01.20
Posted by 째코

댓글을 달아 주세요