Handlers in Spring

Central piece of Spring web application is DispatcherServlet. It's a central point of entry for all incoming requests. But it can't do nothing without another important concept, handlers.

Firstly, in this article we'll discover this famous concept of handlers. At this occasion, we'll present two handler types with some of native Spring handlers. The second part will be more practical and we'll focus on writing our own handler.

What are two handler types in Spring ?

First of all, let's start by defining abstract concept of handler in Spring's world. A handler is kind of translator which translates user actions into elements understandable by Spring. For example, as an "user action" we can consider URL typing like http://myfalsesitedot.com/login . Handler, treated here as translator, will try to find which controller should be invoked for this address. In our case, handler can look for annotation @RequestMapping and checks which mapping matchs with /login URL query. As you can deduce right now, this handler will be called at DispatcherServlet's level.

To be more precise, two types of handlers exist in Spring. The first family is handler mappings. Theirs role is exactly the same as described previously. They try to match current request into corresponding controller and/or method. The second family is handler adapter. The handler adapters take the mapped controllers and methods from handler mappings and invoke them. This type of adapters must implement org.springframework.web.servlet.HandlerAdapter interface which has only 3 methods:
- boolean supports(Object handler): checks if object passed in parameter can be handled by this adapter
- ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler): translates request into view.
- long getLastModified(HttpServletRequest request, Object handler): return last modified date in miliseconds for given HttpServletRequest.

But beware, some of important changes were made in recent Spring versions. Handler adapters as DefaultAnnotationHandlerMapping, AnnotationMethodHandlerAdapter or AnnotationMethodHandlerExceptionResolver are deprecated since Spring's 3.2 version in favor of RequestMappingHandlerMapping, RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver. These new classes were added to facilitate mapping customization. In additionally, introduced at 3.1 org.springframework.web.method.HandlerMethod class, helps to translate handled object into its method representation. Thanks to it, we can retrieve, for example, what is type of returned object by this method or which parameters are expected.

Native handlers in Spring

Except already presented handler adapters, Spring has native handler mappers too. The very basic handler mapper is org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping class. It matches URLs into corresponding beans. For example, imagine following configuration:

<bean name="/friends" class="com.waitingforcode.controller.FriendsController" />

As you can see already, this configuration isn't practical in the case of a lot of URLs. Rapidly, XML file will become unreadable. Some of more flexible handling mapper is org.springframework.web.servlet.handler.SimpleUrlHandlerMapping. Instead of creating beans for every request, we can create a mapping file containing URL as a key and controller as the value, like in following configuration:

<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 
  <property name="mappings">
    <props>
      <prop key="/friends.html">FriendsController
    </props>
  </property>
</bean>
But URL's multiplication can be also a problem in the case of SimpleUrlHandlerMapping. It's why DefaultAnnotationHandlerMapping, or in the newest Spring versions, org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping are used. Their mapping detection is based on annotations. So, all logic stays in Java side, for example:
@Controller
public class FriendsController {
	
	@RequestMapping(value = "/friends.html", method = RequestMethod.GET)
	public String showFriendsList() {
		return "friendsListView";
	}
	
	@RequestMapping(value = "/friends/potential-friends.html" method = RequestMethod.GET)
	public String showPotentialFriends() {
		return "potentialFriendsView";
	}
}
Note that, contrary to previous handlers, this one allows a more flexible configuration. Not only you don't need to pollute your XML file with URL-controller relations, but you can control better URLs by grouping them by family inside the same controller. This handler is activated when the entry <mvc:annotation-driven/> is defined in configuration file. In additionally, to handle controllers annotations, we need to enable it by adding <context:annotation-config /> and <context:component-scan base-package="path.with.my.services.and.controllers" />.

Writing own Spring handler

Now we know a little bit more about Spring mapping handlers. Let's play and implement our own handler for URLs. The idea is simple. We'll replace RequestMappingHandlerMapping and make a simple mapper to handler URL addresses. Our mapper will handle only static URLs, like: /home.html. It won't able to take dynamic parameters as well knowing @PathVariable elements from method signature. The main goal is to expose the steps of handling a request in Spring.

