Solutions that use Feign in Spring Cloud @ RequestBody cannot inherit

  • 2021-11-30 00:02:50
  • OfStack

Directory using Feign, @ RequestBody can not inherit problem cause analysis solution using feign problem 1, example 2, first access timeout problem 3, FeignClient interface

Unable to inherit with Feign, @ RequestBody

According to the example of official website FeignClient, write a simple updateUser interface, which is defined as follows


@RequestMapping("/user")
public interface UserService {
    @RequestMapping(value = "/{userId}", method = RequestMethod.GET)
    UserDTO findUserById(@PathVariable("userId") Integer userId);
    @RequestMapping(value = "/update", method = RequestMethod.POST)
    boolean updateUser(@RequestBody UserDTO user);
}

Implementation class


 @Override
    public boolean updateUser(UserDTO user)
    {   
        LOGGER.info("===updateUser, id = " + user.getId() + " ,name= " + user.getUsername());
        return false;
    }

Execute the unit test and find that the expected input parameters are not obtained

2018-09-07 15:35:38,558 [http-nio-8091-exec-5] INFO [com.springboot.user.controller.UserController] {} - ===updateUser, id = null ,name= null

Cause analysis

In SpringMVC, RequestResponseBodyMethodProcessor class is used to parse the input and output parameters. The following method determines whether to convert the message body based on whether the parameter has an @ RequestBody annotation.


@Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }

Solutions

Since MVC uses RequestResponseBodyMethodProcessor for parameter analysis, a customized Processor can be realized, and the judgment method of supportParameter can be modified.


 @Override
    public boolean supportsParameter(MethodParameter parameter)
    {
        //springcloud The interface entry parameter of does not write @RequestBody And is a custom type object   Also press JSON Analyse 
        if (AnnotatedElementUtils.hasAnnotation(parameter.getContainingClass(), FeignClient.class) && isCustomizedType(parameter.getParameterType())) {
            return true;
        }
        return super.supportsParameter(parameter);
    }

The judgment logic here can be defined according to the actual framework, and the purpose is to judge the interface defined for Spring Cloud and use the same content converter as @ RequestBody when it is a custom object.

After implementing the customized Processor, you need to make the customized configuration take effect. There are two options:

Replace RequestResponseBodyMethodProcessor directly, and customize RequestMappingHandlerAdapter under SpringBoot. Implementation of addArgumentResolvers Interface in WebMvcConfigurer

Here, the simpler second method is adopted, and the message converter at initialization is loaded as needed:


public class XXXWebMvcConfig implements WebMvcConfigurer
{
@Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
    {
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(5);
        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(stringHttpMessageConverter);
        messageConverters.add(new SourceHttpMessageConverter<>());
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());
        CustomizedMappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new CustomizedMappingJackson2HttpMessageConverter();
        jackson2HttpMessageConverter.setObjectMapper(defaultObjectMapper());
        messageConverters.add(jackson2HttpMessageConverter);
        ViomiMvcRequestResponseBodyMethodProcessor resolver = new ViomiMvcRequestResponseBodyMethodProcessor(messageConverters);
        resolvers.add(resolver);
    }

After the modification is completed, the @ RequestBody annotation can be removed from the implementation class of microservice.

Problems with feign

The construction of spring cloud using feign project will not be written here. This article mainly explains the problems encountered in the use process and the solutions

1. Example


@RequestMapping(value = "/generate/password", method = RequestMethod.POST)
KeyResponse generatePassword(@RequestBody String passwordSeed);

Only @ RequestMapping (value = "/generate/password", method = RequestMethod. POST) Annotations cannot be used here

@ PostMapping otherwise the project kick-off meeting will be reported

Caused by: java. lang. IllegalStateException: Method generatePassword not annotated with HTTP method type (ex. GET, POST) Exception

2. The first access timeout problem

Reason: The default timeout for Hystrix is 1 second. If you do not respond after this time, you will enter the fallback code.

The first request tends to be slow (because of the lazy loading mechanism of Spring, which instantiates 1 class), and the response time may be longer than 1 second.

Solution:

< 1: Configure the timeout for Hystrix to 5 seconds


hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000

< 2: Disable the timeout for Hystrix


hystrix.command.default.execution.timeout.enabled: false

< 3: Disable the hystrix feature of feign


feign.hystrix.enabled: false

Note: Individuals recommend the first or second method

3. In FeignClient interface,

If you use @ PathVariable, you must specify its value

spring cloud feign Use Apache HttpClient

Question: 1 If Content-Type is not specified, the following exception will occur? It's puzzling here?

Caused by: java.lang.IllegalArgumentException: MIME type may not contain reserved characters

Interested friends here can study the source code


ApacheHttpClient.class 
  private ContentType getContentType(Request request) {
    ContentType contentType = ContentType.DEFAULT_TEXT;
    for (Map.Entry<String, Collection<String>> entry : request.headers().entrySet())
    //  It will be judged here   If you do not specify  Content-Type  Attribute   Use the default above  text/plain; charset=ISO-8859-1
    //  The problem lies in the default  contentType  :   Format  text/plain; charset=ISO-8859-1 
    //  Go to  ContentType.create(entry.getValue().iterator().next(), request.charset());  Look in the method 
    if (entry.getKey().equalsIgnoreCase("Content-Type")) {
      Collection values = entry.getValue();
      if (values != null && !values.isEmpty()) {
        contentType = ContentType.create(entry.getValue().iterator().next(), request.charset());
        break;
      }
    }
    return contentType;
  }

 @Override
    public boolean updateUser(UserDTO user)
    {   
        LOGGER.info("===updateUser, id = " + user.getId() + " ,name= " + user.getUsername());
        return false;
    }
0

Solution:


 @Override
    public boolean updateUser(UserDTO user)
    {   
        LOGGER.info("===updateUser, id = " + user.getId() + " ,name= " + user.getUsername());
        return false;
    }
1

Note specifies: Content-Type specifies the attribute value of consumes: here the value of consumes is not explained in detail, you can study it if you are interested


Related articles: