Elegant Parameter Validation in Dubbo Services
The article shows how to integrate JSR‑303 bean validation into Dubbo services by adding the necessary Maven dependencies, annotating parameter classes, enabling validation on provider and consumer, customizing the validation filter to return a unified FacadeResult, disabling the default filter, and extending validation with custom annotations for clean, maintainable RPC parameter checks.
When a server provides interface services, whether HTTP interfaces for the front‑end or RPC interfaces for other services, developers often face the problem of how to perform parameter validation in an elegant way.
Early solutions for HTTP interfaces typically evolve through custom validation code per parameter, extracting common logic, using custom aspects, and finally adopting a standard validation framework based on JSR‑303. The standard solution is Java Bean Validation (JSR‑303) with the reference implementation Hibernate Validator, which can be integrated with Spring for clean validation.
This article demonstrates how to achieve elegant parameter validation when using the Dubbo RPC framework.
Solution Overview
Dubbo natively supports parameter validation based on JSR‑303. The following sections show the required Maven dependencies, interface definitions, configuration, and how to customize validation results.
2.1 Maven Dependencies
provided
org.hibernate.validator
hibernate-validator
6.2.0.Final2.2 Interface Definition
Facade interface:
public interface UserFacade {
FacadeResult<Boolean> updateUser(UpdateUserParam param);
}Parameter class:
public class UpdateUserParam implements Serializable {
private static final long serialVersionUID = 2476922055212727973L;
@NotNull(message = "用户标识不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
private String name;
@NotBlank(message = "用户手机号不能为空")
@Size(min = 8, max = 16, message = "电话号码长度介于8~16位")
private String phone;
// getter and setter ignored
}Common return wrapper:
/**
* Facade接口统一返回结果
*/
public class FacadeResult
implements Serializable {
private static final long serialVersionUID = 8570359747128577687L;
private int code;
private T data;
private String msg;
// getter and setter ignored
}2.3 Dubbo Provider Configuration
The provider must enable validation by setting validation="true" :
<bean class="com.xxx.demo.UserFacadeImpl" id="userFacade"/>
<dubbo:service interface="com.xxx.demo.UserFacade" ref="userFacade" validation="true"/>2.4 Dubbo Consumer Configuration
It is recommended (though not mandatory) to also enable validation on the consumer side:
<dubbo:reference id="userFacade" interface="com.xxx.demo.UserFacade" validation="true"/>2.5 Validation Result
When the consumer calls the service with an invalid UpdateUserParam , Dubbo throws a javax.validation.ValidationException containing the constraint violations:
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)
...3 Customizing Dubbo Validation Exception Return
The default ValidationFilter catches the ValidationException and returns it directly, which results in a stack trace being sent to the caller. To return a unified business response, a custom filter can be implemented.
3.1 ValidationFilter & JValidator
@Override
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 (ValidationException e) {
// Annotation 2: wrap the exception as a result
return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation);
} catch (Throwable t) {
return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
}
}
return invoker.invoke(invocation);
}3.2 Custom Validation 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) {
Set
> 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);
}
}The custom filter differs from the built‑in one by handling ConstraintViolationException and converting the first violation into a unified FacadeResult .
3.2.2 SPI Configuration
To make the filter effective, create the SPI file META-INF/dubbo/com.alibaba.dubbo.rpc.Filter (or org.apache.dubbo.rpc.Filter ) with the line:
customValidationFilter=com.xxx.demo.dubbo.filter.CustomValidationFilter3.3 Disabling the Default ValidationFilter
If both the default and custom filters are active, you can disable the default one via provider configuration:
<dubbo:provider filter="-validation, customValidationFilter"/>Dubbo’s built‑in filter list (excerpt) includes:
cache=org.apache.dubbo.cache.filter.CacheFilter
validation=org.apache.dubbo.validation.filter.ValidationFilter
echo=org.apache.dubbo.rpc.filter.EchoFilter
... (other filters omitted for brevity)Using the -validation prefix removes the built‑in validation filter, leaving only the custom implementation.
4 Extending Validation Annotations
When built‑in constraints are insufficient, you can define a custom annotation and validator.
4.1 Annotation definition:
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { }) // Annotation 1
public @interface AllowedValue {
String message() default "参数值不在合法范围内";
Class
[] groups() default { };
Class
[] payload() default { };
long[] value() default {};
}Validator implementation:
public class AllowedValueValidator implements ConstraintValidator
{
private long[] allowedValues;
@Override
public void initialize(AllowedValue constraintAnnotation) {
this.allowedValues = constraintAnnotation.value();
}
@Override
public boolean isValid(Long value, ConstraintValidatorContext context) {
if (allowedValues.length == 0) {
return true;
}
return Arrays.stream(allowedValues).anyMatch(o -> Objects.equals(o, value));
}
}4.2 Registering the validator:
Create META-INF/services/javax.validation.ConstraintValidator and list the fully qualified name of the validator, e.g., com.xxx.demo.validator.AllowedValueValidator .
Conclusion
The article introduced how to use Dubbo’s built‑in JSR‑303 validation, how to customize the validation response to keep a unified business return format, and how to extend validation with custom annotations. These techniques help developers implement clean and maintainable parameter validation in Dubbo‑based micro‑services.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.