本文主要研究一下dubbo的ValidationFilter
dubbo-2.7.2/dubbo-filter/dubbo-filter-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 if validation 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) { return AsyncRpcResult.newDefaultAsyncResult(t, invocation); } } return invoker.invoke(invocation); } }
$
开头的,那么就会从validation获取validator,然后进行校验 dubbo-2.7.2/dubbo-filter/dubbo-filter-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); }
dubbo-2.7.2/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/AbstractValidation.java
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); }
dubbo-2.7.2/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidation.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) { return new JValidator(url); } }
dubbo-2.7.2/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/Validator.java
public interface Validator { void validate(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception; }
dubbo-2.7.2/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.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); } } //...... }
dubbo-2.7.2/dubbo-filter/dubbo-filter-validation/src/test/java/org/apache/dubbo/validation/filter/ValidationFilterTest.java
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 void setUp() 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); }); } }
$