Hibernate.org Community Documentation

- 上一页
- 下一页

## 第 9 章 Using constraint metadata

The Bean Validation specification provides not only a validation engine, but also an API for retrieving constraint metadata in a uniform way, no matter whether the constraints are declared using annotations or via XML mappings. Read this chapter to learn more about this API and its possibilities. You can find all the metadata API types in the package javax.validation.metadata.

The examples presented in this chapter are based on the classes and constraint declarations shown in 例 9.1 “Example classes”.

例 9.1. Example classes

    package org.hibernate.validator.referenceguide.chapter07;

    public class Person {

            public interface Basic {

            }

            @NotNull

            private String name;

            //getters and setters ...

    }

    public interface Vehicle {

            public interface Basic {

            }

            @NotNull(groups = Vehicle.Basic.class)

            String getManufacturer();

    }

    @ValidCar

    public class Car implements Vehicle {

            public interface SeverityInfo extends Payload {

            }

            private String manufacturer;

            @NotNull

            @Size(min = 2, max = 14)

            private String licensePlate;

            private Person driver;

            private String modelName;

            public Car() {

            }

            public Car(

                            @NotNull String manufacturer,

                            String licencePlate,

                            Person driver,

                            String modelName) {

                    this.manufacturer = manufacturer;

                    this.licensePlate = licencePlate;

                    this.driver = driver;

                    this.modelName = modelName;

            }

            public void driveAway(@Max(75) int speed) {

                    //...

            }

            @LuggageCountMatchesPassengerCount(

                            piecesOfLuggagePerPassenger = 2,

                            validationAppliesTo = ConstraintTarget.PARAMETERS,

                            payload = SeverityInfo.class,

                            message = "There must not be more than {piecesOfLuggagePerPassenger} pieces of " +

                                            "luggage per passenger."

            )

            public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {

                    //...

            }

            @Override

            @Size(min = 3)

            public String getManufacturer() {

                    return manufacturer;

            }

            public void setManufacturer(String manufacturer) {

                    this.manufacturer = manufacturer;

            }

            @Valid

            @ConvertGroup(from = Default.class, to = Person.Basic.class)

            public Person getDriver() {

                    return driver;

            }

            //further getters and setters...

    }

## 9.1. `BeanDescriptor`

The entry point into the metadata API is the method `Validator#getConstraintsForClass()`, which returns an instance of the `BeanDescriptor` interface. Using this descriptor, you can obtain metadata for constraints declared directly on the bean itself (class- or property-level), but also retrieve metadata descriptors representing single properties, methods and constructors.

例 9.2 “Using BeanDescriptor” demonstrates how to retrieve a `BeanDescriptor` for the `Car` class and how to use this descriptor in form of assertions.

## 提示

If a constraint declaration hosted by the requested class is invalid, a `ValidationException` is thrown.

例 9.2. Using `BeanDescriptor`

    Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    BeanDescriptor carDescriptor = validator.getConstraintsForClass( Car.class );

    assertTrue( carDescriptor.isBeanConstrained() );

    //one class-level constraint

    assertEquals( 1, carDescriptor.getConstraintDescriptors().size() );

    //manufacturer, licensePlate, driver

    assertEquals( 3, carDescriptor.getConstrainedProperties().size() );

    //property has constraint

    assertNotNull( carDescriptor.getConstraintsForProperty( "licensePlate" ) );

    //property is marked with @Valid

    assertNotNull( carDescriptor.getConstraintsForProperty( "driver" ) );

    //constraints from getter method in interface and implementation class are returned

    assertEquals(

                    2,

                    carDescriptor.getConstraintsForProperty( "manufacturer" )

                                    .getConstraintDescriptors()

                                    .size()

    );

    //property is not constrained

    assertNull( carDescriptor.getConstraintsForProperty( "modelName" ) );

    //driveAway(int), load(List<Person>, List<PieceOfLuggage>)

    assertEquals( 2, carDescriptor.getConstrainedMethods( MethodType.NON_GETTER ).size() );

    //driveAway(int), getManufacturer(), getDriver(), load(List<Person>, List<PieceOfLuggage>)

    assertEquals(

                    4,

                    carDescriptor.getConstrainedMethods( MethodType.NON_GETTER, MethodType.GETTER )

                                    .size()

    );

    //driveAway(int)

    assertNotNull( carDescriptor.getConstraintsForMethod( "driveAway", int.class ) );

    //getManufacturer()

    assertNotNull( carDescriptor.getConstraintsForMethod( "getManufacturer" ) );

    //setManufacturer() is not constrained

    assertNull( carDescriptor.getConstraintsForMethod( "setManufacturer", String.class ) );

    //Car(String, String, Person, String)

    assertEquals( 1, carDescriptor.getConstrainedConstructors().size() );

    //Car(String, String, Person, String)

    assertNotNull(

                    carDescriptor.getConstraintsForConstructor(

                                    String.class,

                                    String.class,

                                    Person.class,

                                    String.class

                    )

    );

You can determine whether the specified class hosts any class- or property-level constraints via `isBeanConstrained()`. Method or constructor constraints are not considered by `isBeanConstrained()`.

The method `getConstraintDescriptors()` is common to all descriptors derived from `ElementDescriptor` (see 第 9.4 节 “ElementDescriptor”) and returns a set of descriptors representing the constraints directly declared on the given element. In case of `BeanDescriptor`, the bean's class-level constraints are returned. More details on `ConstraintDescriptor` can be found in 第 9.6 节 “ConstraintDescriptor”.

Via `getConstraintsForProperty()`, `getConstraintsForMethod()` and `getConstraintsForConstructor()` you can obtain a descriptor representing one given property or executable element, identified by its name and, in case of methods and constructors, parameter types. The different descriptor types returned by these methods are described in the following sections.

Note that these methods consider constraints declared at super-types according to the rules for constraint inheritance as described in 第 2.1.4 节 “Constraint inheritance”. An example is the descriptor for the `manufacturer` property, which provides access to all constraints defined on `Vehicle#getManufacturer()` and the implementing method `Car#getManufacturer()`. `null` is returned in case the specified element does not exist or is not constrained.

The methods `getConstrainedProperties()`, `getConstrainedMethods()` and `getConstrainedConstructors()` return (potentially empty) sets with all constrained properties, methods and constructors, respectively. An element is considered constrained, if it has at least one constraint or is marked for cascaded validation. When invoking `getConstrainedMethods()`, you can specify the type of the methods to be returned (getters, non-getters or both).

## 9.2. `PropertyDescriptor`

The interface `PropertyDescriptor` represents one given property of a class. It is transparent whether constraints are declared on a field or a property getter, provided the JavaBeans naming conventions are respected. 例 9.3 “Using PropertyDescriptor” shows how to use the `PropertyDescriptor` interface.

例 9.3. Using `PropertyDescriptor`

    PropertyDescriptor licensePlateDescriptor = carDescriptor.getConstraintsForProperty(

                    "licensePlate"

    );

    //"licensePlate" has two constraints, is not marked with @Valid and defines no group conversions

    assertEquals( "licensePlate", licensePlateDescriptor.getPropertyName() );

    assertEquals( 2, licensePlateDescriptor.getConstraintDescriptors().size() );

    assertTrue( licensePlateDescriptor.hasConstraints() );

    assertFalse( licensePlateDescriptor.isCascaded() );

    assertTrue( licensePlateDescriptor.getGroupConversions().isEmpty() );

    PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );

    //"driver" has no constraints, is marked with @Valid and defines one group conversion

    assertEquals( "driver", driverDescriptor.getPropertyName() );

    assertTrue( driverDescriptor.getConstraintDescriptors().isEmpty() );

    assertFalse( driverDescriptor.hasConstraints() );

    assertTrue( driverDescriptor.isCascaded() );

    assertEquals( 1, driverDescriptor.getGroupConversions().size() );

Using `getConstrainedDescriptors()`, you can retrieve a set of `ConstraintDescriptor`s providing more information on the individual constraints of a given property. The method `isCascaded()` returns `true`, if the property is marked for cascaded validation (either using the `@Valid` annotation or via XML), `false` otherwise. Any configured group conversions are returned by `getGroupConversions()`. See 第 9.5 节 “GroupConversionDescriptor” for more details on `GroupConversionDescriptor`.

## 9.3. `MethodDescriptor` and `ConstructorDescriptor`

Constrained methods and constructors are represented by the interfaces `MethodDescriptor` and `ConstructorDescriptor`, respectively. 例 9.4 “Using MethodDescriptor and ConstructorDescriptor” demonstrates how to work with these descriptors.

例 9.4. Using `MethodDescriptor` and `ConstructorDescriptor`

    //driveAway(int) has a constrained parameter and an unconstrained return value

    MethodDescriptor driveAwayDescriptor = carDescriptor.getConstraintsForMethod(

                    "driveAway",

                    int.class

    );

    assertEquals( "driveAway", driveAwayDescriptor.getName() );

    assertTrue( driveAwayDescriptor.hasConstrainedParameters() );

    assertFalse( driveAwayDescriptor.hasConstrainedReturnValue() );

    //always returns an empty set; constraints are retrievable by navigating to

    //one of the sub-descriptors, e.g. for the return value

    assertTrue( driveAwayDescriptor.getConstraintDescriptors().isEmpty() );

    ParameterDescriptor speedDescriptor = driveAwayDescriptor.getParameterDescriptors()

                    .get( 0 );

    //The "speed" parameter is located at index 0, has one constraint and is not cascaded

    //nor does it define group conversions

    assertEquals( "arg0", speedDescriptor.getName() );

    assertEquals( 0, speedDescriptor.getIndex() );

    assertEquals( 1, speedDescriptor.getConstraintDescriptors().size() );

    assertFalse( speedDescriptor.isCascaded() );

    assert speedDescriptor.getGroupConversions().isEmpty();

    //getDriver() has no constrained parameters but its return value is marked for cascaded

    //validation and declares one group conversion

    MethodDescriptor getDriverDescriptor = carDescriptor.getConstraintsForMethod(

                    "getDriver"

    );

    assertFalse( getDriverDescriptor.hasConstrainedParameters() );

    assertTrue( getDriverDescriptor.hasConstrainedReturnValue() );

    ReturnValueDescriptor returnValueDescriptor = getDriverDescriptor.getReturnValueDescriptor();

    assertTrue( returnValueDescriptor.getConstraintDescriptors().isEmpty() );

    assertTrue( returnValueDescriptor.isCascaded() );

    assertEquals( 1, returnValueDescriptor.getGroupConversions().size() );

    //load(List<Person>, List<PieceOfLuggage>) has one cross-parameter constraint

    MethodDescriptor loadDescriptor = carDescriptor.getConstraintsForMethod(

                    "load",

                    List.class,

                    List.class

    );

    assertTrue( loadDescriptor.hasConstrainedParameters() );

    assertFalse( loadDescriptor.hasConstrainedReturnValue() );

    assertEquals(

                    1,

                    loadDescriptor.getCrossParameterDescriptor().getConstraintDescriptors().size()

    );

    //Car(String, String, Person, String) has one constrained parameter

    ConstructorDescriptor constructorDescriptor = carDescriptor.getConstraintsForConstructor(

                    String.class,

                    String.class,

                    Person.class,

                    String.class

    );

    assertEquals( "Car", constructorDescriptor.getName() );

    assertFalse( constructorDescriptor.hasConstrainedReturnValue() );

    assertTrue( constructorDescriptor.hasConstrainedParameters() );

    assertEquals(

                    1,

                    constructorDescriptor.getParameterDescriptors()

                                    .get( 0 )

                                    .getConstraintDescriptors()

                                    .size()

    );

`getName()` returns the name of the given method or constructor. The methods `hasConstrainedParameters()` and `hasConstrainedReturnValue()` can be used to perform a quick check whether an executable element has any parameter constraints (either constraints on single parameters or cross-parameter constraints) or return value constraints.

Note that any constraints are not directly exposed on `MethodDescriptor` and `ConstructorDescriptor`, but rather on dedicated descriptors representing an executable's parameters, its return value and its cross-parameter constraints. To get hold of one of these descriptors, invoke `getParameterDescriptors()`, `getReturnValueDescriptor()` or `getCrossParameterDescriptor()`, respectively.

