Java Spring #Valid on a method call [duplicate] - java

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) { ... }
...
}

Related

How do I prevent Spring Boot AOP from removing type annotations?

I am pretty new to Spring Boot and its flavor of AOP, but not new to programming in other languages and AOP frameworks. This one challenge I am not sure how to solve.
I have a simple metadata decorator:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface GreetingsMeta {
public float version() default 0;
public String name() default "";
}
It works just fine with dependency injection:
public GreetingController(List<IGreetingService> greetings) throws Exception {
this.greetings = new HashMap<>();
greetings.forEach(m -> {
Class<?> clazz = m.getClass();
if (clazz.isAnnotationPresent(GreetingsMeta.class)) {
GreetingsMeta[] s = clazz.getAnnotationsByType(GreetingsMeta.class);
this.greetings.put(s[0].name(), m);
}
});
}
Until I applied a standard logging aspect:
#Aspect
#Component
public class LoggingAspect {
#Around("execution(* com.firm..*(..)))")
public Object profileAllMethods(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getName();
final StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
LogManager.getLogger(methodSignature.getDeclaringType())
.info(methodName + " " + (stopWatch.getTotalTimeSeconds() * 1000) + " µs");
return result;
}
}
Then the list of annotationsData becomes empty, even the #Component annotation is gone.
Sample meta-decorated class:
#Component
#GreetingsMeta(name = "Default", version = 1.0f)
public class DefaultGreetingsService implements IGreetingService {
#Override
public String message(String content) {
return "Hello, " + content;
}
}
How should I troubleshoot?
How do I prevent Spring Boot AOP from removing type annotations?
Spring Boot does not remove anything, but for Spring AOP is uses dynamic proxies generated during runtime, i.e. subclasses or interface implementations with event hooks (joinpoints) for aspect advice code wired in via pointcuts. By default, annotations are not inherited, so this is just a JVM feature.
There is one exception for subclasses inheriting annotations from parent classes: You can add the meta annotation #Inherited to your own annotation class GreetingsMeta. The effect will be that if you annotate any class with it, all subclasses (also dynamic proxies created by Spring AOP) will inherit the annotation and your original code should run as expected.
So in this case there is no need to use AnnotationUtils as suggested by JC Carrillo. His approach works too, of course. It is just more complicated because AnnotationUtils uses a lot of reflection magic and lots of helper classes internally in order to compute results. Thus, I would only use AnnotationUtils in cases where you don't directly annotate a class but e.g. methods or interfaces because #Inherited has no effect on them as documented. Or if you rely on a hierarchy of Spring (or own) meta annotations (annotations on annotations) and you need to get information from them all merged into one, AnnotationUtils or MergedAnnotations are appropriate.
You may want to look into AnnotationUtils
Method method = methodSignature.getMethod();
GreetingsMeta greetingsMeta = AnnotationUtils.findAnnotation(method, GreetingsMeta.class);

Spring Managed Custom Validator not being used from endpoint

I've been at this for a while, but I have a Spring managed custom validator that looks like the below, I have some print statements in there which I'll get to later
#Component
public class BulkUpdateValidator implements ConstraintValidator<ValidBulkUpdate, BulkUpdate> {
#Autowired
ObjectMapper mapper;
public BulkUpdateValidator(){
System.out.println(this.toString());
}
#PostConstruct
public void post(){
System.out.println(mapper);
System.out.println(this.toString());
}
public boolean isValid(BulkUpdate update, ConstraintValidatorContext context){
System.out.println(this.toString());
System.out.println(mapper);
}
... other validator methods ...
}
My controller method: (NOTE: my controller class is annotated with #Validated at the top)
#RequestMapping(...)
public #ResponseBody RestResponse bulkUpdate(#Valid #ValidBulkUpdate Bulkupdate bulkUpdate){
... stuff here ...
}
My Bean:
public class BulkUpdate {
#NotEmpty
public List<String> recordIds;
#NotEmpty
#Valid
public List<FieldUpdate> updates;
.... getters and setters ....
}
Here's my problem, when I execute the endpoint it get a NullPointerException when I attempt to use the autowired mapper. The output from the print statements I posted above are quite telling. In both the constructor and the #PostConstruct sections I get the same Object ID for the validator and I also get an ID for the mapper. However, once isValid is called, it prints out a different Object ID. I know the spring managed validator is being created, but it's not being used.
Furthermore, I've tried to remove the #ValidBulkUpdate annotation from the REST endpoint and put it inside a wrapper object, thinking that maybe #Valid was necessary to get spring to take over, like below:
public #ResponseBody RestResponse bulkUpdate(#Valid BulkupdateWrapper bulkUpdate){
... stuff here ...
}
And wrapper
public class BulkUpdateWrapper {
#ValidBulkUpdate
private BulkUpdate update;
.... getter and setter ....
}
This leaves me with a whole new error which is even weirder:
"JSR-303 validated property 'update.org.hibernate.validator.internal.engine.ConstraintViolationImpl' does not have a corresponding accessor"
I'm not sure where to turn, hopefully someone has an idea. Either how to get it to use the Spring managed validator, or how to remove that vague error when I use the object wrapper;
What's worse, is I have MockMvc based Integration tests for this that run flawlessly, this only happens when I deploy it.
UPDATE
So I kept my wrapper and changed #Valid to #Validated and now my error is the following: "NotReadablePropertyException: Bean property 'update.field' does not have a corresponding accessor for Spring data binding"
Fun fact, there is no property called "field"

