Spring5-Processor handlers

The central point of the Spring Web application is the Dispatcher Servlet (see more details) Spring 5 Source Parsing-On the Life Cycle of Spring Dispatcher Servlet ).This is the central entry for all incoming requests.But if we don't, we can't do anything without a lot of handlers.

First of all, in this article, we'll start by explaining what a handler is.Later, we will introduce two types of handlers in some Spring frameworks.Finally, let's add a little salt to get what we've learned to the ground, and we'll write our own handler.

Two handler types in Spring

First, in the Spring world, what exactly do these handlers do?Simply put, this is the same as what we hear a sentence or see a scene and then have a related response, which is transformed from many processes to something that our cortex understands.From the machine language point of view is lexical analysis, grammatical analysis, Okay, you know the importance of compiled language is also the basic importance. Back to the framework, for Spring, these handlers are an element that converts user actions into Spring understandable.When it comes to user actions, we can consider URL types like http://xxx.com/login.Our handler, which acts as a translation process here, will try to find out which controller should be called to handle this address.Often, as we know by writing Spring controller code, handlers can look for the @RequestMapping annotation and check which mappings match the / login URL.As we know from the previous article, this handler will be called inside Dispatcher Servlet.

More precisely, there are two types of handlers in Spring.The first is handler mappings.Their role positioning is exactly the same as described earlier.They try to match the current request with the corresponding controller and the methods within it.The second is handler adapter.The handler adapter takes the mapped controllers and methods from the handler mappings and calls them.This type of adapter must implement the org.springframework.web.servlet.HandlerAdapter interface, which has only three methods:

  • boolean supports(Object handler): Check if the object passed in a parameter can be handled by this adapter
  • ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler): Translate the request into a view.
  • long getLastModified(HttpServletRequest request, Object handler): Returns the last modified date in milliseconds for a given HttpServletRequest.

However, here are some important changes to note in the Spring version.As a DefaultAnnotationHandler Mapping, the Processor Adapter of the AnnotationMethodHandler Adapter or AnnotationMethodHandler ExceptionResolver has been obsolete since Spring version 3.2. You can also see in Spring 4.x that it has been removed within Spring 5 and replaced by RequestMappingHandler Mapping, RequestMappingHandler Adapter and ExceptionHandler ExceptionResolvEr.These new classes allow you to customize the mapping.In addition, the object being processed is converted to its method representation by introducing the org.springframework.web.method.HandlerMethod class in since version 3.1.This way we can tell what type of object is returned or what parameters are expected (open the source code to see such comments if you're scratching).

handler in Spring Framework

In addition to the handler adapters already provided, Spring also has local handler mappings, the most basic of which is the org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping class.It matches the URL to the corresponding bean.For example, see the following configuration:

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

As you can see, this configuration is not practical in many cases of URLs.Some of the more flexible processing mappers are org.springframework.web.servlet.handler.SimpleUrlHandlerMapping.Instead of creating bean s for each request, we can create a mapping file that contains URLs as keys and controller s as values, as shown in the configuration below:

<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 
  <property name="mappings">
    <props>
      <prop key="/friends.html">FriendsController</props>
  </property>
</bean>

However, in SimpleUrlHandlerMapping, handling slightly more complex URL s can also be a headache.That's why we use DefaultAnnotationHandlerMapping or the latest version of Spring

Reasons for org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.Their mapping detection is annotation based.This keeps all the logic in the Java code block, 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";
    }
}

Unlike previous handlers, annotation-based allows for more flexible configuration.Not only do you need to do all sorts of tedious configuration in XML, once there are many URLs, imagine XML, all kinds of headaches. Now through annotations, we can receive URLs from different forks on a road in one controller.This handler will be activated when <mvc:annotation-driven/>is defined in the configuration file.Additionally, for finer-grained processing of controllers annotations, we can enable them by adding <context:annotation-config /> (which can be omitted later, since the latter itself has this capability) and <context:component-scan base-package = "path.with.my.services.and.controllers"/>.

Write a custom Spring handler program

Now let's take a closer look at Spring mapping handlers.We implement our own URL handler.It's really simple (because you only need to do the most basic processing), so we'll replace RequestMappingHandlerMapping and have a simple mapper handle URL addresses.Our mapper will only handle static URLs, such as: /home.html.It does not need to or cannot get dynamic parameters from the method signature and it does not need to know the @PathVariable element.The main goal is to let you discover the steps Spring takes to process a request.