These descriptors provide access to the element's constraints (`getConstraintDescriptors()`) and, in case of parameters and return value, to its configuration for cascaded validation (`isValid()` and `getGroupConversions()`). For parameters, you also can retrieve the index and the name, as returned by the currently used parameter name provider (see 第 8.2.4 节 “ParameterNameProvider”) via `getName()` and `getIndex()`.

## 提示

Getter methods following the JavaBeans naming conventions are considered as bean properties but also as constrained methods.

That means you can retrieve the related metadata either by obtaining a `PropertyDescriptor` (e.g. `BeanDescriptor.getConstraintsForProperty("foo")`) or by examining the return value descriptor of the getter's `MethodDescriptor` (e.g. `BeanDescriptor.getConstraintsForMethod("getFoo").getReturnValueDescriptor()`).

## 9.4. `ElementDescriptor`

The `ElementDiscriptor` interface is the common base class for the individual descriptor types such as `BeanDescriptor`, `PropertyDescriptor` etc. Besides `getConstraintDescriptors()` it provides some more methods common to all descriptors.

`hasConstraints()` allows for a quick check whether an element has any direct constraints (e.g. class-level constraints in case of `BeanDescriptor`). `getElementClass()` returns the Java type of the element represented by a given descriptor. More specifically, the method returns

- the object type when invoked on `BeanDescriptor`,

- the type of a property or parameter when invoked on `PropertyDescriptor` or `ParameterDescriptor` respectively,

- `Object[].class` when invoked on `CrossParameterDescriptor`,

- the return type when invoked on `ConstructorDescriptor`, `MethodDescriptor` or `ReturnValueDescriptor`. `void.class` will be returned for methods which don't have a return value.

例 9.5 “Using ElementDescriptor methods” shows how these methods are used.

例 9.5. Using `ElementDescriptor` methods

    PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(

                    "manufacturer"

    );

    assertTrue( manufacturerDescriptor.hasConstraints() );

    assertEquals( String.class, manufacturerDescriptor.getElementClass() );

    CrossParameterDescriptor loadCrossParameterDescriptor = carDescriptor.getConstraintsForMethod(

                    "load",

                    List.class,

                    List.class

    ).getCrossParameterDescriptor();

    assertTrue( loadCrossParameterDescriptor.hasConstraints() );

    assertEquals( Object[].class, loadCrossParameterDescriptor.getElementClass() );

Finally, `ElementDescriptor` offers access to the `ConstraintFinder` API which allows you to query for constraint metadata in a fine grained way. 例 9.6 “Usage of ConstraintFinder” shows how to retrieve a `ConstraintFinder` instance via `findConstraints()` and use the API to query for constraint metadata.

例 9.6. Usage of ConstraintFinder

    PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(

                    "manufacturer"

    );

    //"manufacturer" constraints are declared on the getter, not the field

    assertTrue(

                    manufacturerDescriptor.findConstraints()

                                    .declaredOn( ElementType.FIELD )

                                    .getConstraintDescriptors()

                                    .isEmpty()

    );

    //@NotNull on Vehicle#getManufacturer() is part of another group

    assertEquals(

                    1,

                    manufacturerDescriptor.findConstraints()

                                    .unorderedAndMatchingGroups( Default.class )

                                    .getConstraintDescriptors()

                                    .size()

    );

    //@Size on Car#getManufacturer()

    assertEquals(

                    1,

                    manufacturerDescriptor.findConstraints()

                                    .lookingAt( Scope.LOCAL_ELEMENT )

                                    .getConstraintDescriptors()

                                    .size()

    );

    //@Size on Car#getManufacturer() and @NotNull on Vehicle#getManufacturer()

    assertEquals(

                    2,

                    manufacturerDescriptor.findConstraints()

                                    .lookingAt( Scope.HIERARCHY )

                                    .getConstraintDescriptors()

                                    .size()

    );

    //Combining several filter options

    assertEquals(

                    1,

                    manufacturerDescriptor.findConstraints()

                                    .declaredOn( ElementType.METHOD )

                                    .lookingAt( Scope.HIERARCHY )

                                    .unorderedAndMatchingGroups( Vehicle.Basic.class )

                                    .getConstraintDescriptors()

                                    .size()

    );

Via `declaredOn()` you can search for `ConstraintDescriptor`s declared on certain element types. This is useful to find property constraints declared on either fields or getter methods.

`unorderedAndMatchingGroups()` restricts the resulting constraints to those matching the given validation group(s).

`lookingAt()` allows to distinguish between constraints directly specified on the element (`Scope.LOCAL_ELEMENT`) or constraints belonging to the element but hosted anywhere in the class hierarchy (`Scope.HIERARCHY`).

You can also combine the different options as shown in the last example.

## 警告

Order is not respected by `unorderedAndMatchingGroups()`, but group inheritance and inheritance via sequence are.

## 9.5. `GroupConversionDescriptor`

All those descriptor types that represent elements which can be subject of cascaded validation (i.e., `PropertyDescriptor`, `ParameterDescriptor` and `ReturnValueDescriptor`) provide access to the element's group conversions via `getGroupConversions()`. The returned set contains a `GroupConversionDescriptor` for each configured conversion, allowing to retrieve source and target groups of the conversion. 例 9.7 “Using GroupConversionDescriptor” shows an example.

例 9.7. Using `GroupConversionDescriptor`

    PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );

    Set<GroupConversionDescriptor> groupConversions = driverDescriptor.getGroupConversions();

    assertEquals( 1, groupConversions.size() );

    GroupConversionDescriptor groupConversionDescriptor = groupConversions.iterator()

                    .next();

    assertEquals( Default.class, groupConversionDescriptor.getFrom() );

    assertEquals( Person.Basic.class, groupConversionDescriptor.getTo() );

## 9.6. `ConstraintDescriptor`

Last but not least, the `ConstraintDescriptor` interface describes a single constraint together with its composing constraints. Via an instance of this interface you get access to the constraint annotation and its parameters.

例 9.8 “Using ConstraintDescriptor” shows how to retrieve default constraint attributes (such as message template, groups etc.) as well as custom constraint attributes (`piecesOfLuggagePerPassenger`) and other metadata such as the constraint's annotation type and its validators from a `ConstraintDescriptor`.

例 9.8. Using `ConstraintDescriptor`

    //descriptor for the @LuggageCountMatchesPassengerCount constraint on the

    //load(List<Person>, List<PieceOfLuggage>) method

    ConstraintDescriptor<?> constraintDescriptor = carDescriptor.getConstraintsForMethod(

                    "load",

                    List.class,

                    List.class

    ).getCrossParameterDescriptor().getConstraintDescriptors().iterator().next();

    //constraint type

    assertEquals(

                    LuggageCountMatchesPassengerCount.class,

                    constraintDescriptor.getAnnotation().annotationType()

    );

    //standard constraint attributes

    assertEquals( SeverityInfo.class, constraintDescriptor.getPayload().iterator().next() );

    assertEquals(

                    ConstraintTarget.PARAMETERS,

                    constraintDescriptor.getValidationAppliesTo()

    );

    assertEquals( Default.class, constraintDescriptor.getGroups().iterator().next() );

    assertEquals(

                    "There must not be more than {piecesOfLuggagePerPassenger} pieces of luggage per " +

                    "passenger.",

                    constraintDescriptor.getMessageTemplate()

    );

    //custom constraint attribute

    assertEquals(

                    2,

                    constraintDescriptor.getAttributes().get( "piecesOfLuggagePerPassenger" )

    );

    //no composing constraints

    assertTrue( constraintDescriptor.getComposingConstraints().isEmpty() );

    //validator class

    assertEquals(

                    Arrays.<Class<?>>asList( LuggageCountMatchesPassengerCount.Validator.class ),

                    constraintDescriptor.getConstraintValidatorClasses()

    );

版权 © 2009 - 2013 Red Hat, Inc. & Gunnar Morling

- 上一页第 8 章 Bootstrapping
- 上一级
- 起始页
- 下一页第 10 章 Integrating with other frameworks