Spring - Validation of custom constraints

I have a custom annotation #UniqueModel, which is validated by a ConstraintValidator:
#Component
public class UniquePlaceValidator implements ConstraintValidator<UniqueModel, Model> {
#Autowired
private ModelRepository repository;
public UniqueModelValidator() {
}
public void initialize(UniqueModel constraint) {
}
#Override
public boolean isValid(Model model, ConstraintValidatorContext context) {
if (repository == null)
return true;
Model dbModel = repository.findByNameAndMail(model.getName(), model.getMail());
return dbModel == null;
}
The problem is, that I need to do the validation before the safe()-method of the repository is called, otherwise the field injection won't work.
I therefor created a delegate-method with a #Valid-annotation, in order to force the unique-validation before:
Model save(#Valid Model model {
return repository.save(model);
}
Unfortunately this doesn't work, it seems like the #Valid-annotation is ignored by Spring.
How can I assure the correct timing of validation?
Depending on your Bean validation configuration you may need to annotate your repository bean with #ValidateOnExecution.
But I'm not sure if Spring does support this annoation (see SPR-10641) hence I'm using Spring's own #Validated annotation in my repository and service interfaces and method level validation works fine!
See also this question and have a look into MethodValidationPostProcessor which clearly states "Target classes with such annotated methods need to be annotated with Spring's #Validated annotation at the type level". So it seems to be pretty clear that you have to use #Validated instead of #ValidateOnExecution until SPR-10641 is fixed.

Guice custom binding annotation fails, but #Named works?

I'd thought that a custom binding annotation #Foo was equivalent in functionality to #Named("foo"). Is this not true?
I've got, in separate GuiceModules, two things providing the same item type:
In GuiceModuleFoo
#Foo
#Provides
public String provideFoo() { return "foo"; }
In GuiceModuleBar
#Bar
#Provides
public String provideBar() { return "bar"; }
class MyOtherThing extends Thing {
#Inject
public MyOtherThing(#Foo String s) {
super(s);
}
}
Further, in case it matters, I'm using Modules.override so that GuiceModuleBar overrides GuiceModuleFoo (for an unrelated provide).
MyOtherThing is not getting injected with the #Foo provided value though. It's getting the value from the #Bar provider. It actually seems somewhat random what provides it when I added a 3rd provider #Baz.
However, when converting everything to use #Named (e.g. #Named("foo")), it works exactly as desired!
So...am I misunderstanding how binding annotations work? The custom annotations looked like this:
#BindingAnnotation
#Target({ FIELD, PARAMETER, METHOD }) #Retention(RUNTIME)
public #interface Foo {
}

How to auto-generate docs for classes annotated spring jmx annotations

I have some code that uses these spring annotations:
org.springframework.jmx.export.annotation.ManagedAttribute;
org.springframework.jmx.export.annotation.ManagedOperation;
org.springframework.jmx.export.annotation.ManagedOperationParameter;
org.springframework.jmx.export.annotation.ManagedOperationParameters;
org.springframework.jmx.export.annotation.ManagedResource;
I want to generate some documentation (even just javadocs) using the comments in the annotations, for example consider the following method?
#ManagedOperation(description="Does foo to bar")
#ManagedOperationParameters({
#ManagedOperationParameter(name = "bar", description = "The bar you want to foo.")})
public long fooBar( Bar bar) throws Exception {
...
}
Is there some way I can automatically generate docs for this, or will I have to duplicate all the annotation strings in javadoc in addition to it?
First, create a custom AnnotationMbeanExporter with a public method that delegates to getRegisteredObjectNames(). Use this as your mbeanExporter.
For example:
#Component
// This is a copy of the AnnotationMBeanExporter with a public version of getRegisteredObjectNames()
public class AnnotationMBeanExporter extends MBeanExporter {
#Autowired
MBeanServer mbeanServer;
AnnotationJmxAttributeSource annotationSource = new AnnotationJmxAttributeSource();
AnnotationMBeanExporter() {
setServer(mbeanServer);
setNamingStrategy(new MetadataNamingStrategy(annotationSource));
setAssembler(new MetadataMBeanInfoAssembler(annotationSource));
setAutodetectMode(MBeanExporter.AUTODETECT_ALL);
}
public ObjectName[] getExportedObjectNames() {
return getRegisteredObjectNames();
}
}
Then for your report, iterate over the object names returned from getExportedObjectNames() and get the relevant metadata for each JMX bean.
For example:
for (ObjectName objectName: mbeanExporter.getExportedObjectNames()) {
MBeanInfo mbeanInfo = mbeanServer.getMBeanInfo(objectName);
MBeanOperationInfo[] operations = mbeanInfo.getOperations();
// etc.
}

Categories