Spring MVC content negotiation - extension parameter analysis

problem

When using spring MVC, when invoking the interface to transfer data, we mostly use json format to transfer data, which is also the default option in this framework. But if we want to define our own transport format, write our own parsing interface, and define our own media type as an extension, what do we need to do?

process

In the data transmission phase of spring MVC, when we request to process in the DispatchServlet, when we find the corresponding method HandlerMapping, we will parse the parameters before executing the method, of course, we will also process its return value after the corresponding method is executed.

For example, a method uses @ ResponseBody and @ RequestBody annotations. as follows

	//Where User is an object, including name and age
	@PostMapping(value = "/add/user")
	@ResponseBody
	public User user(@RequestBody User user) {
        return user;
    }

Before the method is executed, the data in the request needs to be converted into an object, and after the method is executed, the object needs to be put back into the response body. There are two interfaces involved in this process.

  • HandlerMethodArgumentResolver is responsible for processing input parameters
  • HandlerMethodReturnValueHandler is responsible for processing return parameters

In spring MVC, two common implementation classes are provided, RequestResponseBodyMethodProcessor. The following is its inheritance relationship

That is to say, spring MVC provides us with an implementation, which will be processed in the RequestResponseBodyMethodProcessor when passing through the above functions. Of course, this is the corresponding @ ResponseBody annotation and @ RequestBody annotation. Different annotation processing functions are different. Spring MVC is designed in combination mode and selects the corresponding parser by traversing the qualified subclasses.

There are two functions in RequestResponseBodyMethodProcessor

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

	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}

Realization

If we want to transfer the data in the form of properties now, we need to write our own parser.

A method is provided in WebMvcConfigurer of SpringBoot, so we can configure our own data parser.

	/**
	 * A hook for extending or modifying the list of converters after it has been
	 * configured. This may be useful for example to allow default converters to
	 * be registered and then insert a custom converter through this method.
	 * @param converters the list of configured converters to extend.
	 * @since 4.1.3
	 */
	default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
	}
  • Writing analytic classes
package com.hwk.springmvc.http.converter.properties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Properties;

public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter<Properties> {

    //Set supported media types
    public PropertiesHttpMessageConverter() {
        super(new MediaType("text", "properties"));
    }

    @Override
    protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        HttpHeaders httpHeaders = outputMessage.getHeaders();
        MediaType mediaType = httpHeaders.getContentType();

        //Get character encoding
        Charset charset = mediaType.getCharset();

        //Use utf-8 by default when charset is empty
        charset = charset == null ? Charset.forName("UTF-8") : charset;

        //Properties -> String
        //OutputStream -> Writer
        //Byte output stream
        OutputStream outputStream = outputMessage.getBody();
        //Character output stream
        Writer writer = new OutputStreamWriter(outputStream, charset);

        //Properties written to character output stream
        properties.store(writer, "success");
    }

    @Override
    protected Properties readInternal(Class<? extends Properties> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        //inputMessage can be used to retrieve the content transmitted by the Http message request object
        //Parsing encoding from request header content type
        HttpHeaders httpHeaders = inputMessage.getHeaders();
        MediaType mediaType = httpHeaders.getContentType();

        //Get character encoding
        Charset charset = mediaType.getCharset();

        //Use utf-8 by default when charset is empty
        charset = charset == null ? Charset.forName("UTF-8") : charset;

        //Byte stream to character stream
        InputStream inputStream = inputMessage.getBody();
        InputStreamReader reader = new InputStreamReader(inputStream, charset);

        Properties properties = new Properties();
        //Load character stream
        properties.load(reader);

        return properties;
    }

    @Override
    public Properties read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return readInternal(null, inputMessage);
    }
}
  • Add to spring MVC configuration
package com.hwk.springmvc.config;
import com.hwk.springmvc.http.converter.properties.PropertiesHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    	//Note that it needs to be inserted forward, otherwise the text information will be processed first in Json format
        converters.add(0, new PropertiesHttpMessageConverter());
    }
}
  • Write Controller
package com.hwk.springmvc.controller;
import com.hwk.springmvc.model.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Properties;

@RestController
public class PropertiesRestController {
    @PostMapping(value = "/add/properties",
            consumes = "text/properties;charset=UTF-8"//Filter media type
    )
    public Properties user(@RequestBody Properties properties) {
        return properties;
    }
}
  • Using postman testing

Headers need to indicate the custom media type

Use text transfer, of course, the content needs to meet the Properties format

Returning the same result means success.

73 original articles published, praised 6, 10000 visitors+
Private letter follow

Tags: Spring Java encoding JSON

Posted on Wed, 05 Feb 2020 05:02:56 -0800 by Kestrad