spring boot 八:SpringBoot响应返回xml数据

spring boot 八:SpringBoot响应返回xml数据

1 前言

根据DispatcherServlet源码分析,研究SpringBoot的Controller返回xml数据的一些方法,包含单独配置和全局配置返回xml数据两种方式。

依赖的SpringBoot版本:

<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.5.4</version>
</parent>

2 使用

2.1 源码分析:

搜索spring-webmvc包下的DispatcherServlet类:

在这里插入图片描述

DispatcherServlet类下的doDispatch方法,为该方法打上断点,debug模式启动SpringBoot,编写test controller 使用 postman发起debug:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

RequestMappingHandlerAdapter中:

在这里插入图片描述

此处可见,returnValue的值为返回的people对象:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

返回值的handler总共15个:

在这里插入图片描述

选择处理的handler时,会调用handler的supportsReturnType方法,判断是否支持处理:

在这里插入图片描述

由RequestResponseBodyMethodProcessor(该类继承了AbstractMessageConverterMethodProcessor抽象类)的supportsReturnType方法可知:

在这里插入图片描述

类上含有@RestController(@RestController注解包含了@ResponseBody注解)注解,或方法上含有@ResponseBody注解,判断是可以处理的,因此处理returnValue的handler是RequestResponseBodyMethodProcessor:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

取请求头中为Accept的值,赋予acceptableTypes:

在这里插入图片描述

如下在浏览器请求头中的Accept, 其中q值代表权重,q值约大,代表浏览器更能优先接受的类型:

在这里插入图片描述

postman中,亦可见:

在这里插入图片描述

上述可知,RequestResponseBodyMethodProcessor的messageConvertors有10个,其中单独处理某个实体类为xml的HttpMessageConverter,为Jaxb2RootElementHttpMessageConverter,其位于spring-web包下:

在这里插入图片描述

它也是10个HttpMessageConverter中的最后一个(就在处理json的MappingJackson2HttpMessageConverter后面):

在这里插入图片描述

观察其源码可知,该convertor能否支持使用的前提(即canWrite必须返回true,才可支持使用):

1,实体类的class上,必须有XmlRootElement注解(javax.xml.bind.annotation包下,jdk自带注解,该注解的范围是Type,仅支持在类上使用);

2,this.canWrite(mediaType)亦返回true,才可处理;

在这里插入图片描述

因为Jaxb2RootElementHttpMessageConverter继承了抽象类AbstractXmlHttpMessageConverter,所以其初始化支持的MediaType就有如下3种,其中就包含:application/xml。this.canWrite(mediaType)即判断传入的媒体类型,是否是配置的3种之一,即可返回true:

在这里插入图片描述

故而只需要满足在实体类上增加注解:@XmlRootElement,同时请求的mediaType为:application/xml等,即可返回xml结果。

在这里插入图片描述

另外特别需要注意的是getProducibleMediaTypes方法,该方法执行时,比如@RequestMapping注解上不存在produces参数,那么就遍历全部的messageConvertors,canWrite返回true的,则将其支持的MediaType加入result的list中(@RequestMapping没有produces参数,则getProducibleMediaTypes刚开始获取的mediaTypes为null):

在这里插入图片描述

比如支持xml的Jaxb2RootElementHttpMessageConverter,虽然没有增加produces参数,但是只要controller请求方法的返回DTO上,包含了@XmlRootElement注解,也会将其支持的xml相关媒体类型加入result的:

在这里插入图片描述

在这里插入图片描述

注意:虽然有7种媒体类型(json、xml的媒体类型都有),但是排序筛选后,第一个application/json就已经满足情况,直接就break去后面和messageConvertors进行匹配了,所以也不会输出xml格式,因此,以上返回xml的条件,都需要满足(即controller返回的实体类加上@XmlRootElement外,还需要@RequestMapping注解上传入produces参数,值为application/xml等。同时可知返回json数据的情形下,一般是不会使用到@RequestMapping注解的produces参数的,因上述分析可知,排序筛选后,默认就是返回的json格式数据):

在这里插入图片描述

2.2 单Controller响应返回xml:

@Data
public class JackSonDemo {
    long uid;
    String name;
    Date now;
    LocalDateTime localDay;
}

需要返回xml的实体类上加上注解@XmlRootElement:

@XmlRootElement
@Data
public class JackSonDemoSub {
    String name;
    JackSonDemo jackSonDemo;
}

controller:

@RequestMapping(value = "/jackson")
@RestController
public class TestJackSonController {

    @RequestMapping(value = "/demo1")
    public JackSonDemo jackSonOne(@RequestParam(required = false) String name, @RequestParam(required = false) Integer uid){
        JackSonDemo jackSonDemo = new JackSonDemo();
        if(uid != null){
            jackSonDemo.setUid(uid);
        }else{
            jackSonDemo.setUid(100);
        }

        if(name != null){
            jackSonDemo.setName(name);
        }

        jackSonDemo.setNow(new Date());
        jackSonDemo.setLocalDay(LocalDateTime.now());
        return jackSonDemo;
    }

    @RequestMapping(value = "/demo2",produces = {"application/xml;"})
    public JackSonDemoSub jackSonDemoSub(@RequestParam(required = false) String name){
        JackSonDemo jackSonDemo = new JackSonDemo();
        jackSonDemo.setUid(100);
        if(name != null){
            jackSonDemo.setName(name);
        }
        jackSonDemo.setNow(new Date());
        jackSonDemo.setLocalDay(LocalDateTime.now());

        JackSonDemoSub jackSonDemoSub = new JackSonDemoSub();
        jackSonDemoSub.setJackSonDemo(jackSonDemo);
        jackSonDemoSub.setName("nihao");

        return jackSonDemoSub;
    }
}

