Dubbo调用拦截及参数校检扩展

微服务框架(十一)

Posted by Tillend on August 29, 2018

  此系列文章将会描述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,内容为:

1
global=com.test.filter.GlobalExceptionFilter

使用配置

Dubbo 配置模块中,扩展点均有对应配置属性或标签,通过配置指定使用哪个扩展实现。比如:

1
<dubbo:provider filter="global" />

1
dubbo.provider.filter = global

拓展项目路径

1
2
3
4
5
6
7
8
9
10
11
12
project
|-- pom.xml
`-- src
    `-- main
        |-- resources
        |    `-- META-INF
        |        `-- dubbo
        |           `-- com.alibaba.dubbo.rpc.Filter
        |-- java
        	 `-- com.test.filter
        	 	 `-- GlobalExceptionFilter.java

调用拦截拓展

Filter SPI

拦截面

1
2
3
// before filter	
Result result = invoker.invoke(invocation);	
// after filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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接口

1
public class GlobalExceptionFilter<T> implements Filter {}

GlobalExceptionFilter

逻辑为依层级捕捉消化异常,转换为相应的响应码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

参数校检拓展

校检器

根据校检器工厂创建校检器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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

1
2
3
4
5
6
7
8
9
10
11
12
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));
}

参考资料:

  1. Dubbo SPI
  2. 调用拦截扩展
  3. 验证拓展