SpringMVC Pre processing Mode for Custom controller Entry

  • 2021-11-13 07:33:35
  • OfStack

Directory Spring Mvc for Custom controller into Parameter Preprocessing HandlerMethodArgumentResolver Interface Description Beginners like code like the following we need to define the following 1 Parameter Resolver Register Custom Resolver SpringMVC Techniques for General Controller 1. Foreword 2. Problem 3. Solution 4. Use 5. Special Requirements 6. Perfection

Spring Mvc Pre-processing Custom controller Entry

When I was a beginner of springmvc framework, I had a question, why can so many parameters be put on controller method, and all of them can get the desired objects, such as HttpServletRequest or HttpServletResponse, various annotations @ RequestParam, @ RequestHeader, @ RequestBody, @ PathVariable, @ ModelAttribute and so on. I believe many beginners have felt deeply.

This article is to explain how to deal with this aspect of content

We can imitate the source code of springmvc, realize some of our own implementation class, and facilitate our code development.

HandlerMethodArgumentResolver Interface Description


package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodArgumentResolver {
    // Used to determine whether the parameter decomposition needs to be processed, and returns true If necessary, the following methods will be called resolveArgument . 
    boolean supportsParameter(MethodParameter parameter);
    // The method that is really used to handle parameter decomposition, and the returned Object Is controller Gets or sets the formal parameter object on the. 
    Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

Example

This example shows how to gracefully convert the incoming information into a custom entity passed into the controller method.

post data:

first_name = Bill

last_name = Gates

Beginners like code like the following


package com.demo.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.demo.domain.Person;
import com.demo.mvc.annotation.MultiPerson;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
@RequestMapping("demo1")
public class HandlerMethodArgumentResolverDemoController {
    @ResponseBody
    @RequestMapping(method = RequestMethod.POST)
    public String addPerson(HttpServletRequest request) {
        String firstName = request.getParameter("first_name");
        String lastName = request.getParameter("last_name");
        Person person = new Person(firstName, lastName);
        log.info(person.toString());
        return person.toString();
    }
}

This code relies heavily on the HttpServletRequest object of javax. servlet-api, and pushes the "job" of initializing the Person object to controller. The code is cumbersome and inelegant. In controller, I only want to use person instead of assembling person. I want code similar to the following:


@RequestMapping(method = RequestMethod.POST)
public String addPerson(Person person) {
  log.info(person.toString());
  return person.toString();
}

Get person directly in the formal parameter list. So what should this be like?

We need to define the following 1 parameter decomposer


package com.demo.mvc.component;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.demo.domain.Person;
public class PersonArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(Person.class);
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String firstName = webRequest.getParameter("first_name");
        String lastName = webRequest.getParameter("last_name");
        return new Person(firstName, lastName);
    }
}

In supportsParameter, it is judged whether the decomposition function needs to be enabled. Here, it is judged whether the formal parameter type is Person class, that is to say, when the formal parameter encounters Person class, the decomposition process resolveArgument will always be executed, and it can also be judged whether the process decomposition is needed based on whether there is a custom annotation specified by us on paramter. The initialization of person is handled in resolveArgument.

Register a custom decomposer

Traditional XML configuration:


<mvc:annotation-driven>
      <mvc:argument-resolvers>
        <bean class="com.demo.mvc.component.PersonArgumentResolver"/>
      </mvc:argument-resolvers>
</mvc:annotation-driven>

Or


<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="customArgumentResolvers">
          <bean class="com.demo.mvc.component.PersonArgumentResolver"/>
    </property>
</bean>

spring boot java code configuration:


public class WebConfig extends WebMvcConfigurerAdapter{
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new CustomeArgumentResolver());
    }
}

General Controller for SpringMVC Techniques

1 generic Controller. For the most part, you no longer need to write any Controller layer code and focus the developer's attention entirely on the Service layer.

1. Preface

Usually in the traditional MVC development, in order to complete a specific function, we usually need to write Controller, Service, Dao layer code at the same time. The code pattern looks like this.

Only the code of Controller layer is posted here, and Service layer is not our focus this time.


// ----------------------------------------- Controller Layer 
@RestController
@RequestMapping("/a")
public class AController {
 @Resource(name = "aService")
 private AService aService;
 
 @PostMapping(value = "/a")
 public ResponseBean<String> a(HttpServletRequest request, HttpServletResponse response) {
  final String name = WebUtils.findParameterValue(request, "name");
  return ResponseBean.of(aService.invoke(name));
 }
}
// -----------------------------------------  Front-end access path 
// {{rootPath}}/a/a.do

2. Problems

As long as you have a few months of Java Web development experience, you should be very familiar with such code, familiar to disgusting. If we pay a little attention, we will find that in the above Controller code, the following things are roughly done:

Collect parameters passed from the front end.

Pass the parameters collected in Step 1 to a method in the corresponding Service layer for execution.

The result of Service layer execution is encapsulated by ResponseBean, which is unique to Controller layer, and then returned to the foreground.

Therefore, after we exclude the rare special cases, we will find that the existence of this so-called Controller layer is a bit thin in general cases. Therefore, this article tries to get rid of this boring repetitive code.

3. Solutions

Go directly to the code. talk is cheap, show me the code.


//  The reason why this is  /lq ,  Instead of  /* ;  Is because  AntPathMatcher.combine  Handling when merging in the method ,  Cause   Front 1 A  /*  Lost 
/**
 * <p>  Directly passed from the foreend Serivce Name + Method name to call Service Method with the same name of layer ; Controller Layer no longer needs to write any code 
 * <p>  Example 
 * <pre>
 *    Front end : /lq/thirdService/queryTaskList.do
 *   Service Method signature corresponding to layer :  Object queryTaskList(Map<String, Object> parameterMap)
 *    Corresponding Service Register to Spring In the container id : thirdServiceService
 * </pre>
 * @author LQ
 *
 */
