Spring Boot data response and content negotiation

Response pages refer to how we send a request to jump to a specified page. This will be explained later in view resolution. Response pages are common in developing individual applications. Response data is common in developing applications where the front and back ends are separated. The back-end code is mainly used to receive requests. The front-end page sends us the request and gives the front-end the JSON data in response. Or give the front-end response XML, images, audio and video data.

In the process of separating the front end from the back end, the back end will generally encapsulate the data set into a JSON object response to the front end, and generally only need the standard ResponseBody to return the data to the front end

1, Response Json data: jackson. jar+ @responseBody

Assuming that json data is automatically returned to the front-end, dependencies need to be introduced

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<! -- Web scene automatically introduces JSON scene -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.4. RELEASE</version>
    <scope>compile</scope>
</dependency>
Copy the code

The code for the control layer is as follows: When the dependency is introduced and the method is labeled @responseBody, it automatically returns JSON data to the front end.

@Controller
public class ResponseTestController {
    @ResponseBody // The principle is to use the message converter in the return value handler
    @GetMapping("/test/person")
    public Person getPerson(a){
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setUserName("zhangsan");
        returnperson; }}Copy the code

Testing:

2. Principle analysis

  • The return value handler determines whether the type supportsReturnType is supported
  • The return value handler calls handleReturnValue for processing
  • RequestResponseBodyMethodProcessor can handle return value mark @ ResponseBody annotations.
    • Write the data to JSON using MessageConverters for processing
      • Content negotiation (by default, the browser tells the server what type of content it can accept in the form of a request header)
      • The server ultimately decides what content types of data it can produce based on its own capabilities,
      • SpringMVC iterates through all of the bottom layers of the containerHttpMessageConverterWho can handle it? (That is, converting objects to JSON data)
        • Get MappingJackson2HttpMessageConverter message converters can be written as json object
        • Using MappingJackson2HttpMessageConverter object into a json to write out.

What return values are supported by SpringMVC

  • ModelAndView // contains data and pages
  • Model
  • View
  • ResponseEntity
  • ResponseBodyEmitter
  • StreamingResponseBody
  • HttpEntity
  • HttpHeaders
  • Callable / / asynchronous
  • DeferredResult
  • ListenableFuture
  • CompletionStage
  • WebAsyncTask
  • Has @modelAttribute and is of object type
  • @ ResponseBody annotations – > RequestResponseBodyMethodProcessor; The handler // is called @responseBody on a method or class

HTTPMessageConverter principle

MessageConverter specification

HttpMessageConverter: See if an object of Class type can be converted to MediaType data. Example: CanWrite converts the Person object to JSON. CanRead or JSON to Person

The default MessageConverter

  • 0 – Only Byte types are supported
  • 1 – String
  • 2 – String
  • 3 – Resource
  • 4 – ResourceRegion
  • 5 – DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
  • 6 – MultiValueMap
  • 7-true // Support to convert any object to the specified object, no matter what
  • 8 – true
  • 9 – Supports annotated XML processing.

Eventually MappingJackson2HttpMessageConverter the object into a JSON (using the underlying Jackson objectMapper conversion)

3. Content negotiation

3.1 overview,

Depending on the client’s ability to receive data (some only receive XML, some only receive JSON), the data is returned in different media types. Such as returning XML data to the former

Introduce support for XML dependencies:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Copy the code

Recompiling the project runs and returns XML data

3.2 postman tests return JSON and XML respectively

In the above test, if I send the same request with Postman, I get json data. Why does the same request return different data? The reason is the sequence of data responses specified in the request header

View the request header

In content negotiation Accept, what type of data is accepted by the browser? You can see that XML data is accepted first.

Postman software can be used to test json and XML returns separately: just change the Accept field in the request header (application/ JSON, application/ XML). In the Http protocol, it tells the server the type of data that the client can receive.

3.3. Enable the content negotiation function based on browser parameters

To facilitate content negotiation, the content negotiation function based on request parameters is enabled.

spring:
    contentnegotiation:
      favor-parameter: true  Enable request parameter content negotiation mode
Copy the code

Send the request:

  • Json type: http://localhost:8080/test/person? format=json
  • XML types: http://localhost:8080/test/person? format=xml

Determine what type of content the client receives;

1. Parameter policy priority is to return JSON data (obtain the format value in the request header). 2.

