1. 程式人生 > >dubbo實踐1..異常處理

dubbo實踐1..異常處理

 使用dubbo有幾年了,最近開始在部落格上分享自己的一些實際工作經驗,今天就先說下dubbo的異常處理,請大家多加指正。 

       dubbo有自己的異常處理機制,當服務端丟擲一個dubbo可以處理傳遞的異常時,會直接在客戶端上再次丟擲,由開發者自己去處理。注意:這裡說的不是所有異常,而是dubbo可以處理傳遞的異常,具體這個後邊再說。

     先看兩段程式碼,介面程式碼:

public interface IPersonService {
	public Person getPerson(long id);
	/**
	 * 根據名稱獲取
	 * 
	 * @param name
	 * @return
	 */
	public Person getByNick(String name);
}
      簡單實現:
public class PersonServiceImpl implements IPersonService {
	private final static AtomicInteger ID = new AtomicInteger();

	@Override
	public Person getPerson(long id) {
		Person person = new Person();
		person.setId(id);
		person.setName("某某");
		try {
			Thread.sleep(2000l);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return person;
	}

	@Override
	public Person getByNick(String name) {
		name = name.trim();
		Person person = new Person();
		person.setId(ID.getAndIncrement());
		person.setName(name);
		return person;
	}

}
        這兩段程式碼很簡答,先看getByNick方法,根據使用者名稱稱獲取使用者資訊,裡面有一個去空格的操作(主要為了觸發異常),正常呼叫是沒有問題的,但如果傳入null,就會丟擲很常見且低階的空指標異常。我們看下呼叫程式碼:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
				new String[] { "app-dubbo-consumer.xml" });
		context.start();
		IPersonService personService = context.getBean(IPersonService.class);
		// 正常輸入引數
		outString(personService.getByNick("某某"));
		// 輸入null
		outString(personService.getByNick(null));
     執行後,首先會列印{"id":0,"name":"某某"},然後出現java.lang.NullPointerException,一切在我們的預料內,dubbo把服務端的空指標異常傳遞給客戶端了。

     正常來說,空指標異常是不應該出現的,而且客戶端遇到這個錯誤肯定直接懵了,所以我們做下簡單的修改,服務端程式碼:

@Override
	public Person getByNick(String name) {
		if (name == null) {
			throw new RuntimeException("親你咋沒輸入暱稱呢");
		}
		name = name.trim();
		Person person = new Person();
		person.setId(ID.getAndIncrement());
		person.setName(name);
		return person;
	}
         客戶端再次呼叫結果:
Exception in thread "main" java.lang.RuntimeException: 親你咋沒輸入暱稱呢
	at org.dubbo.provider.PersonServiceImpl.getByNick(PersonServiceImpl.java:28)
	at com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)
     結果友好了多,甚至你可以直接對exception獲取異常資訊作為輸出。

       上邊提到過當服務端丟擲一個dubbo可以處理傳遞的異常時,會直接在客戶端上再次丟擲,但不是所有的異常都是dubbo可以處理傳遞的,如下邊的程式碼:

@Override
	public Person getByNick(String name) {
		if (name == null) {
			// 第三方異常
			throw new PersistenceException("mybatis插入異常");
		}
		name = name.trim();
		Person person = new Person();
		person.setId(ID.getAndIncrement());
		person.setName(name);
		return person;
	}

        這裡我們模擬丟擲了一個mybatis的異常,在客戶端呼叫會像上邊的結果一樣嗎?答案是否定的,看下輸出結果:

Exception in thread "main" java.lang.RuntimeException: org.apache.ibatis.exceptions.PersistenceException: mybatis插入異常
org.apache.ibatis.exceptions.PersistenceException: mybatis插入異常
	at org.dubbo.provider.PersonServiceImpl.getByNick(PersonServiceImpl.java:32)
	at com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)
       不要感覺奇怪,這個也是在可以接受的範圍內,因為PersistenceException異常類在客戶端是不存在的,所以不可能接收到PersistenceException異常,dubbo把他進行了封裝。

        針對這點,在介面包中裡面定義了一個全域性的異常類,注意一定是介面所在的工程中,如:UicException(使用者模組異常),這種方案也是官方建議的,服務端程式碼如下:

@Override
	public Person getByNick(String name) {
		if (name == null) {
			// 自定義異常
			throw new UicException("mybatis插入異常", 0);
		}
		name = name.trim();
		Person person = new Person();
		person.setId(ID.getAndIncrement());
		person.setName(name);
		return person;
	}

           錯誤資訊如下

Exception in thread "main" org.dubbo.api.exception.UicException: mybatis插入異常
	at org.dubbo.provider.PersonServiceImpl.getByNick(PersonServiceImpl.java:30)
         這個正是我們想要的異常資訊,上邊特別提到異常一定要在介面所在的工程中,如果異常類不在介面工程中,而是在另一個服務端和客戶端都引入的包中呢?我們曾經碰到這樣一個情況,有一個common的異常類放在一個很底層的工具包內,介面工程引入了這個包,在服務端丟擲的異常都都是這個commonexception,一廂情願的認為客戶端會正常去捕獲處理commonexception。

         但結果很意外,客戶端出現的異常跟上邊丟擲的PersistenceException情況一樣,dubbo用RuntimException進行了包裝,我們無法從異常中獲取有效的資訊!遇到這種情況有點發懵,這個異常類在客戶端和服務端都有呀,為啥不能正確接收呢。還好之前看dubbo原始碼的時候大概記得異常處理的位置,很好找到了目的碼:

 if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = result.getException();

                    // 如果是checked異常,直接丟擲
                    if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
                        return result;
                    }
                    // 在方法簽名上有宣告,直接丟擲
                    try {
                        Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                        Class<?>[] exceptionClassses = method.getExceptionTypes();
                        for (Class<?> exceptionClass : exceptionClassses) {
                            if (exception.getClass().equals(exceptionClass)) {
                                return result;
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        return result;
                    }

                    // 未在方法簽名上定義的異常,在伺服器端列印ERROR日誌
                    logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                            + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

                    // 異常類和介面類在同一jar包裡,直接丟擲
                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
                        return result;
                    }
                    // 是JDK自帶的異常,直接丟擲
                    String className = exception.getClass().getName();
                    if (className.startsWith("java.") || className.startsWith("javax.")) {
                        return result;
                    }
                    // 是Dubbo本身的異常,直接丟擲
                    if (exception instanceof RpcException) {
                        return result;
                    }

                    // 否則,包裝成RuntimeException拋給客戶端
                    return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
                } catch (Throwable e) {
                    logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
                            + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
                    return result;
                }
            }

          看完原始碼以後,做出了新的設計,CommonException不變,各個介面模組(maven工程為單位)單獨定義異常物件繼承CommonException,每個模組丟擲自己的模組異常(如使用者模組丟擲UicException),客戶端中用CommonException統一捕獲處理。

          這裡還要定義兩個攔截器,首先是服務端,保證所有丟擲的異常是當前模組的異常,程式碼如下:

public class UICExceptionAspect {
	private static Log log = LogFactory.getLog(UICExceptionAspect.class);

	public void afterThrowing(Exception ex) {

		if (ex instanceof UICException) {
			throw (UICException) ex;
		} else if (ex instanceof DayimaException) {
			DayimaException de = (DayimaException) ex;
			throw new UICException(de.getErrCode(), de.getErrMsg());
		} else {
			log.error(ex.getMessage(), ex);
			throw new UICException("1", ex.getMessage());
		}

	}
}

         其次是客戶端的,保證異常可以正確的友好的輸出,所有CommonException可以直接輸出(獲取根據錯誤碼獲取錯誤資訊),非CommonException異常根據自己需要去處理,如果是dubbo自帶異常肯定要遮蔽異常資訊,如列印日誌後輸出“網路異常”。

       還有另一種dubbo呼叫方案,普通service層外邊巢狀一層用來做dubbo的服務,普通service層處理了事務之類,dubbo服務層每一個方法都是客戶端要引用的,直接呼叫普通service層方法,但做了手動的try catch處理,封裝自己的返回碼,客戶端只需要根據返回碼去做處理,這種開發成本和文件成本有點高,沒太深入去考慮。

        以上是我自己工作中的dubbo異常實踐,以後會繼續寫些其他的心得,記錄自己的成長。

1、圖片大小不能超過2M

2、支援格式:.jpg .gif .png .bmp

中間水印 右下水印 無水印