Our handler will extend RequestMappingHandlerMapping and override its methods (some of which can be found from RequestMappingInfoHandlerMapping are actually a few abstract methods that override or implement AbstractHandler MethodMapping):

  • protected void registerHandlerMethod(Object handler,Method method,RequestMappingInfo mapping):
  • protected boolean isHandler(Class beanType): Checks whether a bean meets the conditions of a given handler.

  • protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType): Provides a method for mapping a given Method instance, which represents the method of processing (for example, the URL corresponding to the method using the @RequestMapping annotation controller).

  • Protected Handler Method handleNoMatch (Set request MappingInfos, String lookupPath, HttpServletRequest request): Called when a matching processing method cannot be found for a given HttpServletRequest object.
  • Protected void handleMatch (RequestMappingInfo, String lookupPath, HttpServletRequest request): Called when a matching processing method is found for a given HttpServletRequest object.

Before writing this handler, let's write a custom comment for @RequestMapping:

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

The only property is the value representing the URL path, which is identical to the value property in the @RequestMapping annotation.Now we can pass in our handler mapping class.The course reviews internally.That's why it doesn't include any additional comments in the usual Text 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 a new HandlerMapping to our application context.See the following XML-based configuration:

<bean class="com.mypackage.handler.DumberRequestHandlerMapping">//Configure it here based on your own package
  <property name="order" value="0" />
</bean>

Note that the existence of the order attribute determines that requests are processed sequentially by HandlerMapping.Here, if DumberRequestHandlerMapping can be applied to a request, Spring will use it immediately without having to look for another available handler.

The last thing to do is to annotate the method using @DumberRequestMapping:

@Controller
public class TestController {
 private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
    @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 http://localhost:8084/test, you will see that the properties of the request set in the handleMatch method of the DumberRequestHandlerMapping exist.If you have a log of your application deployed, you will see some information about the controller execution process:

2017-08-05 23:31:00,027 [http-bio-8084-exec-1] [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]-[DEBUG] Looking up handler method for path /test   //First found in RequestMappingHandlerMapping, i.e. first found a way to process the logic with the @RequestMapping annotation
2017-08-05 23:31:00,028 [http-bio-8084-exec-1] [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]-[DEBUG] Did not find handler method for [/test]   //No corresponding processing logic was found in RequestMappingHandlerMapping
2017-08-05 23:31:00,028 [http-bio-8084-exec-1] [com.migo.sso.DumberRequestHandlerMapping]-[DEBUG] Looking up handler method for path /test
  //Looking in DumberRequestHandlerMapping, we found that the method annotated by @DumberRequestMapping can be handled, so handle it.
2017-08-05 23:31:00,029 [http-bio-8084-exec-1] [com.migo.sso.DumberRequestHandlerMapping]-[DEBUG] [DumberRequestHandlerMapping] handleMatch info {[/test],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}, lookupPath =/test, request =org.apache.catalina.connector.RequestFacade@24a7274b
2017-08-05 23:31:00,030 [http-bio-8084-exec-1] [com.migo.sso.DumberRequestHandlerMapping]-[DEBUG] Returning handler method [public java.lang.String com.migo.sso.controller.TestController.testSession(javax.servlet.http.HttpServletRequest)]
2017-08-05 23:31:00,030 [http-bio-8084-exec-1] [org.springframework.beans.factory.support.DefaultListableBeanFactory]-[DEBUG] Returning cached instance of singleton bean 'testController'
2017-08-05 23:31:00,030 [http-bio-8084-exec-1] [org.springframework.web.servlet.DispatcherServlet]-[DEBUG] Last-Modified value for [/test] is: -1
2017-08-05 23:31:00,040 [http-bio-8084-exec-1] [com.migo.sso.controller.TestController]-[DEBUG] Is dumber request ?true
2017-08-05 23:31:00,040 [http-bio-8084-exec-1] [com.migo.sso.controller.TestController]-[DEBUG] Handled time ?21230126522470
Handled time ?17452005683775

We can see that handler mapping is a key concept in the Spring ecosystem.All URLs are handled by the corresponding handler, which allows Spring to match the incoming HTTP request with the method of the annotated controller configured.We also saw how to filter requests based on different rules, such as Content-Type, Accept, or other headers or HTTP methods.We also wrote a poor version of Spring's RequestMappingInfoHandlerMapping, which intercepts some URL processing and outputs the results to the user through the view.

To sum up, there's a way to determine where the request should be handled (we usually do this with comments), and that's all, there's so much verbosity, and finally so much straightforward

Keywords: Spring xml JSON Java

Added by sara_kovai on Tue, 14 May 2019 15:12:36 +0300