Monday, October 06, 2014

Using Spring's Type Converter to Inject Objects into Spring MVC Controllers

The pattern of having the identity of a resource articulated in the URL is not uncommon; In fact it is one of the ways of expressing RESTful end points...for example in an application that displays details about people, you can have a URL expressed thus:

http://www.example.com/people/777

where 777 is the number that serves as the identifier used to fetch the resources, in this case that could be the corresponding Person Object with an identifier of 777.

It wont be taking it too far then, if we see the number 777, as a direct mapping to the Person object with an id of 777.

In fact the controller method that maps the the URL above may simply have logic that get's the 777 part of the URL and use it to fetch the corresponding entry in the database backing the application. for example:


@RequestMapping("/people/{id}")
public String getPerson(@PathVariable int id, ModelMap model) {

Person person = new PersonRepository().loadById(id);
model.addAttribute("id", person.getId());
model.addAttribute("firstName", person.getFirstName());
model.addAttribute("lastName", person.getLastName());

return "person";
}

This works. Only problem is that you may have to repeat this code in every controller that needs to fetch a Person object using Id gotten via the path variable in the request.

Won't it be nice to have a mechanism that sort of automatically maps the identifier, which is basically the object's identity, to the needed object? Well Spring has got you covered for this.

You can use Spring's Type Converters to achieve this.

A Converter is a piece of code you write which Spring uses to handle translation from one type to another. In this case we would be interested in translating from the String representation of the identify of a Person to the actual Person object.

There are basically 2 steps of getting the Spring converter working and used in Spring MVC.
  1. Implement the Converter Interface.
  2. Register the converter for use.
Let's see what these two steps look like.

Implement the Converter Interface

You can a converter going by implementing the Spring's Converter interface which is a really simple interface with only one defined method "convert" that needs to be implemented. The convert method takes the type to convert from and return the type to convert into. 

So for example we could have a String-to-Person converter in this form:

public class StringToPersonConverter implements Converter {

    public StringToPersonConverter() {

    }

    @Override
    public Person convert(String id) {
        Person person = new PersonRepository().loadById(id);
        return person;
    }
}

The convert method takes in String (type to convert from) and return a Person (type to convert into)

That is all to it. Next thing to do, is to configure Spring to use this converter by registering it.

Registering the Converter for Use.

Configuring Spring to use your converter involves wiring up a ConversionServiceFactoryBean, a class provided by Spring. This class needs to be wired up as a bean. It is via this bean you register your custom converter by setting  via the ConversionServiceFactoryBean's converters property.

The ConversionServiceFactoryBean is basically what supplies to Spring the necessary type converters needed for a specific type conversion. So to register our  StringToPersonConverter, you would have:




  
    
  






If using Java Config, you would have:

@EnableWebMvc
@ComponentScan(basePackages = {"com.springapp.mvc"})
@Configuration
public class Config extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {
        formatterRegistry.addConverter(new StringToPersonConverter());
    }

Now with the configuration done, you can proceed to use the converter in your application. You let the converter kick into action by having the type to convert as the name of the variable used for the path variable or request parameter. For example, to convert from String to Person using @RequestParam, you make sure the request parameter is the named "person". For example:


http://www.example.com/people/?person=777

and the controller handling that request could look like:

@RequestMapping(value="/people", method = RequestMethod.GET)
public String printWelcome(ModelMap model,
                               Person person) {
     // Person would be injected having being converted using the string 777
     return "hello";
}

While via path variables, the URL would take this form:

http://www.example.com/people/777


And the handling method would look thus:
@RequestMapping(value="/people/{person}", method = RequestMethod.GET)
public String printWelcome(ModelMap model,
                               Person person) {
   // person would be converted to Person Object
   return "hello";
}

Since the path variable contains "person".

No comments: