sequence

This paper mainly studies Dubbo’s ValidationFilter

ValidationFilter

Dubbo – 2.7.2 / dubbo – filter/dubbo – filter – the validation/SRC/main/Java/org/apache/dubbo/validation/filter/ValidationFilter. Java

@Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 10000)
public class ValidationFilter implements Filter {

    private Validation validation;

    /**
     * Sets the validation instance for ValidationFilter
     * @param validation Validation instance injected by dubbo framework based on "validation" attribute value.
     */
    public void setValidation(Validation validation) { this.validation = validation; } /** * Perform the validation of before invoking the actual method based on <b>validation</b> attribute value. * @param  invoker service * @param invocation invocation. * @return Method invocation result
     * @throws RpcException Throws RpcException ifvalidation failed or any other runtime exception occurred. */ @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 (Throwable t) {returnAsyncRpcResult.newDefaultAsyncResult(t, invocation); }}returninvoker.invoke(invocation); }}Copy the code
  • ValidationFilter implements the Filter interface, and its invoke method contains Validation in the method parameter of the URL whose Validation is not null and whose method name is not$Validation gets the Validator from Validation and validates it

Validation

Dubbo – 2.7.2 / dubbo – filter/dubbo – filter – the validation/SRC/main/Java/org/apache/dubbo/validation/validation. Java

@SPI("jvalidation")
public interface Validation {

    /**
     * Return the instance of {@link Validator} for a given url.
     * @param url Invocation url
     * @return Instance of {@link Validator}
     */
    @Adaptive(VALIDATION_KEY)
    Validator getValidator(URL url);

}
Copy the code
  • The Validation interface defines the getValidator method

AbstractValidation

Dubbo – 2.7.2 / dubbo – filter/dubbo – filter – the validation/SRC/main/Java/org/apache/dubbo/validation/support/AbstractValidation. Ja va

public abstract class AbstractValidation implements Validation {

    private final ConcurrentMap<String, Validator> validators = new ConcurrentHashMap<>();

    @Override
    public Validator getValidator(URL url) {
        String key = url.toFullString();
        Validator validator = validators.get(key);
        if (validator == null) {
            validators.put(key, createValidator(url));
            validator = validators.get(key);
        }
        return validator;
    }

    protected abstract Validator createValidator(URL url);

}
Copy the code
  • AbstractValidation implements the Validation interface, which defines a Validators of type ConcurrentHashMap, implements the getValidator method, and defines the createValidator abstract method for subclasses to implement

JValidation

Dubbo – 2.7.2 / dubbo – filter/dubbo – filter – the validation/SRC/main/Java/org/apache/dubbo/validation/support/jvalidation/JValidati on.java

public class JValidation extends AbstractValidation { /** * Return new instance of {@link JValidator} * @param url Valid  URL instance * @return Instance of JValidator
     */
    @Override
    protected Validator createValidator(URL url) {
        returnnew JValidator(url); }}Copy the code
  • JValidation descends from AbstractValidation, and its createValidator creates a JValidator

Validator

Dubbo – 2.7.2 / dubbo – filter/dubbo – filter – the validation/SRC/main/Java/org/apache/dubbo/validation/Validator. Java

public interface Validator { void validate(String methodName, Class<? >[] parameterTypes, Object[] arguments) throws Exception; }Copy the code
  • The Validator interface defines the validate method

JValidator

Dubbo – 2.7.2 / dubbo – filter/dubbo – filter – the validation/SRC/main/Java/org/apache/dubbo/validation/support/jvalidation/JValidato r.java

public class JValidator implements Validator { private static final Logger logger = LoggerFactory.getLogger(JValidator.class); private final Class<? > clazz; private final Map<String, Class> methodClassMap; private final javax.validation.Validator validator; @SuppressWarnings({"unchecked"."rawtypes"})
    public JValidator(URL url) {
        this.clazz = ReflectUtils.forName(url.getServiceInterface());
        String jvalidation = url.getParameter("jvalidation");
        ValidatorFactory factory;
        if(jvalidation ! = null && jvalidation.length() > 0) { factory = Validation.byProvider((Class) ReflectUtils.forName(jvalidation)).configure().buildValidatorFactory(); }else{ factory = Validation.buildDefaultValidatorFactory(); } this.validator = factory.getValidator(); this.methodClassMap = new ConcurrentHashMap<>(); } / /... @Override public 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)); } // add into default group groups.add(0, Default.class); groups.add(1, clazz); // convert list to array Class<? >[] classgroups = groups.toArray(new Class[groups.size()]); Object parameterBean = getMethodParameterBean(clazz, method, arguments);if(parameterBean ! = null) { violations.addAll(validator.validate(parameterBean, classgroups )); }for (Object arg : arguments) {
            validate(violations, arg, classgroups);
        }

        if(! violations.isEmpty()) { 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); }} / /... }Copy the code
  • JValidator implement the Validator interface, its use within the validate method is javax.mail validation. ValidatorFactory create javax.mail. Validation. The Validator

The instance

Dubbo – 2.7.2 / dubbo – filter/dubbo – filter – the validation/SRC/test/Java/org/apache/dubbo/validation/filter/ValidationFilterTest. J ava

public class ValidationFilterTest { private Invoker<? > invoker = mock(Invoker.class); private Validation validation = mock(Validation.class); private Validator validator = mock(Validator.class); private RpcInvocation invocation = mock(RpcInvocation.class); private ValidationFilter validationFilter; @BeforeEach public voidsetUp() throws Exception {
        this.validationFilter = new ValidationFilter();
    }

    @Test
    public void testItWithNotExistClass() throws Exception {
        URL url = URL.valueOf("test://test:11/test? default.validation=true");

        given(validation.getValidator(url)).willThrow(new IllegalStateException("Not found class test, cause: test"));
        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
        given(invoker.getUrl()).willReturn(url);
        given(invocation.getMethodName()).willReturn("echo1"); given(invocation.getParameterTypes()).willReturn(new Class<? >[]{String.class}); given(invocation.getArguments()).willReturn(new Object[]{"arg1"});

        validationFilter.setValidation(validation);
        Result result = validationFilter.invoke(invoker, invocation);

        assertThat(result.getException().getMessage(), is("Not found class test, cause: test"));

    }

    @Test
    public void testItWithExistClass() throws Exception {
        URL url = URL.valueOf("test://test:11/test? default.validation=true");

        given(validation.getValidator(url)).willReturn(validator);
        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
        given(invoker.getUrl()).willReturn(url);
        given(invocation.getMethodName()).willReturn("echo1"); given(invocation.getParameterTypes()).willReturn(new Class<? >[]{String.class}); given(invocation.getArguments()).willReturn(new Object[]{"arg1"});

        validationFilter.setValidation(validation);
        Result result = validationFilter.invoke(invoker, invocation);

        assertThat(String.valueOf(result.getValue()), is("success"));
    }

    @Test
    public void testItWithoutUrlParameters() throws Exception {
        URL url = URL.valueOf("test://test:11/test");

        given(validation.getValidator(url)).willReturn(validator);
        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
        given(invoker.getUrl()).willReturn(url);
        given(invocation.getMethodName()).willReturn("echo1"); given(invocation.getParameterTypes()).willReturn(new Class<? >[]{String.class}); given(invocation.getArguments()).willReturn(new Object[]{"arg1"});

        validationFilter.setValidation(validation);
        Result result = validationFilter.invoke(invoker, invocation);

        assertThat(String.valueOf(result.getValue()), is("success"));
    }

    @Test
    public void testItWhileMethodNameStartWithDollar() throws Exception {
        URL url = URL.valueOf("test://test:11/test");

        given(validation.getValidator(url)).willReturn(validator);
        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
        given(invoker.getUrl()).willReturn(url);
        given(invocation.getMethodName()).willReturn("$echo1"); given(invocation.getParameterTypes()).willReturn(new Class<? >[]{String.class}); given(invocation.getArguments()).willReturn(new Object[]{"arg1"});

        validationFilter.setValidation(validation);
        Result result = validationFilter.invoke(invoker, invocation);

        assertThat(String.valueOf(result.getValue()), is("success"));

    }


    @Test
    public void testItWhileThrowoutRpcException() throws Exception {
        Assertions.assertThrows(RpcException.class, () -> {
            URL url = URL.valueOf("test://test:11/test? default.validation=true");

            given(validation.getValidator(url)).willThrow(new RpcException("rpc exception"));
            given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
            given(invoker.getUrl()).willReturn(url);
            given(invocation.getMethodName()).willReturn("echo1"); given(invocation.getParameterTypes()).willReturn(new Class<? >[]{String.class}); given(invocation.getArguments()).willReturn(new Object[]{"arg1"}); validationFilter.setValidation(validation); validationFilter.invoke(invoker, invocation); }); }}Copy the code
  • Here we validate the withNotExistClass, withExistClass, withoutUrlParameters, methodNameStartWithDollar, throwoutRpcException these scenarios

summary

  • ValidationFilter implements the Filter interface, and its invoke method contains Validation in the method parameter of the URL whose Validation is not null and whose method name is not$Validation gets the Validator from Validation and validates it
  • The Validation interface defines the getValidator method; JValidation descends from AbstractValidation, and its createValidator creates a JValidator
  • JValidator implement the Validator interface, its use within the validate method is javax.mail validation. ValidatorFactory create javax.mail. Validation. The Validator

doc

  • ValidationFilter