前言:
其實很早以前就想寫一篇關於oval和具體服務相整合的常見作法, 並以此做爲一篇筆記. 趁如今項目中間空閒期, 恰好對dubbo的filter有一些瞭解. 所以想結合二者, 寫一下既結合校驗框架, 又能少侵入性的編程模式.
Dubbo的filter機制, 其實對標springmvc的interceptor, netty的iohandler, 甚至servlet體系的filter, 都是責任鏈模式的實現方式. 本文着重dubbo的filter和oval校驗框架的組合, 其實徹底能夠借鑑/延伸過去, 所以本文有它的意義.html
常見的Dubbo接口約定:
如今流行的作法, 兩個服務之間的調用, 是不會把異常信息拋給對方, 而是內部把異常轉化爲特定的錯誤碼, 並告知調用方.
常見的響應類模式被設計爲泛型Result, 而真正返回的實體就是泛型對應的對象.java
@Getter @Setter @ToString public class TResult<T> { private boolean success = true; private int errCode = 0; private String errMsg = "OK"; private T value = null; }
當success爲true時, 泛型對象value爲真正的實體, 而success爲false時, 則errCode/errMsg會具體描述各種錯誤狀況, 好比參數不正確/狀態不正確.spring
常見的實現方法:
服務端的接口具體的實現, 每每是這樣的模樣:編程
@Getter @Setter class EchoReq { @NotNull(message = "message字段不能爲空") private String message; } public interface IEchoService { TResult<String> echo1(String name); TResult<String> echo2(EchoReq req); } // *) 須要設定@Guarded註解, 才能使函數參數的preconditions校驗機制生效 @Guarded @Service("echoService") public class EchoServiceImpl implements IEchoService { @Override public TResult<String> echo1(@NotNull(message="name字段不能爲空") String name) { TResult<String> result = new TResult<String>(); return result; } @Override public TResult<String> echo2(EchoReq req) { TResult<String> result = new TResult<String>(); try { // *) 參數校驗 Validator validator = new Validator(); List<ConstraintViolation> cvs = validator.validate(req); if ( cvs != null && cvs.size() > 0 ) { result.setSuccess(false); result.setErrCode(10001); result.setErrMsg("參數不正確:" + cvs.get(0).getMessage()); return result; } // *) 具體的業務代碼 } catch(Throwable e) { result.setSuccess(false); result.setErrCode(10002); result.setErrMsg("Internal Server Error:"); return result; } return result; } }
正如你所見的, 核心代碼外圍須要一個try/catch以支持異常到錯誤碼的轉換, 可否有個辦法去掉這個try/catch.
同時Oval註解不光做用於Bean類的屬性成員上, 還能夠在做用於函數的參數上, 這樣的話, try/catch就覆蓋不及了.mvc
來個題外話, 讓oval支持preconditions, 須要一些額外的工做, 默認不開啓. 可以使用spring-aop來開啓, 可具體參閱該文章.框架
相似這樣的配置:ide
<beans> <bean id="myService" class="MyServiceImpl" /> <bean id="ovalGuardInterceptor" class="net.sf.oval.guard.GuardInterceptor" /> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="proxyTargetClass" value="true" /> <property name="beanNames" value="*Service" /> <property name="interceptorNames"><list><value>ovalGuardInterceptor</value></list></property> </bean> </beans>
引入filter:
咱們引入OvalFilter, 具體代碼以下:函數
public class OvalFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { Result result = invoker.invoke(invocation); if ( result.hasException() ) { TResult<Void> res = new TResult<Void>(); Throwable e = result.getException(); if ( e instanceof ConstraintsViolatedException) { // *) 參數不正確 res.setSuccess(false); res.setErrCode(10001); res.setErrMsg(e.getMessage()); } else { // *) 服務端內部錯誤 res.setSuccess(false); res.setErrCode(10002); res.setErrMsg("Internal Server Error"); } return new RpcResult(res); } return result; } }
Dubbo的filter的引入, 以及配置, 能夠具體參閱文章:
1. Dubbo透傳traceId/logid的一種思路
2. Dubbo的Filter鏈梳理---分組可見和順序調整
測試的結果, 符合預期, OvalFilter成功地把Dubbo的service拋出的ConstraintsViolatedException捕獲, 併成功轉化爲參數校驗失敗的錯誤信息返回. 這樣的好處, 可讓dubbo具體的service實現類, 減小異常的處理, 使得代碼簡潔, 可讀性更強. 測試
以上面的樣例代碼爲例, 咱們能夠簡化以下:ui
@Guarded @Service("echoService") public class EchoServiceImpl implements IEchoService { @Override public TResult<String> echo1(@NotNull(message="name字段不能爲空") String name) { TResult<String> result = new TResult<String>(); return result; } @Override public TResult<String> echo2(EchoReq req) { TResult<String> result = new TResult<String>(); // *) 參數校驗 Validator validator = new Validator(); List<ConstraintViolation> cvs = validator.validate(req); if ( cvs != null && cvs.size() > 0 ) { throw new ConstraintsViolatedException(cvs); } // *) 具體的業務代碼 return result; } }
總結: Dubbo服務如何處理參數校驗這塊, 不一樣的人/公司, 都有本身的偏好. 本文講述了利用Oval框架來校驗參數, 同時利用dubbo強大的自定義filter機制, 把校驗參數異常隱藏, 並返回更友好的提示信息. 同時使服務的代碼更加簡潔, 可讀性更強. 在整理這塊時, 感受Oval的preconditions支持水更深, 但願本身有機會對這塊可以深刻研究下.