2

Spring の「HTTP Invoker」リモーティング ソリューションを使用して、DAO をさまざまなアプリケーションに公開していますが、単一のサーバーですべてのデータベースにアクセスしています。

これはうまく機能しますが、サーバーが HibernateSystemException などをスローした場合、Spring はそれをシリアル化し、ネットワーク経由でクライアントに送り返します。クライアントのクラスパスに HibernateSystemException が含まれていない (そしてすべきでない) ため、これは機能しません。

このような問題を回避するために、Spring Remoting がクライアントとサーバーの間で共通であると指定したもので私の例外をラップする方法があるでしょうか?

DAO が実行するすべての処理を try/catch でラップすることにより、サーバー コードでそれを実行できることはわかっていますが、これは明らかにずさんです。

ありがとう、ロイ

4

5 に答える 5

4

私もこの問題に遭遇しました。Spring 3.1、JPA 2、および Hibernate を JPA プロバイダーとして使用してデータベースにアクセスする HTTP Invoker を介してサービスを公開しています。

この問題を回避するために、カスタム Interceptor と WrappedException という例外を作成しました。インターセプターは、サービスによってスローされた例外をキャッチし、リフレクションとセッターを使用して例外と原因を WrappedException に変換します。クライアントのクラス パスに WrappedException があると仮定すると、スタック トレースと元の例外クラス名がクライアントに表示されます。

これにより、クライアントがクラスパスに Spring DAO を持つ必要性が緩和され、私が知る限り、元のスタック トレース情報が変換で失われることはありません。

インターセプター

public class ServiceExceptionTranslatorInterceptor implements MethodInterceptor, Serializable {

    private static final long serialVersionUID = 1L;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            return invocation.proceed();
        } catch (Throwable e) {
            throw translateException(e);
        }
    }

    static RuntimeException translateException(Throwable e) {
        WrappedException serviceException = new WrappedException();

        try {
            serviceException.setStackTrace(e.getStackTrace());
            serviceException.setMessage(e.getClass().getName() +
                    ": " + e.getMessage());
            getField(Throwable.class, "detailMessage").set(serviceException, 
                    e.getMessage());
            Throwable cause = e.getCause();
            if (cause != null) {
                getField(Throwable.class, "cause").set(serviceException,
                        translateException(cause));
            }
        } catch (IllegalArgumentException e1) {
            // Should never happen, ServiceException is an instance of Throwable
        } catch (IllegalAccessException e2) {
            // Should never happen, we've set the fields to accessible
        } catch (NoSuchFieldException e3) {
            // Should never happen, we know 'detailMessage' and 'cause' are
            // valid fields
        }
        return serviceException;
    }

    static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        Field f = clazz.getDeclaredField(fieldName);
        if (!f.isAccessible()) {
            f.setAccessible(true);
        }
        return f;
    }

}

例外

public class WrappedException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private String message = null;

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return message;
    }
}

ビーン配線

<bean id="exceptionTranslatorInterceptor" class="com.YOURCOMPANY.interceptor.ServiceExceptionTranslatorInterceptor"/>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="YOUR_SERVICE" />
    <property name="order" value="1" />
    <property name="interceptorNames">
        <list>
            <value>exceptionTranslatorInterceptor</value>
        </list>
    </property>
</bean>
于 2012-04-30T18:04:24.253 に答える
0

クライアントにクラスパスを含めたくないことは理解できますがHibernateSystemException、HTTPInvokerを適切に使用している場合は、そうする必要があると思います。これは、サービスファサード/インターフェイスレイヤーとして設計されていません。RMIの代わりにHTTPを使用して、リモートJVMでJavaメソッドを実行できるようにするだけです。

したがって、クライアントがHibernateに依存することを本当に望まない場合は、try/catchブロックが最適です。(ただし、デバッグが面倒になるため、これには反対します。スタックトレースは、クライアントとサーバーの間で分割されます)。

私自身は使用していませんが、org.springframework.remoting.support.RemoteExporter.setInterceptors(Object[])あちこちにtry / catchを追加するのではなく、特定の例外をキャッチするアスペクトを1か所で追加する方法を試すことができます。

于 2012-03-12T14:15:06.853 に答える
0

返される例外を完全に制御するには、DAO の前にある Facade レイヤーでの try/catch がまさに必要だと主張します。最初は醜く感じることに同意しますが、私の意見では、クライアントと DAO の間の重要なレイヤーです。

void の戻り値の型を使用するのではなく、何らかの種類の OperationStatus オブジェクトを返して、ストアデータ API 呼び出しの結果 (機能した、しなかった) とエラー メッセージの両方を伝えることもできます。

于 2012-03-12T14:30:56.493 に答える
0

AspectJ を使用して例外をラップしていましたが、Spring プロキシで発生する例外、たとえばデータベースへの接続が失敗したときのアノテーション @Transactional では機能しません。ただし、RmiServiceExporter の setInterceptor メソッドは完全に機能します。

于 2015-07-09T14:21:25.750 に答える
0

N1H4L に似たソリューションを使用しましたが、AspectJを使用しました。

最初に、クラスBusinessExceptionを拡張するためにクライアントに認識してもらいたいすべての例外を作成しました(私の場合は、サービス インターフェイスと DTO を備えた jar 内の RuntimeException の非常に単純なサブクラスです)。

クライアントにサービスの内部についてあまり知られたくないので、「内部サーバー エラー」とだけ言います。

package com.myproduct.myservicepackage;

import com.myproduct.BusinessException;
import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class InternalServerErrorExceptionAspect {
    @Pointcut("execution(public * com.myproduct.myservicepackage..*Service.*(..))")
    public void publicServiceMethod() {}

    @Around("publicServiceMethod()")
    public Object hideNonBusinessExceptions(ProceedingJoinPoint jp) throws Throwable {
        try {
            return jp.proceed();
        } catch (BusinessException e) {
            throw e;
        } catch (RuntimeException e) {
            e.printStackTrace();
            throw new RuntimeException("Internal server error.")
        } 
    }
}

BusinessException クラスは次のとおりです。

package com.myproduct.BusinessException;

public class BusinessException extends RuntimeException {

    private static final long serialVersionUID = 8644864737766737258L;

    public BusinessException(String msg) {
        super(msg);
    }

}
于 2014-05-12T16:13:36.953 に答える