Injecting and Binding Objects to Spring MVC Controllers--转

I have written two previous posts on how to inject custom, non Spring specific objects into the request handling methods of a controller in Spring MVC. One was using HandlerMethodArgumentResolver
the other with @ModelAttribute.

An adventurous reader of these posts would have discovered, that even if you remove the implementedHandlerMethodArgumentResolver or the @ModelAttribute annotation the custom objects would still be instantiated and provided to the controller method!

Yes, try it!

The only problem is that the properties of the instantiated object would be null, that is they won't have been initialized (this won't be the case though, if these properties are instantiated in the default constructor).

So if we have a our Person object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person {
  
     private String firstName;
     private String lastName;
 
     public Person() {
     }
  
     public void setFirstName(String fname) {
         this .firstName = fname;
     }
 
     public String getFirstname() {
         return firstName;
     }
 
     public void setLastName(String lname) {
         this .lastName = lname;
     }
 
     public String getLastName() {
         return lastName;
     }
  
}


And a controller with the Person object as method parameter:

1
2
3
@RequestMapping (value= "/index" , method = RequestMethod.GET)
public String printWelcome(ModelMap model, Person person) {
}


Typing in the URL that maps into the controller above would not give any error, even though there is noHandlerMethodArgumentResolver defined or @modelAttribute. Spring would still instantiate the Person Object by calling its default constructor and make it available to the controller. 

This is the default object creation and dependency injection available to controllers in Spring MVC.

If the object you need injected, can be created and ready for use without the need of any external arguments supplied to the constructor, then you are good to go, but often that not, this is not the case. The object creation would either need external inputs for its creation or for its initialization after creation (setting properties etc). This is where an object Factory likeHandlerMethodArgumentResolver comes in handy.

But there is another alternative way Spring makes available which in the absence of HandlerMethodArgumentResolver, and @ModelAttribute, makes it possible to have objects created, initialized with required properties and ready for use. This is via Spring's data binding feature.

As we have seen, by just having a custom object, as a controller's method argument, guarantees that it would be instantiated...data binding, on the other hand guarantees that the created object is fully initialized and values are bound to its properties.

But if data binding guarantees object initialization, where then, does the value used in binding comes from?...from the HTTP request body...

We would look into the various ways values coming from a request can be mapped into properties of an object i.e. data binding to the object provided to the controller.

Binding Values from HTML Form

If you have an HTML form whose action URL points to a controller method, and the field names of the form inputs corresponds to the properties of a custom object that is part of the method arguments of the controller, then Spring would automatically extract the values sent through that form and bind it as properties of the object. This object is usually referred to as a Form Backing Object.

 

For this to work, the Form backing object would need to follow the  JavaBean convention: which is, among other things, have public setter methods (and getter methods) that can be used to set and retrieve the object's properties.


For example if the Person class above serves as the Form backing object and we have the HTML thus:

<form action="/person" method="post">
<input name="firstName" type="text" />
<input name="lastName" type="text" />
<input type="submit" />
</form>

and the controller is as before: 

1
2
3
@RequestMapping (value= "/index" , method = RequestMethod.POST)
public String printWelcome(ModelMap model, Person person) {
}

The Person object instantiated would have its properties automatically bound with whatever is sent through the form.

Binding Values From Path Variables and Request Parameters

Since the data binding is done via extraction of values from the HTTP request and mapped to the object, path variables and request parameters can also be used to pass across values to be bound to the backing object that would be injected into the controller method.

For example using request parameters, this URL:

http://example.com/person/?firstName=akpos&lastName=merenge

would automatically bind the firstName and lastName to the Person object in the printWelcome method just as the HTML form did. The only thing is that the @RequestMapping's method needs to be changed to RequestMethod.GET

Same data binding can also be achieved using the values retrieved from the path variable. Just that the controller method would need some slight modification.

It would have to be updated to have the variable used to designate the path variable match the properties of the object...

To illustrate let's say we have this URL:

http://example.com/person/akpos/merenge

In other to have the values specified, i.e, the /akpos/merenge/ part, mapped to the Person object, the printWelcomemethod would need to be updated thus:

1
2
3
@RequestMapping (value= "/index/{firstName}/{lastName}" , method = RequestMethod.POST)
public String printWelcome(ModelMap model, Person person) {
}


The changes being in the request mapping /index/{firstName}/{lastName}.

The above ensures the values in the URL's path variable would be extracted and bound to the Person Object.

Validating Data Binding

A common operation with data binding is data validation.

When the value to bound is coming from an external source, (HTML form etc) it makes sense to want to specify some validation rule that inputs needs to be adhere to before the actual values are bound to the object. Data validation with data binding is a topic that could deserve a stand alone post on it's own, but I would just quickly point out how it is achieved in Spring.

There are two things needed to be done. First is to specify the validation constraints on the object that we want to bind it properties. Second is to update the handling controller method with the necessary annotation needed to enforce the validation and catch any validation error that occurs.

For specifying the validation constraints/rules, Spring supports declarative bean validation as defined in JSR-303. This support is activated, automatically, if a JSR 303 provider is found in the application's class path. A popular provider is the Hibernate Validator. You can add this via Maven:

1
2
3
4
5
< dependency >
   < groupid >org.hibernate</ groupid >
   < artifactid >hibernate-validator</ artifactid >
   < version >${version}</ version >
</ dependency >


Once you have such a validator on your class path you can proceed to enforce validation rules using annotation on the object to be validated. For example, on our Person object, to enforce that no empty value is bound and only alphabets allowed, we would annotate like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person {
  
     @NotBlank
     @Pattern (regexp = "^[A-Za-z]*$" )
     private String firstName;
     @NotBlank
     @Pattern (regexp = "^[A-Za-z]*$" )
 
     public Person() {
     }
  
     public void setFirstName(String fname) {
         this .firstName = fname;
     }
 
     public String getFirstname() {
         return firstName;
     }
 
     public void setLastName(String lname) {
         this .lastName = lname;
     }
 
     public String getLastName() {
         return lastName;
     }
  
}


We have used the @NotBlank and @Pattern annotation to achieve this validation rule. For more rules you should look at theHibernate Validator documentation

Now that the validation rule has been applied, the controller method needs to be updated to be aware of the validation rules and be able to catch any violation. To do that you annotate the object being bound with @Valid and right next to the object, follow with Spring's BindingResult (Note that the BindingResult would need to be placed right after the backing object that is annotated with @Valid for this to work.)

so our printWelcome method would be:

1
2
@RequestMapping (value= "/index/{firstName}/{lastName}" , method = RequestMethod.POST)
public String printWelcome(ModelMap model, 
1
2
3
4
5
6
7
8
9
10
                 @Valid Person person, BindingResult result) {
 
if (result.hasErrors) {
// extract errors and put in model
// return view
} else {
// continue with normal code execution
// return view
}
}


As can be seen above, the @Valid annotation is used to apply the validation rules on the Person Object. If any violation, then the result object is updated accordingly (Spring also updates the Model with violation details) and in the controller, you can check if validation passed or failed, extract the error message, and determine what should happen in the view.

In practice though, you may not have to manually extract the error details and update the view manually, most Template technologies have macros that handles the task of extracting validation error and displaying them. I am familiar with theFreemarker template engine, and Spring has got macros (Spring.FTL) for handling common task in Freemarker. TheshowError macro handling displaying of validation errors.

The JSR 303 provides an extensive set of definition for enforcing bean validation. Do check the documentation for more information.

reference from:

http://geekabyte.blogspot.com/2014/10/injecting-and-binding-objects-to-spring.html

你可能感兴趣的:(spring mvc)