The handler will extend RequestMappingHandlerMapping and override 5 its methods:
- protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) :
- protected boolean isHandler(Class beanType) : checks if an bean is eligible for given handler.
- protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) : method which provides mapping for given Method instance which represents handled method (for example a controller's method annotated with @RequestMapping corresponding to URL). A mapping is an encapsulator for some of request mapping conditions, like URL pattern, HTTP methods or params. Its normalized version is presented in this way: {[/test/myUrl],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}
- protected HandlerMethod handleNoMatch(Set requestMappingInfos, String lookupPath, HttpServletRequest request) : invoked when no matching handled method was found for given HttpServletRequest object.
- protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) : called when a matching handled method was found for given HttpServletRequest instance.

Before writing a handler, let's write the annotation which will be less smaller than @RequestMapping ones :

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DumberRequestMapping {
	String value() default "";
}

The only property is value which represents the URL model, exactly like value attribute on @RequestMapping annotation. Now we can pass into our handler mapping class. The class is commented internally. It's why it won't contain any supplementary comment in usual "textual mode".

public class DumberRequestHandlerMapping extends RequestMappingHandlerMapping {
	private static final Logger LOGGER = LoggerFactory.getLogger(DumberRequestHandlerMapping.class);
	
	/**
	 * Checks if handler should be applied to given bean's class. The check is made through looking for DumberRequestMapping annotation.
	 */
	@Override
	protected boolean isHandler(Class<?> beanType) {
		Method[] methods = ReflectionUtils.getAllDeclaredMethods(beanType);
		for (Method method : methods) {
			if (AnnotationUtils.findAnnotation(method, DumberRequestMapping.class) != null) {
				LOGGER.debug("[DumberRequestHandlerMapping] Method "+method+" supports @DumberRequestMapping ");
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Make some operations directly before returning HttpServletRequest instance into mapped controller's method. For example, if you add here some attributes to this object, those attributes will be reachable from controller's method which handles the request. 
	 * RequestMappingInfoHandlerMapping does some of more complicated stuff here like exposing URI template variables or extracting 
	 * "matrix variable".
	 * NOTE : "matrix variables" are name-value pairs within path segments, separated with a semicolon (;). For example in this URL 
	 * /clubs;country=France;division=Ligue 1, Ligue 2) we can find 2 matrix variables: country (France) and division (list composed by 
	 * Ligue 1 and Ligue 2)
	 */
	@Override
	protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
		LOGGER.debug("[DumberRequestHandlerMapping] handleMatch info "+info+  ", lookupPath ="+ lookupPath + ", request ="+request);
		request.setAttribute("isDumber", true);
		request.setAttribute("handledTime", System.nanoTime());
	}
	
	/**
	 * Method invoked when given lookupPath doesn't match with this handler mapping.
	 * Native RequestMappingInfoHandlerMapping uses this method to launch two exceptions : 
	 * - HttpRequestMethodNotSupportedException - if some URLs match, but no theirs HTTP methods.
	 * - HttpMediaTypeNotAcceptableException - if some URLs match, but no theirs content types. For example, a handler can match an URL 
	 * like /my-page/test, but can expect that the request should be send as application/json. Or, the handler can match the URL but 
	 * returns an inappropriate response type, for example: text/html instead of application/json.
	 */
	@Override
	protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> requestMappingInfos, String lookupPath, HttpServletRequest request) throws ServletException {
		LOGGER.debug("[DumberRequestHandlerMapping] handleNoMatch info "+requestMappingInfos+  ", lookupPath ="+ lookupPath + ", request ="+request);
		return null;
	}