4. Principle of content negotiation

  • Determines whether the current response header already has a defined media type. MediaType
  • Gets the type of content that the client (PostMan, browser) supports receiving. [application/ XML]
  • ContentNegotiationManager content negotiation manager use strategy based on the request header by default
  • HeaderContentNegotiationStrategy determine the client can receive content type
  • Loop through all of the current system’s Messageconverters to see who supports manipulating this object (Person)
  • Find a Converter that supports manipulating Person, and count the media types that Converter supports.
  • The client needs [Application/XML]. Server-side capabilities [10 kinds, JSON, XML]
  • The best matching media type for content negotiation
  • Use Converter that supports converting objects to the best matching media type. Call it for transformation.

Import Jackson’s package for processing XML, and the XML Converter will come in automatically

WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);

if (jackson2XmlPresent) {
                    Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
                    if (this.applicationContext ! =null) {
                            builder.applicationContext(this.applicationContext);
                    }
                    messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
            }

Copy the code

5, custom MessageConverter MessageConverter

5.1 overview,

Realize multi-protocol data compatibility. Json, XML, X-Guigu

  • @ ResponseBody RequestResponseBodyMethodProcessor response data out call processing
  • Processor Processing method Return value. Use MessageConverter
  • All Messageconverters together support operations (read, write) on data of various media types
  • Content negotiation finds the final messageConverter;

What feature do you want to customize SpringMVC by adding a WebMvcConfigurer to the container through an entry

Suppose you want custom content negotiation based on custom request parameters. In other words, type http://localhost:8080/test/person? in the address bar Format = gg return data, with http://localhost:8080/test/person and request header parameter ` Accept: application/x – guigu ` return custom protocol data consistent.

demo

Through the above analysis, we only need to implement WebMvcConfigurer interface, and implements the configureMessageConverters method, can achieve the goal of a custom message converter. For example, if I don’t want to use Jackson and want to use a fastjson MessageConverter, I can simply add a fastjson-related MessageConverter

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List
       
        > converters)
       > {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.TEXT_HTML);
        fastMediaTypes.add(MediaType.APPLICATION_JSON);
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        FastJsonConfig fastJsonConfig = newFastJsonConfig(); fastJsonConfig.setSerializerFeatures( SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteDateUseDateFormat); SerializeConfig serializeConfig = SerializeConfig.globalInstance; serializeConfig.put(BigInteger.class, ToStringSerializer.instance); serializeConfig.put(Long.class, ToStringSerializer.instance); serializeConfig.put(Long.TYPE, ToStringSerializer.instance); fastJsonConfig.setSerializeConfig(serializeConfig); fastConverter.setFastJsonConfig(fastJsonConfig); converters.add(fastConverter); }}Copy the code

test

@Data
public class Person {
    private String userName;
    private Integer age;
    // Use fastjson annotations for the conversion
    @JSONField(format = "yyyy-MM-dd")
    private Date birth;
    private Pet pet;
}
Copy the code

5.2. Custom Converter

Otherwise, these are all defaults, and we can extend them to implement custom Settings as follows, using this code as follows:

@Bean
public WebMvcConfigurer webMvcConfigurer(a){
    return new WebMvcConfigurer() {

        @Override
        public void extendMessageConverters(List
       
        > converters)
       > {}}}Copy the code

test

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(a){
        return new WebMvcConfigurer() {

            @Override
            public void extendMessageConverters(List
       
        > converters)
       > {
                converters.add(newGuiguMessageConverter()); }}}}Copy the code
/** * Custom Converter */
public class GuiguMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class
        clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class
        clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);
    }

    /** * The server needs to count the content types that all messageconverters can write ** application/x-guigu *@return* /
    @Override
    public List<MediaType> getSupportedMediaTypes(a) {
        return MediaType.parseMediaTypes("application/x-guigu");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        // Write out custom protocol data
        String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();


        / / write outOutputStream body = outputMessage.getBody(); body.write(data.getBytes()); }}Copy the code

Testing:

import java.util.Date;

@Controller
public class ResponseTestController {

    /** ** ** ** ** ** ** ** ** ** ** ** ** ** * JacksonJsonConverter * 3, If the Silicon Valley app sends a request, return the custom protocol data [appliAction/X-Guigu] xxxxConverter * attribute value 1; The attribute value 2; MessageConverter: [guigu-- > Guigu] : [guigu-- > Guigu] How to negotiate content in parameters *@return* /
    @ResponseBody  // Use the message converter inside the return value handler
    @GetMapping(value = "/test/person")
    public Person getPerson(a){
        Person person = new Person();
        person.setAge(28);
        person.setBirth(new Date());
        person.setUserName("zhangsan");
        returnperson; }}Copy the code

Note that the custom functions may overwrite many of the default functions, making some of the default functions invalid.