S2GWT的なもの
GWTのRPCでは、com.google.gwt.user.client.rpc.RemoteServiceを継承したサービスインタフェースとその対になる非同期インタフェース、そしてcom.google.gwt.user.server.rpc.RemoteServiceServletを継承したサービス実装の3点セットが必要になります。
GWTアプリケーションでSeasar2を使っている場合、DI機能でサービス実装をインジェクションしたいところですが、GWTのサービス実装クラスは個別にサーブレットとしてweb.xmlに登録する必要があり、そのままではDIすることができません。
そこで、GWTからS2へのゲートウェイとなる単一のサーブレットを用意することを考えます。GWT側では全てのRPCをこのゲートウェイにマッピングします。ゲートウェイ内部ではS2Containerをルックアップして適切なサービス実装を探し、処理を委譲するわけです。
こういう方式を実践している方は他にもおられるようです。僕はこちらを参考にして、以下のようにゲートウェイを作成しました。
import org.seasar.framework.container.SingletonS2Container; import org.seasar.framework.util.StringUtil; import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.server.rpc.RPC; import com.google.gwt.user.server.rpc.RPCRequest; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader; public class S2RemoteServiceServlet extends RemoteServiceServlet { private static final long serialVersionUID = -7247717864097930745L; @Override public String processCall(String payload) throws SerializationException { String componentName = getRemoteServiceComponentName(payload); Object service = SingletonS2Container.getComponent(componentName); try { RPCRequest rpcRequest = RPC.decodeRequest(payload, null, this); onAfterRequestDeserialized(rpcRequest); return RPC.invokeAndEncodeResponse(service, rpcRequest.getMethod(), rpcRequest.getParameters(), rpcRequest .getSerializationPolicy(), rpcRequest.getFlags()); } catch (IncompatibleRemoteServiceException e) { log( "An IncompatibleRemoteServiceException was thrown while processing this call.", e); return RPC.encodeResponseForFailure(null, e); } } private String getRemoteServiceComponentName(String encodedRequest) { ClassLoader classLoader = Thread.currentThread() .getContextClassLoader(); String serviceName; try { ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader( classLoader, this); streamReader.prepareToRead(encodedRequest); serviceName = streamReader.readString(); } catch (SerializationException e) { throw new IncompatibleRemoteServiceException(e.getMessage(), e); } Class<?> serviceClass; try { serviceClass = Class.forName(serviceName, false, classLoader); } catch (ClassNotFoundException e) { throw new IncompatibleRemoteServiceException( "Could not locate requested interface '" + serviceName + "' in default classloader.", e); } if (!RemoteService.class.isAssignableFrom(serviceClass)) { throw new IncompatibleRemoteServiceException( "Blocked attempt to access interface '" + serviceClass.getName().replace('$', '.') + "', which doesn't extend RemoteService;" + " this is either misconfiguration or a hack attempt."); } // 例えばFooServiceというクラスであれば"fooService" // というコンポーネント名にマッピングされ、この名前を // 使ってS2Containerからサービス実装がルックアップされる return StringUtil.decapitalize(serviceClass.getSimpleName()); } }
このクラスを以下のようにweb.xmlに登録しておきます。
<servlet> <servlet-name>s2RemoteService</servlet-name> <servlet-class>com.kaiseh.gwt.servlet.S2RemoteServiceServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>s2RemoteService</servlet-name> <url-pattern>/hellogwt/s2RemoteService</url-pattern> </servlet-mapping>
GWTアプリのクライアント側では、全てのRPCインタフェースで、URLとして"s2RemoteService"を使うように設定します。これは@RemoteServiceRelativePathアノテーションで指定可能です。
@RemoteServiceRelativePath("s2RemoteService") public interface CalculatorService extends RemoteService { int add(int a, int b); }
上記のCalculatorServiceの例の場合、このインタフェースを実装したCalculatorServiceImplクラスを"calculatorService"というコンポーネント名でapp.diconから探せるようにしておけば、後はS2がマッピングを行ってくれます。
このようにS2とGWT RPCを連携すると、RPCインタフェースを追加するたびにweb.xmlを書き換える必要がなくなるので、開発効率が向上すると思います。