全局配置的jackson objectMapper配置(目前是处理json的objectMapper):

@Configuration
public class JacksonGlobalConfig {

    @Bean(name = "jackson2ObjectMapperBuilder")
    /* 观察SpringBoot-2.5.4
    * jackson的自动配置类:JacksonAutoConfiguration 源码
    *  */
    @SuppressWarnings(value = "   deprecation ")
    public Jackson2ObjectMapperBuilder xiaoxuJackson2ObjectMapperBuilder(){
        System.out.println("1.jackson builder注入:");
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.serializationInclusion(JsonInclude.Include.NON_NULL);
        builder.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        builder.featuresToEnable(SerializationFeature.WRAP_ROOT_VALUE);

        /* 针对new Date  SimpleDateFormat线程不安全, 建议使用自定义的日期工具类,
        * 此处只做简单演示配置*/
        /* 注意: 使用 yyyy-MM-dd HH:mm:ss.fff 将报错 */
        /* 使用 dateFormat, new Date 和 LocalDateTime 均会产生对应效果*/
        /* new Date 不设置的情况下,默认返回时间戳 */
        builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
        /* 全局注册自定义序列化器 */
//        builder.serializerByType(JackSonDemo.class, new JackSonDemoSerializer());
        return builder;
    }

    @Bean(name = "jacksonObjectMapper")
    @Primary
    @ConditionalOnBean(name = {"jackson2ObjectMapperBuilder"})
    /* Jackson的ObjectMapper 是线程安全的, 不过SpringBoot2.5.4源码上使用的是非单例模式,这里和源码保持一致 */
    @Scope("prototype")
//    @Scope("singleton")
    public ObjectMapper xiaoxuJacksonObjectMapper(@Qualifier("jackson2ObjectMapperBuilder") Jackson2ObjectMapperBuilder builder){
        System.out.println("2.jackson objectMapper注入:");
        return builder.createXmlMapper(false).build();
    }

}

执行postman请求:

在这里插入图片描述

demo1接口依然返回json数据:

在这里插入图片描述

2.3 全部配置Controller响应返回xml:

如果希望全局配置返回为xml格式,那么需要额外添加jackson-dataformat-xml依赖(必须添加该jackson依赖,否则全局配置使用builder.createXmlMapper(true).build()时,会提示找不到com.fasterxml.jackson.dataformat.xml.XmlMapper类):

<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.5.4</version>
</parent>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.11.1</version>
    </dependency>
</dependencies>

修改全局jackson objectMapper配置:

@Configuration
public class JacksonGlobalConfig {

    @Bean(name = "jackson2ObjectMapperBuilder")
    /* 观察SpringBoot-2.5.4
    * jackson的自动配置类:JacksonAutoConfiguration 源码
    *  */
    @SuppressWarnings(value = "   deprecation ")
    public Jackson2ObjectMapperBuilder xiaoxuJackson2ObjectMapperBuilder(){
        System.out.println("1.jackson builder注入:");
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.serializationInclusion(JsonInclude.Include.NON_NULL);
        builder.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        builder.featuresToEnable(SerializationFeature.WRAP_ROOT_VALUE);

        /* 针对new Date  SimpleDateFormat线程不安全, 建议使用自定义的日期工具类,
        * 此处只做简单演示配置*/
        /* 注意: 使用 yyyy-MM-dd HH:mm:ss.fff 将报错 */
        /* 使用 dateFormat, new Date 和 LocalDateTime 均会产生对应效果*/
        /* new Date 不设置的情况下,默认返回时间戳 */
        builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
        /* 全局注册自定义序列化器 */
//        builder.serializerByType(JackSonDemo.class, new JackSonDemoSerializer());
        return builder;
    }

    @Bean(name = "jacksonObjectMapper")
    @Primary
    @ConditionalOnBean(name = {"jackson2ObjectMapperBuilder"})
    /* Jackson的ObjectMapper 是线程安全的, 不过SpringBoot2.5.4源码上使用的是非单例模式,这里和源码保持一致 */
    @Scope("prototype")
//    @Scope("singleton")
    public ObjectMapper xiaoxuJacksonObjectMapper(@Qualifier("jackson2ObjectMapperBuilder") Jackson2ObjectMapperBuilder builder){
        System.out.println("2.jackson objectMapper注入:");
        return builder.createXmlMapper(true).build();
    }

}

针对controller不做改动,再次执行:

demo1:

在这里插入图片描述

demo2的返回实体类,去掉@XmlRootElement注解:

在这里插入图片描述

在这里插入图片描述

可见,全局配置后,均返回xml数据(即使demo2接口的返回实体类去掉@XmlRootElement注解,效果也是一致);但是注意区别,由上述源码分析可知,因为demo1接口的@RequestMapping注解没有添加produces参数,所以媒体类型最后依然是application/json(响应头的Content-Type有所区别),只是使用的jackson的XmlMapper进行处理的结果,故而返回xml格式的数据时,Controller上的@RequestMapping最好都加上produces = {“application/xml;”}参数。

若整个controller下的全部接口都是返回xml,无需单独配置,统一如下配置即可:

在这里插入图片描述

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值