Привязать делегирование метода byte buddy только к методам с аннотированными параметрами

Я хочу украсить существующие объекты, чтобы вызовы методов проверялись автоматически. Мне уже удалось делегировать вызов метода перехватчику, который вызывает валидатор Hibernate, и пока он работает нормально:

public class HibernateBeanValidator implements BeanValidator{

    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

    @Override
    public <T> T addMethodValidation(T object) {
        ExecutableValidator executableValidator = factory.getValidator().forExecutables();

        Class<? extends T> dynamicType = (Class<? extends T>)new ByteBuddy()
                .subclass(object.getClass())
                .method(isPublic()).intercept(MethodDelegation.to(new ValidationInterceptor(object, executableValidator)).andThen(SuperMethodCall.INSTANCE))
                .make()
                .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();

        try {
            T validatedObject = dynamicType.newInstance();
            return  validatedObject;
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static class ValidationInterceptor {

        private final Object validatedObject;
        private final ExecutableValidator executableValidator;

        public <T> ValidationInterceptor(T object, ExecutableValidator executableValidator) {
            this.validatedObject = object;
            this.executableValidator = executableValidator;
        }

        public void validate(@Origin Method method, @AllArguments Object[] arguments)
                throws Exception {
            Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(validatedObject, method, arguments);
            if(! constraintViolations.isEmpty()) {
                throw new ValidationException(constraintViolations);
            }
        }
    }
}

Что я хотел бы улучшить, так это привязывать вызовы методов только к методам, у которых есть хотя бы один параметр, аннотированный аннотацией ограничения, например:

class Echo {
    public String repeat(@NotNull String word) { /* should bind validation here */
        return word;
    }

    public String notAnnotated(String word) { /* should not bind validation */
        return word;
    }
}

Как я могу указать ElementMatcher в Byte Buddy, чтобы он привязывался только к методам с параметрами, аннотированными аннотациями, которые аннотированы с помощью @Constraint, например @NotNull (взято из javax.validation.constraints):

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {

    String message() default "{javax.validation.constraints.NotNull.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    /**
     * Defines several {@link NotNull} annotations on the same element.
     *
     * @see javax.validation.constraints.NotNull
     */
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        NotNull[] value();
    }
}

person starko    schedule 29.04.2015    source источник


Ответы (2)


Ваша проблема может быть решена путем реализации пользовательского ElementMatcher, который используется для определения перехватываемых методов. В настоящее время вы используете предопределенный перехватчик isPublic(), который не учитывает аннотации, а только модификатор public метода. Поскольку предопределенные аннотации могут быть объединены в цепочку, вы можете создать подходящий сопоставитель следующим образом:

isPublic().and(hasParameter(hasAnnotation(nameStartsWith("javax."))))

Конечно, вы можете просто реализовать свои собственные сопоставители, не используя предустановленные.

person Rafael Winterhalter    schedule 30.04.2015

На самом деле, вместо того, чтобы просто проверять аннотацию из пространства имен javax.validation.constraints, вероятно, лучше использовать API метаданных Bean Validation. Ограничения не обязательно должны исходить из этого пространства имен, но также могут исходить из Hibernate Validator (org.hibernate.validator.constraints) или быть пользовательскими ограничениями. Возможная реализация ElementMatcher, использующая API метаданных, может выглядеть так:

общедоступный статический класс BeanValidationMatcher реализует ElementMatcher {

private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

@Override
public boolean matches(Object target) {
    // handle different descriptors and potentially use generic MethodDescription
    if ( !( target instanceof MethodDescription.ForLoadedMethod ) ) {
        return false;
    }
    MethodDescription.ForLoadedMethod methodDescription = (MethodDescription.ForLoadedMethod) target;
    Method method = methodDescription.getLoadedMethod();

    boolean isGetter = ReflectionHelper.isGetterMethod( method );

    boolean needsValidation;
    BeanDescriptor beanDescriptor = validator.getConstraintsForClass( method.getDeclaringClass() );
    if ( isGetter ) {
        needsValidation = isGetterConstrained( method, beanDescriptor );
    }
    else {
        needsValidation = isNonGetterConstrained( method, beanDescriptor );
    }

    return needsValidation;
}

private boolean isNonGetterConstrained(Method method, BeanDescriptor beanDescriptor) {
    return beanDescriptor.getConstraintsForMethod( method.getName(), method.getParameterTypes() ) != null;
}

private boolean isGetterConstrained(Method method, BeanDescriptor beanDescriptor) {
    String propertyName = ReflectionHelper.getPropertyName( method );
    PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty( propertyName );
    return propertyDescriptor != null && propertyDescriptor.findConstraints()
            .declaredOn( ElementType.METHOD )
            .hasConstraints();
}

}

person Hardy    schedule 08.06.2015