Java Spring : how can I encapsulate annotations within an other annotation? - java

Is it possible to encapsulate an annotation within an other annotation with values?
I have a lot of end points on my API and would like to automate the required roles / permissions for the end point access.
Here is the current code :
#RestController
#RequestMapping("/health")
public class HealthController {
#GetMapping("/isAlive")
#PreAuthorize("hasAnyAuthority('ISALIVE', 'HEALTH_ENDPOINT')")
public String isAlive() {
return "Server is up and running";
}
#GetMapping("/hello")
#PreAuthorize("hasAnyAuthority('HELLO', 'HEALTH_ENDPOINT')")
public String hello() {
return "Hello";
}
}
I have 2 authorities per end point, the first is the name of the mapping and method and the second is the name of the class.
In this example there is only 2 different end points with makes it easy but the finished product will have in the hundreds and doing all of the authorities by hand is going to be error-prone and not very efficient.
This is the desired result :
#RestController
#RequestMapping("/health")
public class HealthController {
#GetMapping("/isAlive")
#MyAuthorisationAnnotation
public String isAlive() {
return "Server is up and running";
}
#GetMapping("/hello")
#MyAuthorisationAnnotation
public String hello() {
return "Hello";
}
}
Where #MyAuthorisationAnnotation would give the right parameters to the #PreAuthorize annotation.
Is it possible?

Is it possible to encapsulate an annotation within an other annotation
with values?
An annotation can have another annotation as a member.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAuthorizationAnnotation {
PreAuthorize value() default #PreAuthorize("hasAnyAuthority('HELLO', 'HEALTH_ENDPOINT')");
}
However I don't think this helps. I am not familiar with Spring, but I think this post solves your problem, because it seems that #PreAuthorize can be applied to annotations so you can leverage transitivity if you use the checked annotation in a method.
Solution's Post

Related

Spring injection with Java's Optional

When trying to inject an Optional<T>, Spring never calls my bean, and instead injects an Optional.empty().
Here is some sample code:
#Configuration
public class Initialize {
#Value("optionalValue")
private String testString;
#Bean (name = "getOptionalString")
public Optional<String> getOptionalString() {
return Optional.of(this.testString); //breakpoint put here, is never called
}
}
#Component
public class Test {
public Test(#Qualifier("getOptionalString") Optional<String> optional) {
// optional's value is Optional.empty() here
}
I noticed that (by putting a breakpoint) the #Bean is never called. If I were to remove the Optional<String>, and simply return a String, then it works!
I know Spring has its own optional dependency but I am perplexed as to why this doesn't work (whatever I read online says it should), and I also don't understand how it initialized it to Optional.empty()?
The documentation says:
you can express the non-required nature of a particular dependency through Java 8’s java.util.Optional, as the following example shows:
public class SimpleMovieLister {
#Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
So using Optional as the type of a bean is not a good idea.

Java Spring AOP: Can I ignore Xlint:invalidAbsoluteTypeName error which caused by pointcuts to classes which I don't use?

I have an issue concerning a generic component and one (of a dozen) application(s). My component has point cuts to many annotations, which could be used within classes and methods in my apps. When all annotations are present on the classpath, everything works fine. But not in all my apps I have these dependencies. The quick fix is, of course, add them, but that gives my app a lot of code which I don't need in that app. I'm searching for a way to ignore the Xlint:invalidAbsoluteTypeName error as stated here: Xlint:invalidAbsoluteTypeName
So what I have:
I have many apps with Soap/JMS connections, and all are annotated with the #Annotation org.springframework.ws.server.endpoint.annotation.Endpoint.
I have my pointcut in my generic component (jar):
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
And the result is:
All apps having the Spring WS dependency along with my generic component have no issues
Apps which don't have the annotation, cannot start due to java.lang.IllegalArgumentException: warning no match for this type name: org.springframework.ws.server.endpoint.annotation.Endpoint [Xlint:invalidAbsoluteTypeName] (which is obvious, see the link)
So the problem looks like Xlint:invalidAbsoluteTypeName BUT I don't want to add Spring dependencies which I'm not using. I just want this AOP pointcut ignored. Other workarounds like splitting up the pointcuts to different jars imho give too much overhead. Is there any way to have Spring AOP just ignore this pointcut, or e.g. set the pointcut to st like if-exists(class)?
To show why I think separating is causing way too much overhead have a look at my aspect structure:
#Aspect
public class PerformanceLoggingAspect {
private LogWriter logWriter;
#Inject
public PerformanceLoggingAspect(LogWriter logWriter) {
this.logWriter = logWriter;
}
#Around("within(#org.springframework.web.bind.annotation.RestController *)")
public Object withinARestController(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.REST);
}
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
public Object withinAnEndpoint(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.BERICHT);
}
#Around("within(#javax.inject.Named *)")
public Object withinAService(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.SERVICE);
}
private Object proceedWithLogging(ProceedingJoinPoint pjp, String metingType) throws Throwable {
(... Working code (performance logging) if the annotation is on the classpath...)
}
}
Update: I tried creating a #NeedsClass("any.package.Class") which is a #Conditional annotation from spring-context. The condition class is a ClasspathCondition which checked if the classloader could load that given class. But the error occurs before the condition gets evaluated so I'm afraid this is a dead end. But if you're curious:
The #NeedsClass annotation I tried
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#Conditional(ClasspathCondition.class)
public #interface NeedsClass {
String[] value();
}
The Condition implementation. I had logging here, which never got written
public class ClasspathCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
String[] classes = (String[]) metadata.getAnnotationAttributes(NeedsClass.class.getName()).get("classes");
for (String clazz : classes) {
ClassUtils.resolveClassName(clazz, context.getClassLoader());
}
return true;
} catch (Throwable t) { /* noOp() */}
return false;
}
}
For now I have a workaround:
I created a superclass with the method:
protected Object proceedWithLogging(ProceedingJoinPoint pjp, String metingType) throws Throwable {
(... code which adds performance logging ...)
}
I created 4 subclasses with each the #Aspect annotation, and 1 method calling the super. For example this one targets JMS:
#Aspect
public class JmsPerformanceLogger extends PerformanceLoggingAspect {
#Inject
private LogWriter logWriter;
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
public Object withinAnEndpoint(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.BERICHT);
}
}
As a downside I have to configure all different beans which I need within my app, and I cannot add one simple configuration file as shown below, with all beans preconfigured:
#Configuration
public class PerformanceloggingConfig {
#Bean
public LogWriter performanceLogWriter(){
return new DefaultLogWriter();
}
#Bean
public JmsPerformanceLogger jmsPerformanceLogger(){
return new JmsPerformanceLogger();
}
#Bean
public RestPerformanceLogger restPerformanceLogger(){
return new RestPerformanceLogger();
}
#Bean
public ServicesPerformanceLogger servicesPerformanceLogger(){
return new ServicesPerformanceLogger();
}
#Bean
public DaoPerformanceLogger daoPerformanceLogger(){
return new DaoPerformanceLogger();
}
}
And therefore also not the handy annotation to autoconfig the class:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import(PerformanceloggingConfig.class)
public #interface EnablePerformanceLogging {
}
But for now adding these 4 beans when I need them, makes it possible to differentiate per app. But of course this is still a workaround, as I want to use #EnablePerformanceLogging and be done with it. If anyone has a better answer, pls tell me

Java Spring #Valid on a method call [duplicate]

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

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

Spring MVC: How to perform validation?

I would like to know what is the cleanest and best way to perform form validation of user inputs. I have seen some developers implement org.springframework.validation.Validator. A question about that: I saw it validates a class. Does the class have to be filled manually with the values from the user input, and then passed to the validator?
I am confused about the cleanest and best way to validate the user input. I know about the traditional method of using request.getParameter() and then manually checking for nulls, but I don't want to do all the validation in my Controller. Some good advice on this area will be greatly appreciated. I am not using Hibernate in this application.
With Spring MVC, there are 3 different ways to perform validation : using annotation, manually, or a mix of both. There is not a unique "cleanest and best way" to validate, but there is probably one that fits your project/problem/context better.
Let's have a User :
public class User {
private String name;
...
}
Method 1 : If you have Spring 3.x+ and simple validation to do, use javax.validation.constraints annotations (also known as JSR-303 annotations).
public class User {
#NotNull
private String name;
...
}
You will need a JSR-303 provider in your libraries, like Hibernate Validator who is the reference implementation (this library has nothing to do with databases and relational mapping, it just does validation :-).
Then in your controller you would have something like :
#RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, #Valid #ModelAttribute("user") User user, BindingResult result){
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}
Notice the #Valid : if the user happens to have a null name, result.hasErrors() will be true.
Method 2 : If you have complex validation (like big business validation logic, conditional validation across multiple fields, etc.), or for some reason you cannot use method 1, use manual validation. It is a good practice to separate the controller’s code from the validation logic. Don't create your validation class(es) from scratch, Spring provides a handy org.springframework.validation.Validator interface (since Spring 2).
So let's say you have
public class User {
private String name;
private Integer birthYear;
private User responsibleUser;
...
}
and you want to do some "complex" validation like : if the user's age is under 18, responsibleUser must not be null and responsibleUser's age must be over 21.
You will do something like this
public class UserValidator implements Validator {
#Override
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
User user = (User) target;
if(user.getName() == null) {
errors.rejectValue("name", "your_error_code");
}
// do "complex" validation here
}
}
Then in your controller you would have :
#RequestMapping(value="/user", method=RequestMethod.POST)
public createUser(Model model, #ModelAttribute("user") User user, BindingResult result){
UserValidator userValidator = new UserValidator();
userValidator.validate(user, result);
if (result.hasErrors()){
// do something
}
else {
// do something else
}
}
If there are validation errors, result.hasErrors() will be true.
Note : You can also set the validator in a #InitBinder method of the controller, with "binder.setValidator(...)" (in which case a mix use of method 1 and 2 would not be possible, because you replace the default validator). Or you could instantiate it in the default constructor of the controller. Or have a #Component/#Service UserValidator that you inject (#Autowired) in your controller : very useful, because most validators are singletons + unit test mocking becomes easier + your validator could call other Spring components.
Method 3 :
Why not using a combination of both methods? Validate the simple stuff, like the "name" attribute, with annotations (it is quick to do, concise and more readable). Keep the heavy validations for validators (when it would take hours to code custom complex validation annotations, or just when it is not possible to use annotations). I did this on a former project, it worked like a charm, quick & easy.
Warning : you must not mistake validation handling for exception handling. Read this post to know when to use them.
References :
A very interesting blog post about bean validation (Original link is dead)
Another good blog post about validation (Original link is dead)
Latest Spring documentation about validation
There are two ways to validate user input: annotations and by inheriting Spring's Validator class. For simple cases, the annotations are nice. If you need complex validations (like cross-field validation, eg. "verify email address" field), or if your model is validated in multiple places in your application with different rules, or if you don't have the ability to modify your model object by placing annotations on it, Spring's inheritance-based Validator is the way to go. I'll show examples of both.
The actual validation part is the same regardless of which type of validation you're using:
RequestMapping(value="fooPage", method = RequestMethod.POST)
public String processSubmit(#Valid #ModelAttribute("foo") Foo foo, BindingResult result, ModelMap m) {
if(result.hasErrors()) {
return "fooPage";
}
...
return "successPage";
}
If you are using annotations, your Foo class might look like:
public class Foo {
#NotNull
#Size(min = 1, max = 20)
private String name;
#NotNull
#Min(1)
#Max(110)
private Integer age;
// getters, setters
}
Annotations above are javax.validation.constraints annotations. You can also use Hibernate's
org.hibernate.validator.constraints, but it doesn't look like you are using Hibernate.
Alternatively, if you implement Spring's Validator, you would create a class as follows:
public class FooValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Foo.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Foo foo = (Foo) target;
if(foo.getName() == null) {
errors.rejectValue("name", "name[emptyMessage]");
}
else if(foo.getName().length() < 1 || foo.getName().length() > 20){
errors.rejectValue("name", "name[invalidLength]");
}
if(foo.getAge() == null) {
errors.rejectValue("age", "age[emptyMessage]");
}
else if(foo.getAge() < 1 || foo.getAge() > 110){
errors.rejectValue("age", "age[invalidAge]");
}
}
}
If using the above validator, you also have to bind the validator to the Spring controller (not necessary if using annotations):
#InitBinder("foo")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new FooValidator());
}
Also see Spring docs.
Hope that helps.
I would like to extend nice answer of Jerome Dalbert. I found very easy to write your own annotation validators in JSR-303 way. You are not limited to have "one field" validation. You can create your own annotation on type level and have complex validation (see examples below). I prefer this way because I don't need mix different types of validation (Spring and JSR-303) like Jerome do. Also this validators are "Spring aware" so you can use #Inject/#Autowire out of box.
Example of custom object validation:
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { YourCustomObjectValidator.class })
public #interface YourCustomObjectValid {
String message() default "{YourCustomObjectValid.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class YourCustomObjectValidator implements ConstraintValidator<YourCustomObjectValid, YourCustomObject> {
#Override
public void initialize(YourCustomObjectValid constraintAnnotation) { }
#Override
public boolean isValid(YourCustomObject value, ConstraintValidatorContext context) {
// Validate your complex logic
// Mark field with error
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation();
return true;
}
}
#YourCustomObjectValid
public YourCustomObject {
}
Example of generic fields equality:
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { FieldsEqualityValidator.class })
public #interface FieldsEquality {
String message() default "{FieldsEquality.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Name of the first field that will be compared.
*
* #return name
*/
String firstFieldName();
/**
* Name of the second field that will be compared.
*
* #return name
*/
String secondFieldName();
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
public #interface List {
FieldsEquality[] value();
}
}
import java.lang.reflect.Field;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
public class FieldsEqualityValidator implements ConstraintValidator<FieldsEquality, Object> {
private static final Logger log = LoggerFactory.getLogger(FieldsEqualityValidator.class);
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(FieldsEquality constraintAnnotation) {
firstFieldName = constraintAnnotation.firstFieldName();
secondFieldName = constraintAnnotation.secondFieldName();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null)
return true;
try {
Class<?> clazz = value.getClass();
Field firstField = ReflectionUtils.findField(clazz, firstFieldName);
firstField.setAccessible(true);
Object first = firstField.get(value);
Field secondField = ReflectionUtils.findField(clazz, secondFieldName);
secondField.setAccessible(true);
Object second = secondField.get(value);
if (first != null && second != null && !first.equals(second)) {
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(firstFieldName).addConstraintViolation();
ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate());
cvb.addNode(someField).addConstraintViolation(secondFieldName);
return false;
}
} catch (Exception e) {
log.error("Cannot validate fileds equality in '" + value + "'!", e);
return false;
}
return true;
}
}
#FieldsEquality(firstFieldName = "password", secondFieldName = "confirmPassword")
public class NewUserForm {
private String password;
private String confirmPassword;
}
If you have same error handling logic for different method handlers, then you would end up with lots of handlers with following code pattern:
if (validation.hasErrors()) {
// do error handling
}
else {
// do the actual business logic
}
Suppose you're creating RESTful services and want to return 400 Bad Request along with error messages for every validation error case. Then, the error handling part would be same for every single REST endpoint that requires validation. Repeating that very same logic in every single handler is not so DRYish!
One way to solve this problem is to drop the immediate BindingResult after each To-Be-Validated bean. Now, your handler would be like this:
#RequestMapping(...)
public Something doStuff(#Valid Somebean bean) {
// do the actual business logic
// Just the else part!
}
This way, if the bound bean was not valid, a MethodArgumentNotValidException will be thrown by Spring. You can define a ControllerAdvice that handles this exception with that same error handling logic:
#ControllerAdvice
public class ErrorHandlingControllerAdvice {
#ExceptionHandler(MethodArgumentNotValidException.class)
public SomeErrorBean handleValidationError(MethodArgumentNotValidException ex) {
// do error handling
// Just the if part!
}
}
You still can examine the underlying BindingResult using getBindingResult method of MethodArgumentNotValidException.
Find complete example of Spring Mvc Validation
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.technicalkeeda.bean.Login;
public class LoginValidator implements Validator {
public boolean supports(Class aClass) {
return Login.class.equals(aClass);
}
public void validate(Object obj, Errors errors) {
Login login = (Login) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
"username.required", "Required field");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userPassword",
"userpassword.required", "Required field");
}
}
public class LoginController extends SimpleFormController {
private LoginService loginService;
public LoginController() {
setCommandClass(Login.class);
setCommandName("login");
}
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
#Override
protected ModelAndView onSubmit(Object command) throws Exception {
Login login = (Login) command;
loginService.add(login);
return new ModelAndView("loginsucess", "login", login);
}
}
Put this bean in your configuration class.
#Bean
public Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
and then You can use
<T> BindingResult validate(T t) {
DataBinder binder = new DataBinder(t);
binder.setValidator(validator);
binder.validate();
return binder.getBindingResult();
}
for validating a bean manually. Then You will get all result in BindingResult and you can retrieve from there.
Validation groups
Also it is worth to mention validation for some more complex cases, when you have some "multi steps" within your business logic. In such cases we need "validation groups".
#Validated annotation was added to support "validation groups" in validated bean. This can be used in multi step forms where in the first step you need, for example, validate name and email, and in the second step you need to validate, for example, phone number.
With #Validated you first need to declare groups. Groups are declared with your custom marker interfaces.
#Validated example
Let's say we have a scenario when we have a form for user sign up. On this form we want user to provide a name and email. And after user is signed up we have another form where we suggest the user to add his some extra information, for example, email. We don't want email be provided on the first step. But it is required to provide it on the second step.
For this case, we'll declare two groups. First group would be OnCreate, and the second group would be OnUpdate :
OnCreate:
public interface OnCreate {}
OnUpdate:
public interface OnUpdate {}
Our user UserAccount class:
public class UserAccount {
// we will return this field after User is created
// and we want this field to be provided only on update
// so we can determine which user needs to be updated
#NotBlank(groups = OnUpdate.class)
private String id;
#NotBlank(groups = OnCreate.class)
private String name;
#NotBlank(groups = OnCreate.class)
private String email;
#NotBlank(groups = OnUpdate.class)
private String phone;
// standard constructors / setters / getters / toString
}
We mark the validation annotations with our groups interfaces depending on which group those validations are supposed to be related.
And finally our Controller methods:
#PostMapping(value = "/create")
public UserAccount createAccount(#Validated(OnCreate.class) #RequestBody UserAccount userAccount) {
...
}
#PatchMapping(value = "/update")
public UserAccount updateAccount(#Validated(OnUpdate.class) #RequestBody UserAccount userAccount) {
...
}
Here we specify #Validated(...) instead of #Valid and specify the validation group which should be used in different cases.
Now depending on validation group we'll perform the validations for the particular fields within different steps.

Categories