読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

Interceptorにおける@PostConstructの動作がヘンでござるの巻

Struts2

struts.xmlにおいては、ある程度はBeanの定義やDIを設定できます。
もちろんSpringと比較するとごく簡単な機能しか提供していませんが。


さて、とあるInterceptorを作成してstruts.xmlに登録し、そのInterceptorに対してパラメータを与えてみました。

<interceptor name="xyz" class="aa.bb.cc.XyzInterceptor">
  <param name="foo">bar</param>
</interceptor>


で、@javax.annotation.PostConstructで処理を呼び出そうと思ったのです。
その中ではDIしたパラメータ値(foo)を使います。まあこんな感じで。

public class XyzInterceptor implements Interceptor {

    private String foo;

    public void setFoo(String foo) { this.foo = foo; }


    @PostConstruct
    public void postConstruct() {
        // fooを使った何かの初期化処理
    }


    /** {@inheritDoc} */
    @Override
    public String intercept(final ActionInvocation invocation) throws Exception {
        // なんかの処理
    }
    
}


@PostConstructのJavadocによると、

The PostConstruct annotation is used on a method that needs to be executed
after dependency injection is done to perform any initialization. This
method MUST be invoked before the class is put into service. This
annotation MUST be supported on all classes that support dependency
injection.



PostConstructアノテーションはDIの後に何か初期化処理を実行したい場合に使う。

PostConstructアノテーションをつけたメソッドは、サービスへDIされる前に必ず実行されなければならない。

すべてのDI処理系はPostConstructをサポートしなければならない。


とあります。だから何も問題ないハズ。

しかし…


なんかうまく動かない模様。しょうがないのでデバッガで追ってみると、

なんとpostConstruct()メソッドが、setFooが呼ばれる前に実行されているではありませんか。なんじゃそりゃ。


理由を考えてみた


うーむ、何故だ、ということで原因を考えてみてヒラメいた。


僕の環境ではstrutsのBeanファクトリはSpringと連携させてます。まあ今どき、Struts2使うならフツーSpring使いますよね。


で、おそらくなんですが、XyzInterceptorをインスタンス化するときに、StrutsはSpringに対して

<bean id="..." class="aa.bb.cc.XyzInterceptor">
  <property name="foo" value="bar" />
</bean>

ではなくて、

<bean id="..." class="aa.bb.cc.XyzInterceptor" />

という指令を出しているのではないかと。で、BeanRepositoryからXyzInterceptorのインスタンスを取り出したあとでStruts2側でsetFooしているという。

RTFS

Struts2-Springのブリッジ実装の本体はこのクラスです。

com.opensymphony.xwork2.ObjectFactory


で、169行目あたりから始まるbuildInterceptorメソッドがぁゃιぃ。

public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams) throws ConfigurationException {
    String interceptorClassName = interceptorConfig.getClassName();
    Map<String, String> thisInterceptorClassParams = interceptorConfig.getParams();
    Map<String, String> params = (thisInterceptorClassParams == null) ? new HashMap<String, String>() : new HashMap<String, String>(thisInterceptorClassParams);
    params.putAll(interceptorRefParams);

    String message;
    Throwable cause;

    try {
        // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context
        Interceptor interceptor = (Interceptor) buildBean(interceptorClassName, null);
        reflectionProvider.setProperties(params, interceptor);
        interceptor.init();

        return interceptor;
    } catch (InstantiationException e) {
        cause = e;
        message = "Unable to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
    } catch (IllegalAccessException e) {
        cause = e;
        message = "IllegalAccessException while attempting to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
    } catch (ClassCastException e) {
        cause = e;
        message = "Class [" + interceptorClassName + "] does not implement com.opensymphony.xwork2.interceptor.Interceptor";
    } catch (Exception e) {
        cause = e;
        message = "Caught Exception while registering Interceptor class " + interceptorClassName;
    } catch (NoClassDefFoundError e) {
        cause = e;
        message = "Could not load class " + interceptorClassName + ". Perhaps it exists but certain dependencies are not available?";
    }

    throw new ConfigurationException(message, cause, interceptorConfig);
}


この行に注目。

    Interceptor interceptor = (Interceptor) buildBean(interceptorClassName, null);
    reflectionProvider.setProperties(params, interceptor);


どうやら僕の予想は大当たりのようです。┐(´д`)┌ヤレヤレ


ま、おとなしくInterceptor#init()使っとけってことですね。