2012-03-12 111 views
1

我使用Spring's 'HTTP Invoker' remoting solution來將DAO展示給許多不同的應用程序,但在單個服務器中擁有所有的數據庫訪問權限。Spring Remoting HTTP調用者 - 異常處理

這很好,但如果服務器拋出一個HibernateSystemException異常,Spring會將其序列化並通過電線將其發送回客戶端。這是行不通的,因爲客戶端沒有(也不應該)在其類路徑中有HibernateSystemException。

可能有辦法讓Spring Remoting將我的異常包裝在我指定的東西中,這將在客戶端和服務器之間通用以避免這樣的問題?

我知道我可以在我的服務器代碼中通過包裝DAO在try/catch中執行的所有操作來做到這一點,但這無疑是草率的。

感謝, 羅伊

回答

3

我也遇到過這個問題;我通過使用Spring 3.1,JPA 2和Hibernate作爲JPA提供者訪問數據庫的HTTP Invoker公開了一個服務。

要解決這個問題,我寫了一個自定義的攔截器和一個名爲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> 
+0

完美,這就是我一直在尋找。這個問題坐了一會兒後,我想,我需要一個攔截器來處理它,但從來沒有編碼。謝謝! – 2012-04-30 18:07:08

0

我能理解你不想讓你的客戶在他們的類路徑HibernateSystemException,但是,我要說,他們都應該是適當地使用HTTPInvoker。它並不是設計成服務外觀/界面層:它所要做的就是讓你在遠程JVM上運行Java方法,使用HTTP而不是RMI。

所以,如果你真的不希望客戶端依賴Hibernate,你的try/catch塊就是要走的路。 (儘管如此,我還是會反駁,因爲它會讓調試變得很痛苦:你的堆棧跟蹤現在將在客戶端和服務器之間進行劃分)。

我自己並沒有使用它,但您可以嘗試使用org.springframework.remoting.support.RemoteExporter.setInterceptors(Object[])方法添加一個方面,以在一個位置捕獲該特定的異常,而不是在所有位置添加try/catch。

0

我會爭論在您的DAO面前層的try/catch 正好你想要什麼,以獲得完全控制你返回的異常。我認爲它最初感覺很難看,但在我看來,它是客戶和DAO之間的一個重要層面。

您甚至可以返回某種類型的OperationStatus對象,而不是使用void返回類型來傳遞商店數據API調用的結果(工作,沒有)和錯誤消息。

0

我用溶液S與N1H4L類似,但是與AspectJ

首先我做了所有的異常我希望客戶瞭解的擴展類BusinessException(這在我的情況是RuntimeException的與服務接口和DTO的罐子一個非常簡單的子類)。

因爲我不想讓客戶很瞭解服務的內部我只是說「內部服務器錯誤」。

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); 
    } 

} 
0

我使用AspectJ包裝異常,但它並沒有對發生在春季代理例外,例如工作註釋@Transactional當連接到數據庫失敗時。 但是,RmiServiceExporter上的方法setInterceptor完美工作。