微服務框架(十一)Dubbo呼叫攔截及引數校檢擴充套件
此係列文章將會描述Java框架Spring Boot、服務治理框架Dubbo、應用容器引擎Docker,及使用Spring Boot整合Dubbo、Mybatis等開源框架,其中穿插著Spring Boot中日誌切面等技術的實現,然後通過gitlab-CI以持續整合為Docker映象。
使用Dubbo框架時,面對自身的業務場景,需根據定製的需求編寫SPI拓展實現,再根據配置來載入拓展點。本文為Dubbo呼叫攔截及引數校檢擴充套件
本系列文章中所使用的框架版本為Spring Boot 2.0.3-RELEASE,Spring 5.0.7-RELEASE,Dubbo 2.6.2。
Dubbo SPI拓展點
Dubbo 的擴充套件點載入從 JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來。
Dubbo改進了 JDK 標準的 SPI的一些問題,詳見拓展點載入
約定
在擴充套件類的 jar包內 ,放置擴充套件點配置檔案META-INF/dubbo/
介面全限定名,內容為:配置名=擴充套件實現類全限定名,多個實現類用換行符分隔。
示例
以擴充套件 Dubbo 的協議為例,在協議的實現 jar 包內放置文字檔案:META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
,內容為:
global=com .test.filter.GlobalExceptionFilter
使用配置
Dubbo 配置模組中,擴充套件點均有對應配置屬性或標籤,通過配置指定使用哪個擴充套件實現。比如:
XML: <dubbo:provider filter="global" />
properties: dubbo.provider.filter = global
拓展專案路徑
project
|-- pom.xml
`-- src
`-- main
|-- resources
| `-- META-INF
| ` -- dubbo
| `-- com.alibaba.dubbo.rpc.Filter
|-- java
`-- com.test.filter
`-- GlobalExceptionFilter.java
呼叫攔截拓展
呼叫攔截拓展關鍵程式碼:
Filter SPI
// before filter
Result result = invoker.invoke(invocation);
// after filter
@SPI
public interface Filter {
/**
* do invoke filter.
* <p>
* <code>
* // before filter
* Result result = invoker.invoke(invocation);
* // after filter
* return result;
* </code>
*
* @param invoker service
* @param invocation invocation.
* @return invoke result.
* @throws RpcException
* @see com.alibaba.dubbo.rpc.Invoker#invoke(Invocation)
*/
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
實現Filter介面
public class GlobalExceptionFilter<T> implements Filter {}
GlobalExceptionFilter
邏輯為依層級捕捉消化異常,轉換為相應的響應碼
try {
Result result = invoker.invoke(invocation);
try{
Throwable exception = result.getException();
} catch (Throwable e) {
// ExceptionFilter Warn
}
} catch(RuntimeException e){
// ConstraintViolationException Error
// RpcException Error
// Other RuntimeException Error
} catch (Exception e) {
// Exception Error
}
Validation Error
if (e.getCause() != null
&& e.getCause() instanceof ConstraintViolationException) {
ConstraintViolationException exs = (ConstraintViolationException) e
.getCause();
Set<ConstraintViolation<?>> violations = exs
.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
/** 獲取校檢首個失敗原因 */
errorMsg = item.getMessage();
break;
}
resp = Resp.createError(RespCode.PARAM_ERR,
"input.param.error", errorMsg);
}
引數校檢拓展
引數校檢拓展關鍵程式碼:
校檢器
根據校檢器工廠建立校檢器
@SuppressWarnings({ "unchecked", "rawtypes" })
public GlobalValidator(URL url) {
this.clazz = ReflectUtils.forName(url.getServiceInterface());
String globalValidator = url.getParameter("globalValidator");
ValidatorFactory factory;
if (globalValidator != null && globalValidator.length() > 0) {
factory = Validation
.byProvider((Class) ReflectUtils.forName(globalValidator))
.configure().buildValidatorFactory();
} else {
factory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
}
this.validator = factory.getValidator();
}
異常封裝
引數校檢錯誤時自定義錯誤資訊,封裝為RPCException
if (!violations.isEmpty()) {
logger.error("Failed to validate service: " + clazz.getName()
+ ", method: " + methodName + ", cause: " + violations);
String errorMsg = (violations.size() > 0) ? violations.iterator()
.next().getMessage() : "引數校檢錯誤";
throw new RpcException(errorMsg, new ConstraintViolationException(
"Failed to validate service: " + clazz.getName()
+ ", method: " + methodName + ", cause: "
+ violations, violations));
}
參考資料: