Extending handler method argument resolution in Spring MVC

Spring MVC data binding is really flexible and powerful, it can bind request parameters, URI templates variables, cookie values, HTTP headers… directly into your domain objects, but what if you want a custom resolution mechanism?

I will show you how to do it with a simple example. Imagine you want to get the user country name directly in you controller. The handler method I want to have looks like the following:

@RequestMapping(value="/", method=RequestMethod.GET)
public String home(@Country String userCountry, Model model) {
    ...
}

Notice that we have a new annotation called @Country, this is our custom annotation that we are gonna use to know where to inject the country name. Let’s create the annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Country {
}

Don’t forget to set the retention to runtime, otherwise the annotation won’t be available. Now it’s time to define how we want to resolve the argument annotated with @Country. The HandlerAdapter takes care of this resolution but it delegates to the WebArgumentResolvers for this task. We’ll implement our custom WebArgumentResolver:

public class CountryWebArgumentResolver implements WebArgumentResolver {

    private static final String WS_URL = 
        "http://api.ipinfodb.com/v3/ip-city/?key={apiKey}&ip={userIp}&format=xml";
    private static final String API_KEY = "yourKey";
	
    private RestTemplate restTemplate = new RestTemplate();
	
    public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
        // Get the annotation		
        Country countryAnnotation = 
            methodParameter.getParameterAnnotation(Country.class);

        if(countryAnnotation != null) {
            HttpServletRequest request = 
                (HttpServletRequest) nativeWebRequest.getNativeRequest();
			
            // Get user IP
            String userIp = request.getRemoteAddr();
			
            // Call geolocalization web service
            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.set("Accept", "text/xml");

            HttpEntity<UserInfo> userInfo = restTemplate.exchange(WS_URL, HttpMethod.GET, new HttpEntity<UserInfo>(requestHeaders), UserInfo.class, API_KEY, userIp);

            return userInfo.getBody().getCountryName();
        }
		
        return UNRESOLVED;
    }
}

As you can see, it’s quite straightforward, we just need to implement a single method. What this resolver is doing, is basically checking if the parameter is annotated with @Country, getting the user IP and calling a web service to get geolocalization data of the user IP (notice that an extra class has been created, UserInfo, in order to bind the web service parameters). I’ll be using IPInfoDB as the geolocalization service.

We just have one step left: define our new resolver in the handler adapter. Our custom web argument resolver will kick in before any Spring MVC default resolver.

	<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
		<property name="customArgumentResolver">
			<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
		</property>
	</bean>

Note: if you are using the Spring 3 MVC namespace (

<mvc:annotation-driven/>

), remember to place your handler adapter definition on top of it.

In Spring 3.1, you can user the mvc namespace to register the argument resolver:

	<mvc:annotation-driven>
		<mvc:argument-resolvers>
			<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
		</mvc:argument-resolvers>
	</mvc:annotation-driven>

We are now ready to use the @Country annotation in our controller :)

	@RequestMapping(value="/", method=RequestMethod.GET)
	public String home(@Country String userCountry, Model model) {
		
		String message = null;
		
		if(userCountry.equals("SPAIN")) {
			message = "It seems you are in the same country as I am, up for a beer?";
		} else {
			message = "It seems we are not close to each other, will let you know when I am in " + userCountry;
		}
		
		model.addAttribute("message", message);
		
		return "home";
	}
About these ads

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s