nekop's blog

OpenShift / JBoss / WildFly / Infinispanの中の人 http://twitter.com/nekop

「GroovyでEJB作成は辛い」の解説

GroovyでEJB作成は辛い、でなにがどうなっているのかの解説。

Exception while loading the app : EJB Container initialization error
javax.ejb.EJBException: Illegal non-business method access on no-interface view
     at megascus.javaee6groovy.__EJB31_Generated__MessageFacade__Intf____Bean__.$getStaticMetaClass(Unknown Source)
     at megascus.javaee6groovy.MessageFacade.<init>(MessageFacade.groovy)
     at megascus.javaee6groovy.__EJB31_Generated__MessageFacade__Intf____Bean__.<init>(Unknown Source)

インタフェースなしのEJBGlassFishにデプロイするとコケる話。

NoInterfaceEJBというEJBクラスを作る。それを呼び出す側は当然こうなる。

@Inject
NoInterfaceEJB ejb;

ここには実際にはNoInterfaceEJBのインスタンスがそのまま代入されるのではなく、コンテナが仲介するためにプロキシしたクラスが代入される。このコンテナが生成したクラスはプロキシ、ビュー、スタブなどと呼ばれる。クライアントプロキシ、というようにクライアント、という接頭辞がついたりつかなかったりもする。

さて、このEJBにはビジネスインタフェースが無いので、プロキシしたクラスはどうやって作るかというとデプロイ時にこのクラスを継承したクラスをダイナミックに生成する。逆に言うと、継承する以外にプロキシを元のEJBの型に代入する方法がない。こんな感じ。

class NoInterfaceEJBProxy extends NoInterfaceEJB {
  @Override
  public void foo() {
    // EJBのコンテナサイドの処理とかインターセプタの処理とかいろいろやってからNoInterfaceEJB本体を呼び出すコードに差し替えられている
  }
  @Override
  protected void bar() {
    // ここは呼び出されたらいけないので例外投げるコードに差し替えられている
  }
}

というわけで実装継承しているのでコンストラクタが問題になる。継承してるんだからsuper()は必ず呼び出される。そして元々のGroovyで書かれたEJBコンストラクタでprotectedなメソッド、この例ではbar()が呼ばれてるので上のプロキシ実装のとおり例外が送出される。

ここでコンテナが何をすべきか、という話なんだけど、内部からのprotectedのメソッド呼び出しを判別してガン無視する、というのも一つの選択肢だとは思うのだけど、それも余計に話がややこしくなる。かと言って、この話をインタフェースなしEJBの制約として仕様に書くのもこれまたややこしくて微妙、というところ。結構タチの悪い問題な気がするけど、インタフェース書けばいいだけの問題なのであまり突っ込むモチベーションがない。あと、EJB仕様でもコンストラクタはなるべく避けて@PostConstruct使うことが推奨されてる。

個人的には仕様上でコンストラクタで処理を行うのはかなりどぎつい制限がありますよ、というような記述でもしておけばいいと思う。現状仕様ではコンストラクタはどこで何回呼ばれても構わないよう実装されていること、という実装の仕組みがわからないとなんだかよくわからないであろう制限が記述されている。

他にもfinal修飾子とか付いててもたぶんデプロイエラーになるんじゃないかなー。と思いつつ終わり。