`
teamojiao
  • 浏览: 350389 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Spring Validation with Java Annotations

阅读更多

Spring Framework provides a ready-to-use, simple validation framework out of the box. In this article, I'll explore how to use Java annotations to make Spring's validation framework even more convenient to use, while providing a basic introduction to Spring validation and Java annotations in general.

Introduction

Spring's data binding and validation framework is simple but functional - all you need to do is implement the Validator interface. In particular, if you use Spring's MVC framework (and its command/form controllers) then you're probably using Spring validation as well. If not, you should be! Moving your validation outside the controller into a Validator implementation allows you to declaratively assign and reuse your validation logic.

But while you can declaratively assign and reuse Validators with your form or command controllers, doing so only allows you to declare validation on a 'per-class' level, with no field-level granularity.

A typical scenario

For example, consider the typical scenario where you have a LoginForm class to represent your typical login form with two fields username and password. It's trivial to write a LoginFormValidator that checks that both fields are present and assign that to the controller that handles login.

What if you now need a RegistrationForm controller, with additional fields such as verifyPassword and emailAddress? Well, you could extend LoginFormValidator or simply add an new Validator to the registration controller for the additional required fields. All is still good and dandy.

But what about other form or command objects? What if, a PasswordChangeForm needs only the password and verifyPassword fields? How about a UserSearchForm with only the username?

You could of course write unique Validators for individual fields, but that's a terrible CodeSmell. At this point you know you need to write a more generic validation implementation with field-level granularity.

But, depending upon your approach, you might end up not being able to use Spring's declarative approach to assigning validators to controllers. Or, you might have to implement your own mechanism to allow declarative validation rules using fancy XML. Or use YetAnotherThirdPartyLibrary, which, for a small application or during its initial stages may be overkill and cause unnecessary code bloat.

Annotations to the rescue

Fortunately, Java 5.0 annotations give us a convenient way to mark object elements or fields in a way that lets us roll out an equally simple and extensible, yet more granular validation implementation based on Spring.

The rest of this article details a simplistic approach to this, using the common, if rather trivial example above. At the end of the article I have some thoughts on how one can take this approach and make it more functional and useful.

Our typical LoginForm

package foo.bar;

public class LoginForm {

    private String username;

    private String password;

    public String getUsername() {
        return this.username;
    };

    // snip...
}

Nothing exciting happening here, I'll just put it here for reference.

Annotating classes

First we'll want to 'mark' our class so that our annotation-aware validators can easily recognize them later (without having to inspect down to the class members). Let's create our first annotation:

package foo.bar;

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validatable {

}

As you can see, there's really nothing there. It's a plain marker annotation. A few things about the annotations that annotate our annotation are in order, though.

The "@Target(ElementType.TYPE)" annotation tells the compiler that Validatable should only be applied to Types or classes.

"@Retention(RetentionPolicy.RUNTIME)" indicates that this annotation should be recorded into the class file and retained by the VM at run-time, enabling it to be introspected. This is probably the most important part of this whole annotation business for our purposes, as it's crucial to how our validator will work later.

We can now use @Validatable to annotate our classes so they can be identified and validated by our validation framework later. Next, we'll create the @Required annotation to indicate required fields.

Annotating fields

package foo.bar;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Required {

}

Note that while I said @Required will be used to annotate required fields, I indicate that it's target should be a METHOD. In particular, we'll be using @Required to annotate field or property 'getters'.

There are two reasons for this. One is I'm a lazy bum who likes to get things done the simplest way possible. Next is that if we use @Required on a class' private fields then we'd have to do double work to introspect the class just to find our annotation(s) (as opposed to being able to use Spring's BeanUtils to help us out). So instead, I opt to use @Required on the getField() methods.

Annotations in action

Armed with these two annotations, let's go ahead and apply them to our LoginForm.

@Validatable
public class LoginForm {

    // ...snip

    @Required
    public String getUsername() {
        return this.username;
    };

    // ...snip...

    @Required
    public String getUsername() {
        return this.username;
    };

    // snip...

}

As you can see, writing annotations is quite painless, especially simple marker annotations with no parameters (or elements) like ours.

Also, using annotations in one's code doesn't require much work, either (just remember to import them if they're in a different package). In fact, annotations can lend to better code clarity - after all, that's what they're there for in the first place.

A Spring Validator in 3 easy steps

Now let's make this magic happen. We'll write a RequiredAnnotationValidator that uses introspection to find our annotations, and perform validation on the required fields.

Step 1: Implement Validator

public class RequiredAnnotationValidator implements Validator {

// snip...

Because we're implementing Spring's Validator interface, we need to implement its two primary methods supports() and validate().

Step 2: Implement supports()

What classes should our RequiredAnnotationValidator support? Why, classes annotated as @Validatable, of course. How do we find out if a class is annotated as @Validatable using introspection? Why, it's almost trivial:

    @SuppressWarnings("unchecked")
    public final boolean supports(final Class clazz) {
        return clazz.isAnnotationPresent(Validatable.class);
    }

As you can see if we had only used our @Required annotation, our validator would have to do a lot more work - go through the methods of the given Class clazz sequentially, looking for any methods annotated as @Required and returning true once it finds one. Annotating the class itself is simpler and faster.

A little note about the @SuppressWarnings above. If you're using Eclipse IDE, the line "clazz.isAnnotationPresent(...)" will generate a warning because we're calling a method on the raw type Class (and not a parameterized version of the generic type Class < T >). It's kind of a nuisance - though we can get rid of the warning with a fugly cast: "Class<?> c = clazz" - so we tell Eclipse to just shut up.

Step 3: Implement validate()

Now to the meat of the matter, implementing validate(). Now that you've seen how supports() works, you should have a general idea of how validate() is supposed to work.

First we'll want a list of getter methods. We could use standard Java Reflection, or, since we're already using Spring:

    public final void validate(final Object obj, final Errors errors) {
        // list the object's properties
        final PropertyDescriptor[] propertyDescriptors 
            = BeanUtils.getPropertyDescriptors(obj.getClass());

...

We can rely on BeanUtils.getPropertyDescriptors() for convenience since, by our own convention, we're looking for annotations in property getter methods. As I mentioned earlier, had we annotated private fields, we'd have to use Class.getDeclaredFields(), iterate through the fields looking for corresponding accessor methods, etc. Our approach is a lot less painful.

What do we do with each property descriptor? First check if the property has a read method, and if that method is annotated as @Required (since we're using Java 5.0, we'll go ahead and use an enhanced for loop):

        for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {

            // see if the property (getter) is annotated as required
            final Method method = propertyDescriptor.getReadMethod();
            if (method != null && method.isAnnotationPresent(Required.class)) {

At this point we know that the field foo is required because the method getFoo() was annotated with @Required. Now all we have to do is check that its value is not null, or, since we want to be a little smart, check that it's not the empty string either (""). Fortunately, once again Spring provides utility code in ValidationUtils to do just the job:

                final String field = propertyDescriptor.getName();
                ValidationUtils.rejectIfEmpty(errors, field, field + ".required");

Here we just told Spring to check if the given field is empty, and if it is, reject it with the error code "fieldname.required" and place that error into the passed in errors object.

The complete validator

Here's a more-or-less complete look at our RequiredAnnotationValidator (minus any import statements):

package foo.bar;

public class RequiredAnnotationValidator implements Validator {

    @SuppressWarnings("unchecked")
    public final boolean supports(final Class clazz) {
        return clazz.isAnnotationPresent(Validatable.class);
    }

    public final void validate(final Object obj, final Errors errors) {

        // list the object's properties
        final PropertyDescriptor[] propertyDescriptors 
            = BeanUtils.getPropertyDescriptors(obj.getClass());

        for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {

            // see if the property (getter) is annotated as required
            final Method method = propertyDescriptor.getReadMethod();
            if (method != null && method.isAnnotationPresent(Required.class)) {

                // reject with "field.required" if null/empty
                final String field = propertyDescriptor.getName();
                ValidationUtils.rejectIfEmpty(errors, field, field + ".required");

            }
        }
    }

}

Voila!

Annotation-based validation based on Spring's validation framework in a couple of dozen lines of code, how's that? Here's how we'd use it in an sample Spring application context XML descriptor file:

  <bean name="requiredAnnotationValidator" class="foo.bar.RequiredAnnotationValidator"/>

  <!-- e.g., foo.bar.LoginController extends SimpleFormController -->
  <bean name="loginController" class="foo.bar.LoginController">
    <property name="commandClass"><value>foo.bar.LoginForm</value></property>
    <property name="validator"><ref bean="requiredAnnotationValidator"/></property>
  </bean>

If we need to use more than one validator for LoginController, then we simply use the plural validators property of BaseCommandController.

Conclusion

Simple, extensible and granular declarative validation is possible using the Spring framework, aided by Java annotations.

There are a lot of more things we can do from here. We could use an annotation element as a parameter to declaratively specify the error code for our required field (as opposed to a hard-coded "field.required"). We can go ahead and create other annotations, for example, @MaxLength with a length parameter.

One drawback to our approach is the fact that the validation has to perform introspection every time it's asked to validate an object. For form command object binding and validation, this isn't such a problem. However, this can lead to performance issues especially if the validation is called repeatedly on a large collection of objects.

One enhancement I could think of is to have the validators cache the introspection results so they don't have to do it every time (and do so in a thread-safe manner).

This article has outlined a gentle introduction Spring validation as well as Java annotations. I hope now you know how easy they both can be, and that you're now as excited as I am on their added posssiblities.

Alistair Israel started learning programming with BASIC on a TRS-80. That was a long time ago. He's still learning.

分享到:
评论

相关推荐

    Spring Validation方法实现原理分析

    Spring Validation方法实现原理分析 Spring Validation是Spring框架中的一种校验机制,用于验证JavaBean的属性是否符合JSR-303规范。该机制可以在应用程序中自动验证JavaBean的属性,从而确保数据的正确性和完整性...

    Deep Learning: Practical Neural Networks with Java 完整高清英文azw3版

    Deep Learning: Practical Neural Networks with Java by Yusuke Sugomori English | 8 Jun. 2017 | ASIN: B071GC77N9 | 1057 Pages | AZW3 | 20.28 MB Build and run intelligent applications by leveraging key ...

    spring注解-validation所用到的jar包

    在Spring框架中,注解和Validation是两个关键的组件,它们极大地简化了应用程序的开发过程。Validation主要用于数据验证,而Spring注解则提供了声明式编程的能力,使得代码更加简洁、可读性更强。这里我们将详细探讨...

    springmvc校验器validation的所有配套jar包

    在Java Web开发中,Spring MVC框架是用于构建高效、模块化和可测试的Web应用程序的首选工具。在Spring MVC中,数据验证是一个重要的部分,它确保了用户输入的数据符合业务规则,提高了系统的稳定性和安全性。这里...

    validation:spring2.5 + spring-modules-validation 扩展

    Spring Modules Validation是Spring框架的一个扩展,为开发者提供了更强大的数据验证功能。在这个"validation:spring2.5 + spring-modules-validation 扩展"中,我们将探讨如何利用这两个组件来实现业务上的复杂判断...

    spring-boot-starter-validation-1.3.5.RELEASE.zip

    标题中的"spring-boot-starter-validation-1.3.5.RELEASE.zip"是一个Spring Boot的启动模块,专注于数据验证功能的版本。Spring Boot是Java生态系统中一个流行的微服务框架,它简化了创建独立、生产级别的Spring应用...

    Spring-Validation 后端数据校验的实现

    Spring Validation 是一个基于Java的数据校验框架,提供了一些 annotation 来描述数据的约束条件。这些 annotation 可以被应用于 JavaBean 的成员变量、方法和构造函数参数上,以指定它们的校验规则。 在 Spring ...

    Spring MVC validation Example

    在本文中,我们将深入探讨如何使用Spring MVC框架进行基于注解的验证,特别是在Spring Bean Validation框架下。Spring MVC是Spring框架的一部分,它提供了一个强大的模型-视图-控制器(MVC)架构,用于构建Web应用...

    详解使用spring validation完成数据后端校验

    Spring Validation 是一种基于 Java Bean Validation(JSR-303/JSR-349)规范的校验框架,提供了强大的数据校验功能。 JSR-303/JSR-349 是 Java Bean Validation 的规范,它规定了一些校验规范,即校验注解,如 @...

    Getting.started.with.Spring.Framework.2nd.Edition1491011912.epub

    This book is meant for Java developers with little or no knowledge of Spring Framework. All the examples shown in this book use Spring 4. You can download the examples (consisting of 60 sample ...

    Really easy field validation with Prototype 1.5.3 中文扩展版

    "Really easy field validation with Prototype 1.5.3 中文扩展版"是一个针对Prototype JavaScript库的前端验证插件,旨在简化网页表单的数据验证过程。这个工具以其简单易用和强大的功能著称,并且提供了对多语言的...

    spring-boot-starter-validation-2.3.7.RELEASE.jar

    常用jar包

    The Java XML Validation API

    Java XML Validation API是Java平台中用于验证XML文档的重要工具,它是Java API for XML Processing (JAXP)的一部分。XML(eXtensible Markup Language)是一种用于标记数据的标准格式,广泛应用于数据交换、配置...

    resin 支持spring mvc 5.0以上版本 支持Hibernate validation

    Resin 是一款高性能的Java应用服务器,它支持多种Web应用程序框架,包括Spring MVC。Spring MVC是Spring框架的一部分,专门用于构建MVC模式的Web应用程序。本文将深入探讨Resin如何支持Spring MVC 5.0及以上版本,...

    spring-modules-validation-0.6.jar

    spring-modules-validation-0.6.jar

    如何使用Spring Validation优雅地校验参数

    "Spring Validation参数校验" 在业务开发过程中,参数校验是一个必不可少的步骤,但是一般来说,我们都是通过if-else语句来实现参数校验的,这种方式确实可以实现参数校验,但是这种方式存在一些问题,如代码臃肿、...

    spring的一个适合初学者的项目

    Spring 框架是Java开发中的一个核心框架,尤其对于初学者来说,它是一个极好的起点,可以帮助理解企业级应用的构建方式。本项目旨在为初学者提供一个基础的Spring项目实例,帮助大家快速上手并熟悉Spring的核心概念...

    spring-modules-validation.jar

    非常不错的数据校验jar,与spring的无缝接入,是java pojo对象校验的好框架。

    spring 经典 java 书籍 Spring+Framework+2.5

    《Spring+Framework+2.5》是一本专为Java开发者准备的Spring入门书籍,它深入浅出地介绍了Spring框架的核心概念和技术。这本书籍是针对Spring Framework 2.5版本编写的,该版本在当时是一个非常重要的里程碑,引入了...

Global site tag (gtag.js) - Google Analytics