1. 程式人生 > >微服務框架(十一)Dubbo呼叫攔截及引數校檢擴充套件

微服務框架(十一)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));
}

參考資料: