dubbo重试机制配置,dubbo接口调试

  dubbo重试机制配置,dubbo接口调试

  一.背景二。解决方案2.1maven依赖2.2接口定义2.3Dubbo服务提供者配置2.4Dubbo服务消费者配置2.5验证参数验证3、自定义Dubbo参数检查异常返回3.1 ValidationFilterjValidator 3.2自定义参数检查异常返回3.2.1自定义过滤器3.2.2自定义过滤器的配置3.3.3Dubbo服务配置四如何扩展check comment 4.1定义check comment 4.2配置自定义验证器discovery V,本文总结分享了如何对Dubbo服务进行优雅的参数验证,从而实现服务器统一的数据返回格式,同时在一定程度上提高开发效率,避免重复简单的参数验证逻辑。

  00-1010服务器对外提供接口服务时,无论是向前端提供HTTP接口,还是向其他内部服务器提供RPC接口,都经常面临这样一个问题,那就是如何优雅地解决检查各种接口参数的问题?

  早期人们做HTTP接口做前端的时候,参数的验证可能都要经历这几个阶段:为每个接口和每个参数编写自定义验证代码,提炼通用验证逻辑,使用自定义节进行验证,使用通用标准验证逻辑。

  这里所说的通用标准的验证逻辑是指基于JSR303的Java Bean验证,其中官方指定的实现是Hibernate Validator。在Web项目中结合Spring可以使参数验证变得优雅。

  本文主要是想向大家介绍在使用Dubbo时,如何做优雅的参数验证。

  

目录

Dubbo框架本身支持参数验证,也是基于JSR303实现的。我们来看看是怎么实现的。

 

  

一、背景

!-在facade接口模块的pom文件中定义找到那个-依赖关系groupIdjavax.validation/groupId工件验证-API/工件ID version2.0.1.Final/version!-如果不想facade包有多余的依赖关系,这里作用域设置为provided,否则可以删除-scope provided/scope/dependency!-以下依赖项通常被添加到门面接口实现模块的pom文件中-依赖项groupIdorg.hibernate.validator/groupId工件Hibernate-验证器/工件ID version6.2.0.Final/version/dependency

 

  

二、解决方案

立面接口定义:

 

  公共接口user facade { FacadeResultBoolean update user(UpdateUserParam param);}参数定义

  公共类UpdateUserParam实现Serializable { private static final long serialVersionUID=2476922055212727973 l;@NotNull(message=“用户ID不能为空”)私有长ID;@NotBlank(message=“用户名不能为空”)私有字符串名称;@NotBlank(message=用户手机号码不能为空)@Size(min=8,max=16,message=电话号码长度在8到16位之间)私串电话;//getter和setter忽略}公共返回定义

  /** * Facade接口统一返回结果*/public类FacadeResult实现Serializable { private static final long serialversionUID=8570359747128577687 l;私有int代码;

   private T data; private String msg; // getter and setter ignored}

 

  

2.3 Dubbo服务提供者端配置

Dubbo服务提供者端必须作这个validation="true"的配置,具体示例配置如下:

 

  Dubbo接口服务端配置

  

<bean class="com.xxx.demo.UserFacadeImpl" id="userFacade"/><dubbo:service interface="com.xxx.demo.UserFacade" ref="userFacade" validation="true" />

 

  

2.4 Dubbo服务消费者端配置

这个根据业务方使用习惯不作强制要求,但建议配置上都加上validation="true",示例配置如下:

 

  

<dubbo:reference id="userFacade" interface="com.xxx.demo.UserFacade" validation="true" />

 

  

2.5 验证参数校验

前面几步完成以后,验证这一步就比较简单了,消费者调用该约定接口,接口入参传入UpdateUserParam对象,其中字段不用赋值,然后调用服务端接口就会得到如下的参数异常提示:

 

  Dubbo接口服务端配置

  

javax.validation.ValidationException: Failed to validate service: com.xxx.demo.UserFacade, method: updateUser, cause: [ConstraintViolationImpl{interpolatedMessage=用户名不能为空, propertyPath=name, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate=用户名不能为空}, ConstraintViolationImpl{interpolatedMessage=用户手机号不能为空, propertyPath=phone, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate=用户手机号不能为空}, ConstraintViolationImpl{interpolatedMessage=用户标识不能为空, propertyPath=id, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate=用户标识不能为空}]javax.validation.ValidationException: Failed to validate service: com.xxx.demo.UserFacade, method: updateUser, cause: [ConstraintViolationImpl{interpolatedMessage=用户名不能为空, propertyPath=name, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate=用户名不能为空}, ConstraintViolationImpl{interpolatedMessage=用户手机号不能为空, propertyPath=phone, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate=用户手机号不能为空}, ConstraintViolationImpl{interpolatedMessage=用户标识不能为空, propertyPath=id, rootBeanClass=class com.xxx.demo.UpdateUserParam, messageTemplate=用户标识不能为空}] at org.apache.dubbo.validation.filter.ValidationFilter.invoke(ValidationFilter.java:96) at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:83) .... at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:175) at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51) at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)

 

  

三、定制Dubbo参数校验异常返回

从前面内容我们可以很轻松的验证,当消费端调用Dubbo服务时,参数如果不合法就会抛出相关异常信息,消费端调用时也能识别出异常信息,似乎这样就没有问题了。

 

  但从前面所定义的服务接口来看,一般业务开发会定义统一的返回对象格式(如前文示例中的FacadeResult),对于业务异常情况,会约定相关异常码并结合相关性信息提示。因此对于参数校验不合法的情况,服务调用方自然不希望服务端抛出一大段包含堆栈信息的异常信息,而是希望还保持这种统一的返回形式,就如下面这种返回所示:

  Dubbo接口服务端配置:

  

{ "code": 1001, "msg": "用户名不能为空", "data": null}

 

  

3.1 ValidationFilter & JValidator

想要做到返回格式的统一,我们先来看下前面所抛出的异常是如何来的?

 

  从异常堆栈内容我们可以看出这个异常信息返回是由ValidationFilter抛出的,从名字我们可以猜到这个是采用Dubbo的Filter扩展机制的一个内置实现,当我们对Dubbo服务接口启用参数校验时(即前文Dubbo服务配置中的validation="true"),该Filter就会真正起作用,我们来看下其中的关键实现逻辑:

  

@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if (validation != null && !invocation.getMethodName().startsWith("$") && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) { try { Validator validator = validation.getValidator(invoker.getUrl()); if (validator != null) { // 注1 validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()); } } catch (RpcException e) { throw e; } catch (ValidationException e) { // 注2 return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation); } catch (Throwable t) { return AsyncRpcResult.newDefaultAsyncResult(t, invocation); } } return invoker.invoke(invocation);}

从前文的异常堆栈信息我们可以知道异常信息是由上述代码「注2」处所产生,这边是因为捕获了ValidationException,通过走读代码或者调试可以得知,该异常是由「注1」处valiator.validate方法所产生。

 

  而Validator接口在Dubbo框架中实现只有JValidator,这个通过idea工具显示Validator所有实现的UML类图可以看出(如下图所示),当然调试代码也可以很轻松定位到。

  

 

  既然定位到JValidator了,我们就继续看下它里面validate方法的具体实现,关键代码如下所示:

  

@Overridepublic void validate(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception { List<Class<?>> groups = new ArrayList<>(); Class<?> methodClass = methodClass(methodName); if (methodClass != null) { groups.add(methodClass); } Set<ConstraintViolation<?>> violations = new HashSet<>(); Method method = clazz.getMethod(methodName, parameterTypes); Class<?>[] methodClasses; if (method.isAnnotationPresent(MethodValidated.class)){ methodClasses = method.getAnnotation(MethodValidated.class).value(); groups.addAll(Arrays.asList(methodClasses)); } groups.add(0, Default.class); groups.add(1, clazz); Class<?>[] classgroups = groups.toArray(new Class[groups.size()]); Object parameterBean = getMethodParameterBean(clazz, method, arguments); if (parameterBean != null) { // 注1 violations.addAll(validator.validate(parameterBean, classgroups )); } for (Object arg : arguments) { // 注2 validate(violations, arg, classgroups); } if (!violations.isEmpty()) { // 注3 logger.error("Failed to validate service: " + clazz.getName() + ", method: " + methodName + ", cause: " + violations); throw new ConstraintViolationException("Failed to validate service: " + clazz.getName() + ", method: " + methodName + ", cause: " + violations, violations); }}

从上述代码中可以看出当「注1」和注「2」两处代码进行参数校验时所得到的「违反约束」的信息都被加入到violations集合中,而在「注3」处检查到「违反约束」不为空时,就会抛出包含「违反约束」信息的ConstraintViolationException,该异常继承自ValidationException,这样也就会被ValidationFilter中方法所捕获,进而向调用方返回相关异常信息。

 

  

 

  

3.2 自定义参数校验异常返回

从前一小节我们可以很清晰的了解到了为什么会抛出那样的异常信息给调用方,如果想做到我们前面想要的诉求:统一返回格式,我们需要按照下面的步骤去实现。

 

  

 

  

3.2.1 自定义Filter

@Activate(group = {CONSUMER, PROVIDER}, value = "customValidationFilter", order = 10000)public class CustomValidationFilter implements Filter { private Validation validation; public void setValidation(Validation validation) { this.validation = validation; } public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if (validation != null && !invocation.getMethodName().startsWith("$") && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) { try { Validator validator = validation.getValidator(invoker.getUrl()); if (validator != null) { validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()); } } catch (RpcException e) { throw e; } catch (ConstraintViolationException e) {// 这边细化了异常类型 // 注1 Set<ConstraintViolation<?>> violations = e.getConstraintViolations(); if (CollectionUtils.isNotEmpty(violations)) { ConstraintViolation<?> violation = violations.iterator().next();// 取第一个进行提示就行了 FacadeResult facadeResult = FacadeResult.fail(ErrorCode.INVALID_PARAM.getCode(), violation.getMessage()); return AsyncRpcResult.newDefaultAsyncResult(facadeResult, invocation); } return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation); } catch (Throwable t) { return AsyncRpcResult.newDefaultAsyncResult(t, invocation); } } return invoker.invoke(invocation); }}

该自定义filter与内置的ValidationFilter唯一不同的地方就在于「注1」处所新增的针对特定异常ConstraintViolationException的处理,从异常对象中获取包含的「违反约束」信息,并取其中第一个来构造业务上所定义的通用数据格式FacadeResult对象,作为Dubbo服务接口调用返回的信息。

 

  

 

  

3.2.2 自定义Filter的配置

开发过Dubbo自定义filter的同学都知道,要让它生效需要作一个符合SPI规范的配置,如下所示:

 

  

 

  a. 新建两级目录分别是META-INF和dubbo,这个需要特别注意,不能直接新建一个目录名为「META-INFO.dubbo」,否则在初始化启动的时候会失败。

  b. 新建一个文件名为com.alibaba.dubbo.rpc.Filter,当然也可以是org.apache.dubbo.rpc.Filter,Dubbo开源到Apache社区后,默认支持这两个名字。

  c. 文件中配置内容为:customValidationFilter=com.xxx.demo.dubbo.filter.CustomValidationFilter。

  

 

  

3.3.3 Dubbo服务配置

有了自定义参数校验的Filter配置后,如果只做到这的话,其实还有一个问题,应用启动后会有两个参数校验Filter生效。当然可以通过指定Filter的order来实现自定义Filter先执行,但很显然这种方式不稳妥,而且两个Filter的功能是重复的,因此只需要一个生效就可以了,Dubbo提供了一种机制可以禁用指定的Filter,只需在Dubbo配置文件中作如下配置即可:

 

  

<!-- 需要禁用的filter以"-"开头并加上filter名称 --><!-- 查看源码,可看到需要禁用的ValidationFilter名为validation--><dubbo:provider filter="-validation"/>

但经过上述配置后,发现customValidationFilter并没有生效,经过调试以及对dubbo相关文档的学习,对Filter生效机制有了一定的了解。

 

  a. dubbo启动后,默认会生效框架自带的一系列Filter;

  可以在dubbo框架的资源文件org.apache.dubbo.rpc.Filter中看到具体有哪些,不同版本的内容可能会有些许差别。

  

cache=org.apache.dubbo.cache.filter.CacheFiltervalidation=org.apache.dubbo.validation.filter.ValidationFilter // 注1echo=org.apache.dubbo.rpc.filter.EchoFiltergeneric=org.apache.dubbo.rpc.filter.GenericFiltergenericimpl=org.apache.dubbo.rpc.filter.GenericImplFiltertoken=org.apache.dubbo.rpc.filter.TokenFilteraccesslog=org.apache.dubbo.rpc.filter.AccessLogFilteractivelimit=org.apache.dubbo.rpc.filter.ActiveLimitFilterclassloader=org.apache.dubbo.rpc.filter.ClassLoaderFiltercontext=org.apache.dubbo.rpc.filter.ContextFilterconsumercontext=org.apache.dubbo.rpc.filter.ConsumerContextFilterexception=org.apache.dubbo.rpc.filter.ExceptionFilterexecutelimit=org.apache.dubbo.rpc.filter.ExecuteLimitFilterdeprecated=org.apache.dubbo.rpc.filter.DeprecatedFiltercompatible=org.apache.dubbo.rpc.filter.CompatibleFiltertimeout=org.apache.dubbo.rpc.filter.TimeoutFiltertps=org.apache.dubbo.rpc.filter.TpsLimitFiltertrace=org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilterfuture=org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFiltermonitor=org.apache.dubbo.monitor.support.MonitorFiltermetrics=org.apache.dubbo.monitor.dubbo.MetricsFilter

如上「注1」中的Filter就是我们上一步配置中想要禁用的Filter,因为这些filter都是Dubbo内置的,所以这些filter集合有一个统一的名字,default,因此如果想全部禁用,除了一个一个禁用外,也可以直接用'-default'达到目的,这些默认内置的filter只要没有全部或单独禁用,那就会生效。

 

  b. 想要开发的自定义Filter能生效,不并一定要在<dubbo:provider filter="xxxFitler" >中体现;如果我们没有在Dubbo相关的配置文件中去配置Filter相关信息,只要写好自定义filter代码,并在资源文件/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter中按照spi规范定义好即可,这样所有被加载的Filter都会生效。

  c. 如果在Dubbo配置文件中配置了Filter信息,那自定义Filter只有显式配置才会生效。

  d. Filter配置也可以加在dubbo service配置中(<dubbo:service interface="..." ref="..." validation="true" filter="xFilter,yFilter"/>)。

  当dubbo配置文件中provider 和service部分都配置了Filter信息,针对service具体生效的Filter取两者配置的并集。

  因此想要自定义的校验Filter在所有服务中都生效,需要作如下配置:

  

<dubbo:provider filter="-validation, customValidationFilter"/>

 

  

四、如何扩展校验注解

前面示例中都是利用参数校验的内置注解去完成,在实际开发中有时候会遇到默认内置的注解无法满足校验需求,这时就需要自定义一些校验注解去满足需求,方便开发。

 

  假设有这样一个场景,某参数值需要校验只能在指定的几个数值范围内,类似于白名单一样,下面就以这个场景来演示下如何扩展校验注解。

  

 

  

4.1 定义校验注解

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })@Retention(RUNTIME)@Documented@Constraint(validatedBy = { })// 注1// @Constraint(validatedBy = {AllowedValueValidator.class}) 注2public @interface AllowedValue { String message() default "参数值不在合法范围内"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; long[] value() default {}; }
public class AllowedValueValidator implements ConstraintValidator<AllowedValue, Long> { private long[] allowedValues; @Override public void initialize(AllowedValue constraintAnnotation) { this.allowedValues = constraintAnnotation.value(); } public boolean isValid(Long value, ConstraintValidatorContext context) { if (allowedValues.length == 0) { return true; } return Arrays.stream(allowedValues).anyMatch(o -> Objects.equals(o, value));}

「注1」中的校验器(Validator)并没有指定,当然是可以像「注2」中那样直接指定校验器,但考虑到自定义注解有可能是直接暴露在facade包中,而具体的校验器的实现有时候会包含一些业务依赖,所以不建议直接在此处指定,而是通过Hibernate Validator提供的Validator发现机制去完成关联。

 

  

 

  

4.2 配置定制Validator发现

 

  a. 在resources目录下新建META-INF/services/javax.validation.ConstraintValidator文件。

  b. 文件中只需填入相应Validator的全路径:com.xxx.demo.validator.AllowedValueValidator,如果有多个的话,每行一个。

  

 

  

五、总结

本文主要介绍了使用Dubbo框架时如何使用优雅点方式完成参数的校验,首先演示了如何利用Dubbo框架默认支持的校验实现,然后接着演示了如何配合实际业务开发返回统一的数据格式,最后介绍了下如何进行自定义校验注解的实现,方便进行后续自行扩展实现,希望能在实际工作中有一定的帮助。

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: