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 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" />
그 밖에도 다른 <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() 애노테이션으로 요청하는 객체들 중 XWorkConverter.class
TextProvider.class
ActionProxyFactory.class
ObjectTypeDeterminer.class
ActionMapper.class
MultiPartRequest.class
FreemarkerManager.class
VelocityManager.class
위 타입 이외에 것들은 (UnknownHandler, ActionEventListener)
<bean /> 요소로 선언 할 때 name 속성은 무턱대고 "struts" 로 지정할 경우 정상적으로 작동 되지 않습니다.
그냥 name 속성을 생략 하거나 동일한 타입이 여러개일 경우 주입하고자 하는 <bean /> 요소에 name 속성을
"default" 로 설정 하면 됩니다.