@RestController
@RequestMapping("/lq")
public class CommonController {
 private static final Logger LOG = LoggerFactory.getLogger(ThirdServiceController.class);
 @PostMapping(value = "/{serviceName}/{serviceMethodName}")
 public void common(@PathVariable String serviceName, @PathVariable final String serviceMethodName, HttpServletRequest request, HttpServletResponse response) {
  //  Collect parameters passed from the foreground ,  And pretreatment 
  final Map<String, String> parameterMap = HtmlUtils.getParameterMap(request);
  final Map<String, Object> paramsCopy = preDealOutParam(parameterMap);
  //  Get the dispatching service name and the corresponding method name of this time 
  //final List<String> serviceAndMethod = parseServiceAndMethod(request);
  //final String serviceName = serviceAndMethod.get(0) + "Service";
  //final String serivceMethodName = serviceAndMethod.get(1);
  
  //  Direct use Spring3.x Newly added @PathVariable Annotation ;  Instead of the custom action above 
  serviceName = serviceName + "Service";
  final String fullServiceMethodName = StringUtil.format("{}.{}", serviceName, serivceMethodName);
  //  Output log ,  Convenient backtracking 
  LOG.debug("### current request method is [ {} ] ,  parameters is [ {} ]", fullServiceMethodName, parameterMap);
  //  Get Spring Registered in Service Bean
  final Object serviceBean = SpringBeanFactory.getBean(serviceName);
  Object rv;
  try {
   //  Call Service Layer method 
   rv = ReflectUtil.invoke(serviceBean, serivceMethodName, paramsCopy);
   //  If the user returns 1 Active constructed FriendlyException
   if (rv instanceof FriendlyException) {
    rv = handlerException(fullServiceMethodName, (FriendlyException) rv);
   } else {
    rv = returnVal(rv);
   }
  } catch (Exception e) {
   rv = handlerException(fullServiceMethodName, e);
  }
  LOG.debug("### current request method [ {} ] has dealed,  rv is [ {} ]", fullServiceMethodName, rv);
  HtmlUtils.writerJson(response, rv);
 }
 /**
  *  Analyze out Service And the corresponding method name 
  * @param request
  * @return
  */
 private List<String> parseServiceAndMethod(HttpServletRequest request) {
  // /lq/thirdService/queryTaskList.do  Analyze out  [ thirdService, queryTaskList ]
  final String serviceAndMethod = StringUtil.subBefore(request.getServletPath(), ".", false);
  List<String> split = StringUtil.split(serviceAndMethod, '/', true, true);
  return split.subList(1, split.size());
 }
 
 //  Will be passed JSON String to the corresponding Map, List Etc 
 private Map<String, Object> preDealOutParam(final Map<String, String> parameterMap) {
  final Map<String, Object> outParams = new HashMap<String, Object>(parameterMap.size());
  for (Map.Entry<String, String> entry : parameterMap.entrySet()) {
   outParams.put(entry.getKey(), entry.getValue());
  }
  for (Map.Entry<String, Object> entry : outParams.entrySet()) {
   final String value = (String) entry.getValue();
   if (StringUtil.isEmpty(value)) {
    entry.setValue("");
    continue;
   }
   Object parsedObj = JSONUtil.tryParse(value);
   //  No JSON String format 
   if (null == parsedObj) {
    continue;
   }
   entry.setValue(parsedObj);
  }
  return outParams;
 }
 //  Return value after successful build execution 
 private Object returnVal(Object data) {
  return MapUtil.newMapBuilder().put("data", data).put("status", 200).put("msg", "success").build();
 }
 //  Return value after a failed build execution 
 private Object handlerException(String distributeMethod, Throwable e) {
  final String logInfo = StringUtil.format("[ {} ] fail", distributeMethod);
  LOG.error(logInfo, ExceptionUtil.getRootCause(e));
  return MapUtil.newMapBuilder().put("data", "").put("status", 500)
    .put("msg", ExceptionUtil.getRootCause(e).getMessage()).build();
 }
}

STEP 4 Use

At this point, the code of Controller layer is finished. In most of the subsequent development work, we will no longer need to write any code for the Controller layer. As long as the following convention is followed, the front end will directly call the corresponding method of Service layer and get the response value in the convention format.

Front-end request path: {{rootPath}}/lq/serviceName/serviceMethodName. do {{rootPath}}: Root path to access address lq: Custom fixed name to satisfy the mapping rules of SpringMVC. serviceName: Used to get Service Bean from the Spring container. The rule here is to append the Service character to the name as Bean Id to get the corresponding Service Bean from the Spring container. serviceMethodName: The method named serviceMethodName in Service Bean found in Step 3. Signed Object serviceMethodName (Map) < String,Object > param).

5. Special needs

For special Controller with additional needs, it can be written exactly as the previous Controller layer. There is nothing extra to pay attention to.

STEP 6 Improve

In the method signature of Service layer above, its parameters use fixed Map < String,Object > param. The debate between Map and Bean has a long history and is enduring, so we will not stir up this muddy water here.

If you want to use Bean as a method parameter, you can refer to the implementation of Controller layer method call in SpringMVC to achieve the desired effect. Specific implementation is not here, interested students can refer to the source code ServletInvocableHandlerMethod. invokeAndHandle.


Related articles: