Tuesday, September 25, 2012

Method Parameter Validation in Spring 3 MVC Controllers


JSR-303 specification allows validation of beans only however in this post I am going to tell you how you can use JSR-303 provider such as hibernate validator to validate request parameters, path variables in Spring 3 controller classes. To run this examaple Hibernate Validator 4.2 must be in classpath.

Below is the sample controller class in Spring 3 for fetching the product

@Controller
public class ProductController {

    @Autowired
    private transient ProductService productService;

    @RequestMapping(value = "/product/{prodId}/", method = RequestMethod.GET)
    public ModelAndView getProduct(@PathVariable("prodId") final String productId) {
        ProductDetail product = productService.getProduct(productId);
        mv.addObject("product", product);
        mv.setViewName("product");
        return mv;
    }
}

You can see that anyone can enter garbage value for {prodId} and that value will be passed to the getProduct() method of product service. We will validate the {prodId} before method execution begins.

Lets create our own JSR-303 annotation @ProductId which will validate the format of prodId

@NotBlank
@Size(min = 5, max = 5)
@Digits(integer = 5, fraction = 0)
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, LOCAL_VARIABLE })
// specifies where this validation can be used (Field, Method, Parameter etc)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {})
@ReportAsSingleViolation
// specifies if any of the validation fails, it will be reported as single validation
public @interface ProductId {

    /**
     * This is the key to message will that will be looked in validation.properties for validation
     * errors
     * 
     * @return the string
     */
    String message() default "{invalid.product.id}";

    Class[] groups() default {};

    Class[] payload() default {};
}

@ProductId annotation validates a given string for
  • Not empty string
  • Size should be exactly 5 characters 
  • All characters should be digits.

Now we will use this annotation to validate our prodId path variable.
To use this validation we would need to do following:
  1. Prefix the @PathVariable with @ProductId 
  2. Add @Validated annotation to controller class. It tells spring to validate class at method level.
  3. We need a bean of type MethodValidationPostProcessor in the spring context which will look for @Validated annotated classes and will apply method level validations.
See below how the ProductController now looks like:

@Controller
@Validated
// enables methods parameters JSR validation.
public class ProductController {

    @Autowired
    private transient ProductService productService;

    @RequestMapping(value = "/product/{prodId}/", method = RequestMethod.GET)
    public ModelAndView getProduct(@ProductId @PathVariable("prodId") final String productId) {
        ProductDetail product = productService.getProduct(productId);
        mv.addObject("product", product);
        mv.setViewName("product");
        return mv;
    }
}

Below code tells how to instantiate MethodValidationPostProcessor in spring configuration

@Configuration
@ComponentScan(basePackages = { "web.controller" })
// packages to scans for components
@EnableWebMvc
// enable the MVC support
public class SpringWebConfig extends WebMvcConfigurerAdapter {

    /**
     * Method validation post processor. This bean is created to apply the JSR validation in method
     * parameters. Any class which want to perform method param validation must use @Validated
     * annotation at class level.
     * 
     * @return the method validation post processor
     */
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}


Now when you will call this controller method using url "/product/{some garbage value}", it will throw MethodConstraintViolationException exception. You can handle this error using @ExceptionHandler annotation and redirect user to error page with a message.

    @ExceptionHandler({Exception.class})
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    protected ModelAndView handleException(final Exception ex) {
        final ModelAndView mv = new ModelAndView();
        final List messages = new ArrayList();
        if (ex instanceof MethodConstraintViolationException) {
            for (ConstraintViolation failure : ((MethodConstraintViolationException) ex).getConstraintViolations()) {
                messages.add(failure.getMessage());
            }
        } else {
            messages.add(ex.getMessage());
        }
        mv.addObject("exceptionModel", messages);
        mv.setViewName("exception");
        return mv;
    }

You can use the similar way to validate request parameters by prefixing your validation annotation in front of @RequestParameter annotation.

If you want to disable the validation for some particular class remove the @Validated annotation from that class. If you are interested in completely disabling the method validation then remove the MethodValidationPostProcessor bean from spring context. You might want to disable validations for unit tests.

Monday, September 24, 2012

XML less configuration in spring 3 using annotations


Since the release of Spring 3, JavaConfig is part of core module and provides and easy way to configure Spring applications.  In this post I am going to show how you can achieve complete Java based configuration in Spring 3 without any XML files.

Before  we start looking at the code lets look at some basic Spring annotation and their meanings.

@Configuration: It indicates that a class declares one or more Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime.

@Bean: This annotation is applied to methods that create beans in a Spring context. The name of the bean is the method name.

These two annotation are minimum requirement to start with XML less configuration in spring. Let see the code now:



/**
 * This is purely java based configuration and do not uses
 * any XML files.
 */
@Configuration
public class AppConfig {

    /**
     * This creates bean with name testBean. Note that the bean name is method name by default. In
     * this case bean name will be testBean.
     */
    @Bean
    public MyBean testBean() {
        return new TestBean();
    }

    /**
     * You can override the default name providing your own name to bean annotation. In this case
     * bean name will be renamedBean.
     */
    @Bean(name = "renamedBean")
    public AnotherBean anotherBean() {
        return new AnotherBean();
    }

    public static void main(String[] args) {
        // Initialize spring container. Note that we are giving AppConfig.class so that spring loads
        // beans defined in this class
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println("calling a bean method");
        // Retrieve the bean from Container by class
        MyBean bean = context.getBean(MyBean.class);

        // Retrieve the bean from Container by name
        // typecase is required
        AnotherBean anotherBean = (AnotherBean) context.getBean("renamedDao");
        bean.helloWorld();
        anotherBean.helloSpring();
    }
}

Yo can also use @ComponentScan annotation to instantiate the beans outside of AppConfig class. Spring IoC scans the the package or class name given as argument to @ComponentScan annotation and instantiates them as beans if they have @Service or @Component annotation.


@Configuration
@ComponentScan("test.package") //scans test.packages for any components and instantiates them a beans
public class AppConfig {