Customized Request Mapping Handler Mapping for Spring Mvc

Keywords: Spring xml encoding JSP

Above Request Mapping Handler Mapping Matching in Spring MVC We mentioned that Spring not only matches url, method, contentType and other conditions, but also matches the customized conditions provided by users. The customized conditions provided by users are encapsulated using RequestCondition s. In this paper, I work on a practical case to explain if the request matching process is customized using Request Conditions, and will explain the problems that need attention in this matching process.

1. Background description

The main project I am engaged in is the sale of housing resources, where each user has his own web site, which is configured through a secondary domain name, such as A user's web site is a.house.com, B user's web site is b.house.com. In addition, we also provide a number of templates for each user to display the housing resources. The advantage of this design is that if users need to SEO their websites, then they can deal with it in a unified way, and through the secondary domain name we can know who the users of the current website belong to. But the problem is that, for example, for different templates of the same page, although the main body is the same, the details of the page are very different, so it is necessary to use different interfaces to process them, but this requires the front-end to determine which set of templates the current user is using each time when calling the interface, and then to make different interfaces. Call. In this way, as template pages become more and more, the code will become extremely difficult to maintain.

In order to solve the above problems, the root of the problem lies in introducing the complexity of different templates into the front-end. If the front-end requests the same page, regardless of the current user's template, it can use the same url to request, then the complexity will be shielded. So the problem that needs to be solved here is that when the front end requests the server through a domain name link, such as a.house.com/user/detail, how can the server get the current set of templates by the domain name requested, and then distribute the requests to the interface that can handle the current template.

This problem can actually be solved by customizing Request Conditions. First, we write two interfaces on the server. The signatures of the two interfaces are identical, including the attributes in the @RequestMapping annotation. The two interfaces will be annotated with a custom annotation. The annotation parameter values are used to indicate which sets of templates the current interface can handle. When the front-end requests a.house.com/user/detail, in the custom Request Conditions, it first obtains which user is currently using which template according to the domain name requested, and then obtains which template is supported by the custom annotation annotated by the Handler (a method of processing requests in Controller) represented by the current Request Mapping Info, if the two are matched. That means that the current handler can handle the current request, so that the purpose of request forwarding can be achieved.

2. Implementation code

Here we first show how to write the target interface. From the following code, we can see that the parameters in @RequestMapping used by the two interfaces are identical, but the parameters of @Template annotation used by the two interfaces are different, which achieves the purpose of separating the interfaces of different templates, thus shielding the interfaces caused by different templates. The complexity of different ways of reasoning, and also provides a unified way of requesting, that is, / user/detail to the front-end, enhance the maintainability of the front-end code. The following is the specific statement of the interface:

@Controller
@RequestMapping("/user")
public class UserController {

  @Autowired
  private UserService userService;

  @Template(1)
  @RequestMapping(value = "/detail", method = RequestMethod.GET)
  public ModelAndView detailForTemplateOne(@RequestParam("id") long id) {
    System.out.println("handled by detailForTemplateOne");
    ModelAndView view = new ModelAndView("user");
    User user = userService.detail(id);
    view.addObject("user", user);
    return view;
  }

  @Template(2)
  @RequestMapping(value = "/detail", method = {RequestMethod.GET, RequestMethod.POST})
  public ModelAndView detailForTemplateTwo(@RequestParam("id") long id) {
    System.out.println("handled by detailForTemplateTwo");
    ModelAndView view = new ModelAndView("user");
    User user = userService.detail(id);
    view.addObject("user", user);
    return view;
  }
}

The declaration of the @Template annotation is relatively simple and requires only specifying the template it supports:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Template {
  int[] value();
}

As we mentioned earlier, if you need to customize the matching process of RequestMapping Handler Mapping, you need to register a RequestCondition object for each registered RequestMapping Info. The following is how the object is declared:

// Because each RequestMapping Info holds a RequestCondition object and these objects are stateful,
// Therefore, it must be annotated using prototype to indicate that a new object is created every time it is retrieved from BeanFactory.
@Component
@Scope("prototype")
public class TemplateRequestCondition implements RequestCondition<TemplateRequestCondition> {

  private int[] templates;

  public TemplateRequestCondition(int[] templates) {
    this.templates = templates;
  }

  // Here the combine() method is mainly for the composite type Request Mapping, which can be held.
  // Two Mapping info, so you need to merge the two Mapping info. The merging process is actually to merge each Request Mapping Info.
  // Each condition is merged. Here is the merge of RequestCondition conditions.
  public TemplateRequestCondition combine(TemplateRequestCondition other) {
    int[] allTemplates = mergeTemplates(other.templates);
    return new TemplateRequestCondition(allTemplates);
  }

  // Determine whether the template selected by the user corresponding to the current request is consistent with the template that the current interface can handle.
  // If consistent, the current RequestCondition is returned, where RequestMapping Handler Mapping matches the request.
  // If the matching result of the current condition is not null, it means that the current condition can be matched, and if the return value is null, it means that it cannot match.
  public TemplateRequestCondition getMatchingCondition(HttpServletRequest request) {
    String serverName = request.getServerName();
    int template = getTemplateByServerName(serverName);
    for (int i = 0; i < templates.length; i++) {
      if (template == templates[i]) {
        return this;
      }
    }

    return null;
  }


  // Comparing two RequestCondition objects, the main point here is that if there are two Mapping registrations of the same type, then the
  // These two Mappings are sorted to determine which Mapping is more suitable for processing the current request request request request
  public int compareTo(TemplateRequestCondition other, HttpServletRequest request) {
    return null != templates && null == other.templates ? 1
      : null == templates && null != other.templates ? -1 : 0;
  }

  // What is actually used in the project is to obtain the template selected by the user according to the domain name currently requested.
  private int getTemplateByServerName(String serverName) {
    if (serverName.equalsIgnoreCase("peer1")) {
      return 1;
    } else if (serverName.equalsIgnoreCase("peer2")) {
      return 2;
    }

    return 0;
  }

  // Merge the two template data
  private int[] mergeTemplates(int[] otherTemplates) {
    if (null == otherTemplates) {
      return templates;
    }

    int[] results = new int[templates.length + otherTemplates.length];
    for (int i = 0; i < templates.length; i++) {
      results[i] = templates[i];
    }

    for (int i = templates.length; i < results.length; i++) {
      results[i] = otherTemplates[i - templates.length];
    }

    return results;
  }
}

The above is the core code for customizing the matching process of RequestMapping Handler Mapping. The main concern here is the getMatching Condition () method, which first gets the domain name of the current request and then compares it with the templates supported by the current RequestMapping Info. If it supports it, it returns the current ReqeustCondition object. Return empty. It should be pointed out that if the getMatchingCondition() method returns a value that is not empty when matching the request with the RequestCondition, it means that they are matched, otherwise they are not matched.

With regard to the injection of RequestCondition s, we need to rewrite the getCustomMethodCondition() method of RequestMapping Handler Mapping, which declares each method as a RequestMapping Info object and calls RequestMapping Handler Mapping when RequestMapping Handler Mapping scans all bean s (Controller objects) that can handle requests in BeanFactory The rMapping. getCustomMethodCondition() method obtains the conditions currently registered by RequestMapping Info, which by default returns null. Here is the rewritten Request Mapping Handler Mapping:

@Component
public class TemplateHandlerMapping extends RequestMappingHandlerMapping {
  @Override
  protected RequestCondition<?> getCustomMethodCondition(Method method) {
    method.setAccessible(true);
    Template template = method.getAnnotation(Template.class);
    int[] templates = null == template ? new int[0] : template.value();
    return obtainApplicationContext().getBean(RequestCondition.class, templates);
  }
}

Rewriting RequestMapping Handler Mapping here is actually a custom Handler Mapping object. When Spring is initialized, it determines whether there is a Handler Mapping object in the current BeanFactory, if there is one, it uses user-defined, if not, it creates a Request Mapping Handler Mapping for processing requests. The following is the configuration of Spring's xml file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="mvc"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

Run the above code in a tomcat container using Spring, and then access the respective http://a.house.com/user/detail?id=1 and http://b.house.com/user/detail?id=1. You can see that the console prints the following logs:

handled by detailForTemplateOne
handled by detailForTemplateTwo

This shows that we have successfully customized the request process of Request Mapping Handler Mapping. Through this customization method, we effectively shield the complexity of requests brought by different templates from the front-end, and separate the processing methods of different templates from the same request in the back-end.

3. pay attention.

There are three main points to be explained about the definition of Request Conditions:

  • When Dispatcher Servlet is initialized, Spring judges whether there is a custom Handler Mapping object in the current BeanFactory in two ways: first, it judges whether there is a bean named handler Mapping in the current BeanFacotry; second, it directly obtains all the beans in the current BeanFactory that implement the Handler Mapping interface, which is also a default method. If an unreachable Andler Mapping object is specified in these two ways, then a default Request Mapping Handler Mapping object is created to process the request.
  • If a custom Request Mapping is used to process requests, try not to use the <mvc: annotation-driven/> tag in Spring's configuration file, because Spring registers an object of Request Mapping Handler Mapping in BeanFactory when parsing the tag, which will interfere with our custom Handler Mapping, because I need to pay attention to the specific request. The Handler Mapping they define handles the Request Mapping Handler Mapping provided by Spring. If you really need to use this tag, we can implement a PriorityOrdered interface for our custom Handler Mapping, which ensures that it can determine whether it can process the current request before RequestMapping Handler Mapping.
  • Two almost complete interfaces are defined in the UserController above. For Spring, two identical interfaces are not allowed in the container. However, in the absence of customized RequestConditions, Spring incorporates the judgment of the RequestConditions object when making two interface judgments (that is, the judgment of the RequestMapping Info object encapsulating the interface). If it is customized and the two RequestMapping Infos hold different RequestConditions objects, then Spring will also include the judgment of the RequestConditions object. So even if the other conditions are the same, the two RequestMapping Infos are different. Specific readers can read the source code of the Mapping Registry. assertUniqueMethodMapping () method.

4. summary

This paper first introduces a problem of using multiple templates in one project, then introduces the solution of using RequestCondition to solve this problem, then demonstrates the solution in the form of code, and finally introduces the problems needing attention when using RequestCondition. In general, this paper illustrates how to customize the matching process of Request Mapping Handler Mapping with a practical case.

Posted by galewis on Thu, 16 May 2019 03:18:47 -0700