Glassfish のソースコードを元に、リモートEJBコールがどのように処理されていくかを説明します。
トラブルシュートにはどうしてもソースコードを読む必要がありますし、設定の問題なのかバグなのかの切り分けも容易になります。手っ取り早いし確実です。 そういった意味で、コードの概要を把握しておくことはフレームワークを使う上で重要なことがらです。
Glassfish ではどのようにしてリモートEJBのメソッド呼び出しが処理されているかの概要を示すことを目的とするため、今回の説明のために不要な箇所は大幅に省略しています。 つまり概要を把握しやすいようにソースコードは改編したものとなりますので注意してください。
コードは Glassfish 4.1.2 系のものです。
アウトライン
大枠は以下のようになります。
もとにするEJB
以下のような リモートの SSB を対象にします。
@Remote public interface FooService { viod hoge(String atg); }
SSB の実装は以下のようなものを想定します。
@Stateless public class FooServiceImpl implements FooService { public void hoge(String arg) { } }
Foo サービスの hoge メソッドがどのように呼び出されるかについて見ていきます。
リモート・オブジェクトのコール(クライアント)
EJB は分散アプリケーションに向けたものであり、リモート・オブジェクトをローカル・オブジェクトと同じように扱うことができます。 裏の仕組みとしては、 EJB は ORB (Object Request Broker) を元にしてクライアントから呼び出されます。
コンテナにより自動生成された ORB 通信用のオブジェクトを経由することでリモートコールの通信が処理されるますが、ここではその詳細については踏み込みません。
例えば以下のような(EJBの)クライアントコードがあった場合、
@Path("/") public class FooResource { @EJB private FooService fooSrvice; }
fooSrvice
には _FooService_Wrapper
といった自動生成されたプロキシクラスが DI されます。
このサービスのメソッド呼び出しは、_FooService_DynamicStub
といった、こちらも自動生成された ORB 通信用のスタブクラスに委譲されます。
このスタブからのメソッド呼び出しは以下で処理されます。
package com.sun.corba.ee.impl.presentation.rmi.codegen; public class CodegenStubBase extends Stub { protected Object invoke(int methodNumber, Object[] args) { Method method = this.methods[methodNumber]; return this.handler.invoke((Object)null, method, args); } }
ハンドラの invoke()
を method
を引数にコールします。
method
は _FooService_Remote
という自動生成されたクラスの hoge()
メソッドになります。
その結果、実際に呼び出される該当コードは以下になります。
package com.sun.corba.ee.impl.presentation.rmi; public final class StubInvocationHandlerImpl implements LinkedInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) { interceptor.preInvoke(); Object ret = this.privateInvoke(delegate, proxy, method, args); interceptor.postInvoke(); return ret; } }
preInvoke()
と postInvoke()
の間で privateInvoke()
を呼びます。
private Object privateInvoke(Delegate delegate, Object proxy, final Method method, Object[] args) { DynamicMethodMarshaller dmm = this.pm.getDynamicMethodMarshaller(method); ORB orb = delegate.orb(this.stub); ServantObject so = delegate.servant_preinvoke(this.stub, giopMethodName, method.getDeclaringClass()); Object[] copies = dmm.copyArguments(args, orb); Object result = method.invoke(so.servant, copies); return dmm.copyResult(result, orb); }
各変数の具体的なインスタンスは以下となります。
- delegate :
FooService
- method :
_FooService_Remote.hoge()
- pm :
PresentationManagerImpl
method
は _FooService_Remote.hoge()
のメソッドコールとして ORB 経由でリモートのEJBコールが行われます。
リモートコールの受け口
EJBコールは EJBObjectInvocationHandlerDelegate
が入り口になります。
package com.sun.ejb.containers; public class EJBObjectInvocationHandlerDelegate implements InvocationHandler { private Class remoteBusinessIntfClass; private EJBObjectInvocationHandler delegate; public Object invoke(Object proxy, Method method, Object[] args) { return delegate.invoke(remoteBusinessIntfClass, method, args); } }
このクラスは EJB のリモートインターフェースプロキシと EJBObjectInvocationHandler
の単純なアダプタとなっています。
各変数の具体的なインスタンスは以下となります。
- remoteBusinessIntfClass :
ClientDelegateImpl
- delegate :
EJBObjectInvocationHandler
- method :
_FooService_Remote.hoge()
EJBObjectInvocationHandler.invoke()
に処理を委譲します。
package com.sun.ejb.containers; public final class EJBObjectInvocationHandler extends EJBObjectImpl implements InvocationHandler { Object invoke(Class clientInterface, Method method, Object[] args) { EjbInvocation inv = baseContainer.createEjbInvocation(); // ... baseContainer.preInvoke(inv); Object returnValue = baseContainer.intercept(inv); baseContainer.postInvoke(inv); baseContainer.getSecurityManager().resetPolicyContext(); return returnValue; }
このクラスが EJB コールの実質的な入り口です。
EJBObjectInvocationHandler
は以下のようなクラス階層になっています。
- abstract class EJBLocalRemoteObject
- abstract class EJBObjectImpl extends EJBLocalRemoteObject implements EJBObject
- class EJBObjectInvocationHandler extends EJBObjectImpl implements InvocationHandler
- abstract class EJBObjectImpl extends EJBLocalRemoteObject implements EJBObject
baseContainer.createEjbInvocation()
により EjbInvocation
を生成し、このクラスに EJB 起動に必要な情報が設定されます。
作成した EjbInvocation
を引数にして baseContainer.intercept(inv)
をコールします。
BaseContainer
BaseContainer
クラスは以下のクラスの親クラスです。
- StatefulSessionContainer
- StatelessSessionContainer
- MessageBeanContainer
- CMCSingletonContainer
- BMCSingletonContainer
BaseContainer.intercept(inv)
は以下のようになります。
package com.sun.ejb.containers; public abstract class BaseContainer implements Container, EjbContainerFacade, JavaEEContainer { protected Object intercept(EjbInvocation inv) { Object result = null; if (inv.mustInvokeAsynchronously()) { EjbAsyncInvocationManager asyncManager = ((EjbContainerUtilImpl) ejbContainerUtilImpl).getEjbAsyncInvocationManager(); Future future = inv.isLocal ? asyncManager.createLocalFuture(inv) : asyncManager.createRemoteFuture(inv, this, (GenericEJBHome) ejbRemoteBusinessHomeStub); return (inv.invocationInfo.method.getReturnType() == void.class) ? null : future; } else { onEjbMethodStart(inv.invocationInfo.str_method_sig); result = interceptorManager.intercept(inv.getInterceptorChain(), inv); onEjbMethodEnd(inv.invocationInfo.str_method_sig, inv.exception); } return result; } }
非同期と同期のコールをここで分岐しています。今回の場合は else
側の同期コールになります。
InterceptorManager.intercept()
にてEJBメソッドコール時のインタセプトを処理します。
inv.getInterceptorChain()
ではインタセプターを順に実行する InterceptorChain
が得られます。
インタセプタチェーンはコンテナスタート時の以下の BaseContainer
内で InvocationInfo
として格納されており、EJBObjectInvocationHandler
の中で EjbInvocation
に設定されたものです。
BaseContainer
ではコンテナスタート時に以下のように InterceptorChain
が作成されます。
public final void setStartedState() { initializeInterceptorManager(); for(Object o : invocationInfoMap.values()) { InvocationInfo next = (InvocationInfo) o; setInterceptorChain(next); } for(Object o : this.webServiceInvocationInfoMap.values()) { InvocationInfo next = (InvocationInfo) o; setInterceptorChain(next); } containerState = CONTAINER_STARTED; }
インタセプタチェーン
インタセプタチェーンは以下から開始します。
package com.sun.ejb.containers.interceptors; public class InterceptorManager { public Object intercept(InterceptorManager.InterceptorChain chain, AroundInvokeContext ctx) { return chain.invokeNext(0, ctx); } }
InterceptorChain.invokeNext()
でインタセプタ順番に適用していく処理となります。
第一引数がインタセプタのインデックスとなっており、順番に実行していきます(開始時なので0
)。
package com.sun.ejb.containers.interceptors; class AroundInvokeChainImpl implements InterceptorManager.InterceptorChain { public Object invokeNext(int index, InterceptorManager.AroundInvokeContext inv) { return (index < size) ? interceptors[index].intercept(inv) : inv.invokeBeanMethod(); } }
インタセプタがあれば実行、無くなったら EjbInvocation.invokeBeanMethod()
で EJB メソッドをコールという流れです。
この時 interceptors[]
には AroundInvokeInterceptor
のインスタンスが 2つ入っています
SystemInterceptorProxy
へ処理を委譲するものSessionBeanInterceptor
へ処理を委譲するもの
interceptors[index].intercept(inv)
のコールは以下のように処理します。
package com.sun.ejb.containers.interceptors; class AroundInvokeInterceptor { Object intercept(final InterceptorManager.AroundInvokeContext invCtx) { final Object[] interceptors = invCtx.getInterceptorInstances(); return method.invoke(interceptors[index], invCtx); } }
最初のインタセプタでは SystemInterceptorProxy
のメソッドをコールします。
package com.sun.ejb.containers.interceptors; public class SystemInterceptorProxy { @AroundInvoke public Object aroundInvoke(InvocationContext ctx) throws Exception { return doCall(ctx, aroundInvoke); } private Object doCall(InvocationContext ctx, Method m) throws Exception { Object returnValue = null; if (delegate != null && m != null) { returnValue = m.invoke(delegate, ctx); } else { returnValue = ctx.proceed(); } return returnValue; }
doCall()
では委譲先があれば委譲メソッドコール、そうでなければ EjbInvocation.proceed()
をコールします。
今回は委譲先が無いため EjbInvocation.proceed()
の処理に移ります。
package com.sun.ejb; public class EjbInvocation extends ComponentInvocation implements InvocationContext, TransactionOperationsManager, org.glassfish.ejb.api.EJBInvocation, InterceptorManager.AroundInvokeContext public Object proceed() { interceptorIndex++; return getInterceptorChain().invokeNext(interceptorIndex, this); }
EjbInvocation
ではインタセプタのインデックスをインクリメントし、次のインタセプタを処理します。
2 つ目のインタセプタとして CDI用 の SessionBeanInterceptor
が入っており、これをコールします。
package org.jboss.weld.ejb; public class SessionBeanInterceptor extends AbstractEJBRequestScopeActivationInterceptor implements Serializable { public Object aroundInvoke(InvocationContext invocation) { return super.aroundInvoke(invocation); } }
親の aroundInvoke()
をコールしており、
package org.jboss.weld.ejb; public abstract class AbstractEJBRequestScopeActivationInterceptor implements Serializable { public Object aroundInvoke(InvocationContext invocation) throws Exception { if (this.isRequestContextActive()) { // リクエストスコープのコンテキストが有効 return invocation.proceed(); } else { EjbRequestContext requestContext = this.getEjbRequestContext(); Object ret; requestContext.associate(invocation); requestContext.activate(); ret = invocation.proceed(); requestContext.invalidate(); requestContext.deactivate(); return ret; } } }
CDI のリクエストコンテキストの処理を行うメソッドとなります。
今回はリクエストスコープのコンテキストが既に有効なので EjbInvocation.proceed()
をコールするのみで終わります。
前述の通り、次のインタセプタチェーンのコールに戻り、
public Object proceed() { interceptorIndex++; return getInterceptorChain().invokeNext(interceptorIndex, this); }
AroundInvokeChainImpl
では、
public Object invokeNext(int index, InterceptorManager.AroundInvokeContext inv) { return (index < size) ? interceptors[index].intercept(inv) : inv.invokeBeanMethod(); } }
インタセプタを実行し尽くして EjbInvocation.invokeBeanMethod()
をコールします。
EJB メソッドのコール
EjbInvocation.invokeBeanMethod()
の処理です。
package com.sun.ejb; public class EjbInvocation extends ComponentInvocation // ... { public Object invokeBeanMethod() { return ((BaseContainer) container).invokeBeanMethod(this); } }
BaseContainer
で自身(EjbInvocation
) を引数に invokeBeanMethod()
をコールします。
package com.sun.ejb.containers; public abstract class BaseContainer implements Container, EjbContainerFacade, JavaEEContainer { public Object invokeBeanMethod(EjbInvocation inv) { return securityManager.invoke( inv.getBeanMethod(), inv.isLocal, inv.ejb, inv.getParameters()); } }
BaseContainer
では EJBSecurityManager
に処理を委譲してEJBコールを行います。
EJBSecurityManager
では、
package org.glassfish.ejb.security.application; public final class EJBSecurityManager implements SecurityManager { public Object invoke(Method beanClassMethod, boolean isLocal, Object obj, Object[] objArr) { return this.runMethod(beanClassMethod, obj, objArr); } public Object runMethod(Method beanClassMethod, Object obj, Object[] oa) { String oldCtxID = setPolicyContext(this.contextId); Object ret = beanClassMethod.invoke(obj, oa); resetPolicyContext(oldCtxID, this.contextId); return ret; } }
JACC のコンテキストの処理を挟んで、beanClassMethod.invoke()
により EJBのサービスメソッドをコールします。
beanClassMethod
は FooServiceImpl.hoge()
であり、リフレクション経由でようやく EJB のメソッドのコール となります。
まとめ
- EJBコールは
EJBObjectInvocationHandler
が実質的な入り口 BaseContainer
が主役EjbInvocation
が EJB 起動時の情報や状態を管理するInterceptorManager
およびその内部クラスAroundInvokeChain
がEJBコール時のインタセプタで各種処理を挟む- 最終的な EJBメソッドのコールは
EJBSecurityManager
により行われる