I'm trying to create a custom annotation for enabling functionality based on feature flags. I'm basing my approach on the ConditionalOnProperty annotation, but to enable code reuse I want to define a common prefix that can be used wherever I need this functionality.
I have defined my annotation as below.
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Documented
#ConditionalOnProperty(prefix = "foo.bar")
public #interface ConditionalOnFeature {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "value")
String[] value();
}
I'm using the AliasFor annotation to set the value attribute in the ConditionalOnProperty annotation, and hardcoding my prefix as foo.bar (all my features will sit under this property). As far as I understand Spring annotation overriding, this should work.
However when I use this on a class (for example a controller), the code fails to compile, stating that The name or value attribute of #ConditionalOnProperty must be specified.
#RestController
#ConditionalOnFeature("enable-fancy-controller")
public class FancyController {
#RequestMapping(value = "/fancy-method", method = RequestMethod.GET)
public String fancyMethod() {
return "Fancy";
}
}
However it works perfectly fine when I instead use my annotation on a method. Compiles successfully and behaves as intended.
#RestController
public class FancierController {
#ConditionalOnFeature("enable-fancier-method")
#RequestMapping(value = "/fancier-method", method = RequestMethod.GET)
public String fancierMethod() {
return "Fancier";
}
}
Have I configured my annotation wrong? Or is this some bug with Spring?
Related
I am new to Java annotation. I have used the following annotation in my Spring boot application as follows:
Original annotation definition:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
ActionType actionType();
Resource resourceType();
}
Now I would like to move actionType() and resourceType() to a different annotation say MySubAnnotation and use it in the original above annotation MyAnnotation as follows:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation();
}
But I am facing an issue with using this as follows:
#MyAnnotation(entityType = EntityType.MY_ENTITY,
mySubAnnotation = <???>) <---HERE I CANNOT UNDERSTAND WHAT TO SPECIFY
#MySubAnnotation(actionType=ActionType.UPDATE,
resourceType=Resource.MY_RESOURCE)
public void myMethod() {
...
}
As mentioned above, I cannot understand what to specify for sub annotation. Could anyone please help here? Thanks.
You didn’t include the declaration of your MySubAnnotation. Besides that, the syntax for the actual annotation values is not different for nested annotations. You just have to place it after the =:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation();
}
#Retention(RetentionPolicy.RUNTIME)
#Target({})
public #interface MySubAnnotation {
ActionType actionType();
Resource resourceType();
}
#MyAnnotation(
entityType = EntityType.MY_ENTITY,
mySubAnnotation = #MySubAnnotation(
actionType = ActionType.UPDATE,
resourceType = Resource.MY_RESOURCE
)
)
public void myMethod() {
}
Note that in this example, MySubAnnotation has an empty list of targets, i.e. #Target({}) which permits it only as a value within other annotations. Of course, you could add other permitted targets. That would not affect its use as “sub annotation”, as that’s always allowed.
But there’s not much advantage in designing annotation parts as sub annotation here. All you’ve achieved, is requiring more typing. One imaginable possibility, is to provided a default here, e.g.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
EntityType entityType();
MySubAnnotation mySubAnnotation() default
#MySubAnnotation(actionType=ActionType.UPDATE, resourceType=Resource.MY_RESOURCE);
}
The difference to just specifying defaults for actionType and resourceType is that now, the developer may use the default for MySubAnnotation, i.e. both values, or has to specify explicit values for both, they can not override only one of them.
I'm trying to define constraint definition with Hibernate Validation 6.0.1 where the validator is in a different location (.jar/project) relative to the constraint annotation. Aka, I have my objects that I want to validate that are in the project "api" with the annotation definition, but I'll have the validators in the project "modules/common"
I was following what was describes in the documentation.
Configuration file
#Bean
public Validator validator() {
HibernateValidatorConfiguration configuration = Validation
.byProvider( HibernateValidator.class )
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.constraintDefinition(ValidationComplexePerson.class)
.validatedBy(ValidationComplexePersonValidator.class);
return configuration.addMapping( constraintMapping )
.buildValidatorFactory()
.getValidator();
Constraint Annotation
#Documented
#Constraint(validatedBy = { })
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
public #interface ValidationComplexePerson {
...}
Validator
public class ValidationComplexePersonValidator
implements ConstraintValidator<ValidationComplexePerson, Personne> {
#Override
public void initialize(ValidationComplexePerson constraintAnnotation) {
}
#Override public boolean isValid(
Personne personne,
ConstraintValidatorContext constraintValidatorContext) {
if (personne.nom.matches(".*\\d+.*")) {
return false;
}
return true;
}
My problem
The problem I have is that if I don't put the "#Constraint(validatedby={})" in the Annotation, I get the error
HV000116: The annotation type must be annotated with #javax.validation.Constraint when creating a constraint definition.
when reaching the ".constraintDefinition" in the Bean config.
On the other hand, if I put the "#Constraint(validatedby={})", I get
Error:(17, 1) java: For non-composed constraints a validator
implementation must be specified using #Constraint#validatedBy().
Any suggestion on what could be worng or alternatives to this solution?
I also tried the procedure presented here.
I suspect you are using the annotation processor as your second error comes from it?
The issue is that the annotation processor check is not correct in this case. I think we should probably remove it as there's no way to make this check work with the programmatic API.
Just remove the annotation processor for now and it should work OK.
I opened https://hibernate.atlassian.net/browse/HV-1470 to track this issue.
Let us define a annotation as below:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CheckSomething {
String validIdentity() default "";
String validUserGroup() default "google";
}
I have some set of annotations which is possible in my applicable and I would like to use them.
For example:
2 annotations below:
HumanCheckSomething, RobotCheckSomething - calling these for identification
#CheckSomething(validIdentity = "IAMSTRING", validUserGroup = "HUMANS")
#CheckSomething(validIdentify = "123", validUserGroup = "ROBOTS")
Now, I would like to create an enum using above annotations - HumanCheckSomething, RobotCheckSomething and reuse in my code, to annotate some methods.
#MyEnum.HumanCheckSomething
void allowOnlyHumans(){}
#MyEnum.RobotCheckSomething
void allowAll(){}
instead of
#CheckSomething(validIdentity = "IAMSTRING", validUserGroup = "HUMANS")
void allowOnlyHumans(){}
#CheckSomething(validIdentify = "123", validUserGroup = "ROBOTS")
void allowAll(){}
No, that's not possible.
But many frameworks use "meta-annotations" to solve this problem: you define your own annotation (CheckHumanfor example), which is itself annotated with the original annotation (#CheckSomething(validIdentity = "IAMSTRING", validUserGroup = "HUMANS") for example). Then, when you annotate some class (or field, or method, or whatever) with #CheckHuman, the framework does the same thing as if it was annotated directly with the original annotation.
See the Spring's GetMapping annotation, for example, which is a meta-annotation for #RequestMapping(method=GET).
Another advantage of this pattern is that it allows combining several annotations in one. For example #RestController is a meta-annotation which combines #Controller and #ResponseBody.
Hej,
I want to use the #Validated(group=Foo.class) annotation to validate an argument before executing a method like following:
public void doFoo(Foo #Validated(groups=Foo.class) foo){}
When i put this method in the Controller of my Spring application, the #Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.
Can't you use the #Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?
Update:
I have added the following two beans to my service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
and replaced the #Validate with #Null like so:
public void doFoo(Foo #Null(groups=Foo.class) foo){}
I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the #Null annotation and not the #Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?
In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for #Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.
The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.
With MethodValidationPostProcessor, take a look at the javadoc
Applicable methods have JSR-303 constraint annotations on their
parameters and/or on their return value (in the latter case specified
at the method level, typically as inline annotation).
Validation groups can be specified through Spring's Validated
annotation at the type level of the containing target class, applying
to all public service methods of that class. By default, JSR-303 will
validate against its default group only.
The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like #Null or #Valid. Remember that you can use as many annotations as you would like on a method parameter.
As a side note on Spring Validation for methods:
Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.
This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:
#Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
#Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
#Validated
public void callMeOutside(#Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
#Validated
public void callMeInside(#Valid AnotherForm form) {
// stuff
}
}
Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.
#pgiecek You don't need to create a new Annotation. You can use:
#Validated
public class MyClass {
#Validated({Group1.class})
public myMethod1(#Valid Foo foo) { ... }
#Validated({Group2.class})
public myMethod2(#Valid Foo foo) { ... }
...
}
Be careful with rubensa's approach.
This only works when you declare #Valid as the only annotation. When you combine it with other annotations like #NotNull everything except the #Valid will be ignored.
The following will not work and the #NotNull will be ignored:
#Validated
public class MyClass {
#Validated(Group1.class)
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated(Group2.class)
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
In combination with other annotations you need to declare the javax.validation.groups.Default Group as well, like this:
#Validated
public class MyClass {
#Validated({ Default.class, Group1.class })
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated({ Default.class, Group2.class })
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
As stated above to specify validation groups is possible only through #Validated annotation at class level. However, it is not very convenient since sometimes you have a class containing several methods with the same entity as a parameter but each of which requiring different subset of properties to validate. It was also my case and below you can find several steps to take to solve it.
1) Implement custom annotation that enables to specify validation groups at method level in addition to groups specified through #Validated at class level.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extend MethodValidationInterceptor and override determineValidationGroups method as follows.
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implement your own MethodValidationPostProcessor (just copy the Spring one) and in the method afterPropertiesSet use validation interceptor implemented in step 2.
#Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Register your validation post processor instead of Spring one.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
That's it. Now you can use it as follows.
#Validated(groups = Group1.class)
public class MyClass {
#ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}
I am using spring boot (1.3.4.RELEASE) and have a question regarding the new #AliasFor annotation introduced spring framework in 4.2
Consider the following annotations:
View
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Component
public #interface View {
String name() default "view";
}
Composite
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#View
public #interface Composite {
#AliasFor(annotation = View.class, attribute = "name")
String value() default "composite";
}
We then annotate a simple class as follows
#Composite(value = "model")
public class Model {
}
When running the following code
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
String[] beanNames = context.getBeanNamesForAnnotation(View.class);
for (String beanName : beanNames) {
View annotationOnBean = context.findAnnotationOnBean(beanName, View.class);
System.out.println(annotationOnBean.name());
}
I am expecting the output to be model, but it's view.
From my understanding, shouldn't #AliasFor (among other things) allow you to override attributes from meta-annotations (in this case #View)?
Can someone explain to me what am I doing wrong?
Thank you
Take a look at the documentation for #AliasFor, and you will see this quite in the requirements for using the annotation:
Like with any annotation in Java, the mere presence of #AliasFor on its own will not enforce alias semantics.
So, trying to extract the #View annotation from your bean is not going to work as expected. This annotation does exist on the bean class, but its attributes were not explicitly set, so they cannot be retrieved in the traditional way. Spring offers a couple utility classes for working with meta-annotations, such as these. In this case, the best option is to use AnnotatedElementUtils:
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
String[] beanNames = context.getBeanNamesForAnnotation(View.class);
for (String beanName : beanNames) {
Object bean = context.getBean(beanName);
View annotationOnBean = AnnotatedElementUtils.findMergedAnnotation(bean, View.class);
System.out.println(annotationOnBean.name());
}