	/**
	 * Here we constructs RequestMappingInfo instance for given method.
	 * RequestMappingInfo - this object is used to encapsulate mapping conditions. For example, it contains an instance of 
	 * PatternsRequestCondition which  is used in native Spring's RequestMappingInfoHandlerMapping  handleMatch() method to put URI 
	 * variables into @RequestMapping pattern. 
	 * Ie, it will take the following URL /test/1 and match it for URI template /test/{id}. In occurrence, it will found that 1 
	 * corresponding to @PathVariable represented  by id variable ({id}) and will set its value to 1.
	 */
	@Override
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		LOGGER.debug("[DumberRequestHandlerMapping] getMappingForMethod method "+method+  ", handlerType ="+handlerType);
		RequestMappingInfo info = null;
		// look for @DumberRequestMapping annotation for the Method method from signature
		DumberRequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, DumberRequestMapping.class);
		if (methodAnnotation != null) {
			RequestCondition<?> methodCondition = getCustomMethodCondition(method);
			info = createRequestMappingInfo(methodAnnotation, methodCondition);
		}
		LOGGER.debug("[DumberRequestHandlerMapping] getMappingForMethod method; returns info mapping "+info);
		return info;
	}
	
	/**
	 * Creates RequestMappingInfo object which encapsulates:
	 * - PatternsRequestCondition: represents URI template to resolve. Resolving is helped by UrlPathHelper utility class from
	 * package org.springframework.web.util.
	 * - RequestMethodsRequestCondition: methods accepted by this handler. You can make a test and replace RequestMethod.GET by 
	 * RequestMethod.POST. You will able to observe that our test won't work.
	 * - ParamsRequestCondition: 
	 * - HeadersRequestCondition: headers which should be send in request to given handler should handle this request. You can,
	 * for exemple, put there an header value like "my-header:test" and observe the program behavior.
	 * - ConsumesRequestCondition: this condition allows to specify the content-type of request. We can use it for, for example,
	 * specify that a method can be handled only for application/json request.
	 * - ProducesRequestCondition: this condition allows to specify the content-type of response. We can use it for, for example,
	 * specify that a method can be applied only for text/plain response. 
	 */
	protected RequestMappingInfo createRequestMappingInfo(DumberRequestMapping annotation, RequestCondition<?> customCondition) {
		return new RequestMappingInfo(
				new PatternsRequestCondition(new String[] {annotation.value()}),
				new RequestMethodsRequestCondition(new RequestMethod[]{RequestMethod.GET}),
				new ParamsRequestCondition(new String[]{}),
				new HeadersRequestCondition(new String[] {}),
				new ConsumesRequestCondition(new String[]{}, new String[]{}),
				new ProducesRequestCondition(new String[]{}, new String[]{}, getContentNegotiationManager()),
				customCondition);
	}

}

We need to add new handler mapping to our's application context. This is an example how to do it for XML based configuration :

<bean class="com.mypackage.handler.DumberRequestHandlerMapping">
  <property name="order" value="0" />
</bean>

Note the existence of order property which determines in which order request handler mappings should be sorted by Spring and loaded for all incoming request. For our case, if DumberRequestHandlerMapping can be applied to one request, Spring will use it immediately, without looking for another available handlers.

The last thing to do is adding the annotation for method with @DumberRequestMapping :

@Controller
public class TestController {

	@DumberRequestMapping(value = "/test")
	public String testSession(HttpServletRequest request) {
		LOGGER.debug("Is dumber request ?"+request.getAttribute("isDumber"));
		LOGGER.debug("Handled time ?"+request.getAttribute("handledTime"));
    	return "testTemplate";
	}

}

By executing the URL /test, you'll see that request's attributes set on DumberRequestHandlerMapping's handleMatch method are present. And if you deploy the application's logs, you'll see some interesting informations about handler mapping execution flow:

[DumberRequestHandlerMapping] Method public java.lang.String com.mypackage.controller.TestController.testSession(javax.servlet.http.HttpServletRequest) supports @DumberRequestMapping 
[DumberRequestHandlerMapping] getMappingForMethod method public java.lang.String com.mypackage.controller.TestController.testSession(javax.servlet.http.HttpServletRequest), handlerType =class com.mypackage.controller.TestController
[DumberRequestHandlerMapping] getMappingForMethod method; returns info mapping {[/test],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}
[DumberRequestHandlerMapping] getMappingForMethod method public java.lang.String com.mypackage.controller.TestController.testSession(javax.servlet.http.HttpServletRequest), handlerType =class com.mypackage.controller.TestController
[DumberRequestHandlerMapping] getMappingForMethod method; returns info mapping {[/test],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}
14 mars 2014 14:00:47 org.apache.catalina.core.ApplicationContext log

// ... log entry after querying http://localhost:8080/test URL
[DumberRequestHandlerMapping] handleMatch info {[/test],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}, lookupPath =/test, request =org.apache.catalina.connector.RequestFacade@48007a17
HERE !
Is dumber request ?true
Handled time ?17452005683775

As we can see, handler mapping is a key concept in Spring ecosystem. All URLs are treated by adapted handlers and thanks to it, Spring can match incoming HTTP request and configured controller-method pair. We learned too that requests can be filtered in according to a lot of different rules, like: Content-Type, Accept or another headers or HTTP methods. We've also written a poorer version of Spring's RequestMappingInfoHandlerMapping which intercepts some URLs and outputs adapted view to user.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!