I have an instanceof an Java annotation which has a target of METHOD: is it possible to get the Method object that it's attached to?
I'm trying to do cross-parameter validation, where the validator will apply logic to the combination of parameters to determine if they are correct. However, since the validator can be applied to multiple methods, I'm looking for a way to flag which parameters are which.
For example, in the example below, I have methods that use two arguments to specify a range by a start and an end. I want a Validator to check that the start is not greater than the end. I want to use annotations on the parameters to indicate which parameter is the start and which is the end, and then have the validator use those annotations to determine which parameters to use in the validation.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface StartParam {}
/***************************/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface EndParam {}
/***************************/
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = RangeParamsValidator.class)
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public @interface RangeParams { }
/***************************/
public class RangeParamsValidator implements ConstraintValidator<RangeParams, Object[]> {
private int indexOfStartArg;
private int indexOfEndArg;
@Override
public void initialize(
RangeParams constraintAnnotation
) {
// This is where I'm hoping to get the method that `constraintAnnotation`
// is attached to, so I can iterate over its params and see which are
// annotated with @StartParam and @EndParam so I can set indexOfStartArg
// and indexOfEndArg;
}
@Override
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
Integer start = value[indexOfStartArg];
Integer end = value[indexOfEndArg];
return start <= end;
}
}
/***************************/
// Example use:
class Whatever {
@RangeParams
void doSomething(
String ignoreThisArg,
@StartParam int start,
@FinishParam int finish,
Object ignoreThisToo
) {
// ...
}
}
I haven't been able to figure this out using the parameter names, even though there might be a way. But here is a solution with the position of the parameters.
First you need to create properties in the Constraint so the position of the fields can be passed.
The
message
,groups
andpayload
are to conform to the standard (more info in the documentation). The important part are the 2 additional parameters to provide the index. If you set a default value then the parameters will be optional.Then in the Validator's initialize you get the values.
And you can use the values as you wanted.
Then to provide the values you do it in the annotation as follows:
I think it's possible to do it with the parameter names since in the
isValid()
the context has the list of parameter names. But I've only managed to access the values through the implementation of the interface instead of through the interface itself, so I would need to cast the context to the implementation which doesn't feel clean.