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を書き換える必要がなくなるので、開発効率が向上すると思います。