Jul 4, 2009

AspectJ Validation for Spring annotated controller

Spring 2.5.x introduces annotated controller that makes web development with Spring MVC a lot easier. No more XML is required for each controller you define.

Spring MVC's annotated controller reminds me of Ben Parker's famous quote: "With great power comes great responsibility". What's the great responsibility you will have for using the powerful annotated controller? The validation!!

With the old base-controller-classes approach you can just inject validators to your form controller and Spring MVC will take care of the validation for you. With annotated controller, this validation mechanism is removed. The common way to do validation with annotated controller is to inject Validator objects into your annotated controller and use its validate() method on the bean you want to validate. It's painful to write these validation steps in every request method I want to validate. The more of these validation codes I have in my method body, the uglier my method will be (Imagine if you have to use multiple validators to validate a single bean).

Luckily, with Spring 2.x's @Aspect, we can write a nice validation mechanism for our annotated controllers. If you want your request method to be validated, you just add annotation on top of your method and condition to check if the binding result has errors.

@Validateable(type=Example.class, validators="exampleValidator")
public String create(@ModelAttribute("example") Example example,
BindingResult bindingResult, WebRequest webRequest) {

if (bindingResult.hasErrors())/*do something here*/;

return null;
}

First, write the @Validateable annotation:

package com.mycompany.myapp.aspect.bind;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validateable {
Class type();
String[] validators();
}
@Validateable accepts two values: the target bean's class type and validator beans' name/id.

Next, write the Architecture aspect for your application:

package com.mycompany.myapp.aspect.system;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Architecture {
@Pointcut("execution(* com.mycompany.myapp..*Controller*.*(..))")
public final void controllerExecution() {}
}
The Architecture aspect will intercept any call that triggers any controller's method.

Then write the validation aspect:

package com.mycompany.myapp.aspect.bind;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;

@Aspect
public class Validation implements ApplicationContextAware {
private ApplicationContext applicationContext;

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stub
if (this.applicationContext == null)
this.applicationContext = applicationContext;
}

private Validator getValidator(String validator) {
return (Validator) applicationContext.getBean(validator);
}

@Around("com.mycompany.myapp.aspect.system.Architecture.controllerExecution() && " +
"@annotation(validateable)")
public Object validate(ProceedingJoinPoint joinPoint, Validateable validateable) throws Throwable {
Object target = null;
BindingResult result = null;

// Get the class type and validators' id/names
Class type = validateable.type();
String[] validators = validateable.validators();

// Get the arguments
Object[] args = joinPoint.getArgs();

// The bindingResultIndex will hold the index of BindingResult object
Integer bindingResultIndex = null;

// Check arguments array and retrieve the target bean and the BindingResult object
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof BindingResult) {
if (bindingResultIndex == null) {
result = (BindingResult) args[i];
bindingResultIndex = new Integer(i);
}
} else if (type.isInstance(args[i])) {
target = args[i];
} else if (target != null && result != null)
break;
}

for (String validator: validators) {
// Get the validator bean from application context and validate the target bean
getValidator(validator).validate(target, result);

if (result.hasErrors()) {
// Assign the BindingResults back to the arguments array
args[bindingResultIndex.intValue()] = result;
break;
}
}

return joinPoint.proceed(args);
}
}
Validation aspect will intercept any call to a controller's request methods annotated with @Validateable annotation. It will validate the target bean and set the validation results to the BindingResult object.

Declare the validation aspect as a Spring bean in one of your context:

<aop:aspectj-autoproxy />
<bean class="com.mycompany.myapp.aspect.bind.Validation" factory-method="aspectOf" />

Create the target bean:

package com.mycompany.myapp.mymodule.example.model;

public class Example {
private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

Write a simple validator for your bean:

package com.mycompany.myapp.mymodule.example.validator;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

import com.mycompany.myapp.mymodule.example.model.Example;

@Component("exampleValidator")
public class ExampleValidator implements Validator {
public boolean supports(Class arg) {
// TODO Auto-generated method stub
return Example.class.isAssignableFrom(arg);
}

public void validate(Object object, Errors errors) {
// TODO Auto-generated method stub
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "value", "field.required");
}
}

Finally, add @Validateable annotation to the request method in your annotated controller:

@RequestMapping(method=RequestMethod.POST, params="create")
@Validateable(type=Example.class, validators="exampleValidator")
public String create(@ModelAttribute("example") Example example,
BindingResult bindingResult, WebRequest webRequest) {

if (bindingResult.hasErrors()) {
System.out.println("AspectJ Validation is amazing!!!");
}
return null;
}

Don't forget to add AspectJ plugin to your pom.xml. If you use ant, you can use AspectJ's Ant task

Note: You can check an improved version of this validation system in the next post

8 comments:

Stevo Slavić said...

Great article and solution! Wish something like this was in the framework itself.

Is use of Architecture class really needed?

Wasn't @Before enough?

To avoid cglib, guess one would have to program *Controller class to an interface.

Thinking if a slightly different solution was feasible: have Validation.validate method intercept all the calls to the public methods only, where @target is @Controller annotated, whose @args have been annotated with @ModelAttribute. Validation class could have an @Autowired array/collection of all registered Validator beans. Validation.validate method could then take @args and loop through registered validators, check if they support @ModelAttribute annotated argument, and if so, just call validate like in your solution. Search for Validator that supports validation of given type could be optimized. Anyway, this solution would loose the need for @Validatable. Guess a related tag could be implemented to support easy enabling of such "magic" in a spring context, e.g. <mvc:controller-autovalidate />

Donny said...

Hi, Stevo.

Thanks a lot for nice words :D

The architecture class is not needed. I use it because I'm still unsure if adding a more verbose pointcut to controller would make AspectJ faster in locating the target method. ^^

The reason I use @Around is because I need to return the BindingResult argument back to the request method being validated.

===============================
Thinking if a slightly different solution was feasible: have Validation.validate method intercept all the calls to the public methods only, where @target is @Controller annotated, whose @args have been annotated with @ModelAttribute. Validation class could have an @Autowired array/collection of all registered Validator beans. Validation.validate method could then take @args and loop through registered validators, check if they support @ModelAttribute annotated argument, and if so, just call validate like in your solution. Search for Validator that supports validation of given type could be optimized. Anyway, this solution would loose the need for @Validatable. Guess a related tag could be implemented to support easy enabling of such "magic" in a spring context, e.g. <mvc:controller-autovalidate />
==================================

Thanks a ton for the suggestion on adding @Controller and @ModelAttribute annotations!! I will improve the validation example in the next post :D

XML namespace is currently beyond my reach ^^

Thanks for visiting my blog!!! :D

Stevo Slavić said...

Hi Donny,

If present BindingResult will never be null. You should be able to get a reference to it even by just using @Before aspect, and apply that aspect only to methods that have @ModelAttribute annotated argument and an argument of BindingResult type (along with other @target, @args stuff). You pass that bindingResult reference around to Validator.validate calls you make, they will update same object accordingly, so IMO there's no need to "return" anything.

There is a bit old but still relevant article at theserverside on custom spring namespaces that you might find useful.

Regards,
Stevo.

Donny said...

I never knew that with @Before, I will have the reference to those intercepted objects ^^

Thanks for the pointer and the link to namespace tutorial!! The link is very helpful. I always wanted to learn how to create namespace.

Stevo Slavić said...

If org.aspectj.lang.JoinPoint is defined as first argument of an advice, one can access target object (in our case @Controller annotated bean) and should be able to call (through reflection?) it's methods, like controller's validator accessor (e.g. getValidator()) method. This would eliminate the need for getting all the validators in Validation and searching for appropriate one.

Donny said...

In my previous project, I had a base controller to store the validators. But I found that I need to create a new bean to inject multiple validators into a controller. I would use the getter approach if @qualifier can be used to inject multiple beans into an array ^^

Stevo Slavić said...

I've tried and it seems that Spring AOP currently (2.5.6.SEC01) doesn't support matching join points by parameter annotations (created improvement request at SpringSource JIRA) so one can not match methods by the fact that they have parameters annotated with @ModelAttribute. Everything else from the idea is feasible and works.

Donny said...

You're right. I thought it would work with AspectJ 1.6.x
I also tried this pointcut: execution(* (@org.springframework.stereotype.Controller *..*Controller).*(@org.springframework.web.bind.annotation.ModelAttribute target,..)) and it couldn't locate the method.