spring boot 支持返回 xml

摘要:
实现技术方式对比JAXB(JavaArchitectureforXMLBinding)是一个业界的标准,可以实现java类和xml的互转jdk中包括JAXBJAXBvsjackson-dataformat-xmlspringboot中默认使用jackson返回json,jackson-dataformat-xml中的XmlMapperextendsObjectMapper所以对于xml而已跟jso

实现技术方式对比

JAXB(Java Architecture for XML Binding) 是一个业界的标准,可以实现java类和xml的互转

jdk中包括JAXB

JAXB vsjackson-dataformat-xml

spring boot中默认使用jackson返回json,jackson-dataformat-xml 中的 XmlMapper extendsObjectMapper 所以对于xml而已跟json的使用方式更类似,并且可以识别

pojo上的@JsonProperty、 @JsonIgnore 等注解,所以推荐使用jackson-dataformat-xml 来处理xml

jaxb 对list的支持不好也,使用比较复杂

packagecom.example.demo;

importcom.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
importcom.fasterxml.jackson.annotation.JsonIgnore;
importcom.fasterxml.jackson.annotation.JsonProperty;
importcom.fasterxml.jackson.annotation.PropertyAccessor;
importcom.fasterxml.jackson.core.JsonProcessingException;
importcom.fasterxml.jackson.databind.ObjectMapper;
importcom.fasterxml.jackson.databind.SerializationFeature;
importcom.fasterxml.jackson.dataformat.xml.XmlMapper;

classMyPojo {

    @JsonProperty("_id")
    privateString id;

    privateString name;

    private intage;

    @JsonIgnore
    privateString note;

    publicString getNote() {
        returnnote;
    }

    public voidsetNote(String note) {
        this.note =note;
    }

    publicString getId() {
        returnid;
    }

    public voidsetId(String id) {
        this.id =id;
    }

    publicString getName() {
        returnname;
    }

    public voidsetName(String name) {
        this.name =name;
    }

    public intgetAge() {
        returnage;
    }

    public void setAge(intage) {
        this.age =age;
    }
}

public classTest {

    public static void main(String[] args) throwsJsonProcessingException {
        XmlMapper mapper1 = newXmlMapper();
        ObjectMapper mapper2 = newObjectMapper();

        mapper1.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        mapper2.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

        mapper1.enable(SerializationFeature.INDENT_OUTPUT);
        mapper2.enable(SerializationFeature.INDENT_OUTPUT);

        MyPojo mypojo = newMyPojo();
        mypojo.setName("Dhani");
        mypojo.setId("18082013");
        mypojo.setAge(5);

        String jsonStringXML =mapper1.writeValueAsString(mypojo);
        String jsonStringJSON =mapper2.writeValueAsString(mypojo);
        //takes java class with def or customized constructors and creates JSON
System.out.println("XML is " + "
" + jsonStringXML + "
");
        System.out.println("Json is " + "
" +jsonStringJSON);
    }
}

接口返回xml

spring boot中默认用注册的xmlHttpMessageConverter 为Jaxb2RootElementHttpMessageConverter

spring boot 支持返回 xml第1张

spring boot 支持返回 xml第2张

接口返回xml

//需要有注解,否则会报No converter for [class com.example.demo.IdNamePair] with preset Content-Type 'null' 错误
@XmlRootElement
public classIdNamePair {
    Integer id;
    String name;

    publicInteger getId() {
        returnid;
    }

    public voidsetId(Integer id) {
        this.id =id;
    }

    publicString getName() {
        returnname;
    }

    public voidsetName(String name) {
        this.name =name;
    }
}

原因:Jaxb2RootElementHttpMessageConverter 中

@Override
    public boolean canWrite(Class<?>clazz, @Nullable MediaType mediaType) {
        return (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null &&canWrite(mediaType));
    }

控制器

packagecom.example.demo;

importorg.springframework.http.MediaType;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RestController;

@RestController
public classWelcomeController {

    /*** <IdNamePair>
     *     <id>123</id>
     *     <name>蓝银草</name>
     * </IdNamePair>
     *
     * @return
     */@RequestMapping(value = "/xml")
// produces = MediaType.APPLICATION_JSON_VALUE 增加可以强制指定返回的类型,不指定则默认根据 请求头中的 Accept 进行判定
// 注意返回类型 HttpServletResponse response; response.setContentType(MediaType.APPLICATION_JSON_VALUE); 设置不生效 todo
// 示例:*/* 、 text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

publicIdNamePair xml() { IdNamePair idNamePair = newIdNamePair(); idNamePair.setId(123); idNamePair.setName("蓝银草"); returnidNamePair; } }

一个请求同时支持返回 json 和 xml

1、根据header中的Accept自动判定

@RestController
public classWelcomeController {

    
    @RequestMapping(value = "/both")
    publicIdNamePair both() {

        IdNamePair idNamePair = newIdNamePair();
        idNamePair.setId(456);
        idNamePair.setName("蓝银草");

        returnidNamePair;
    }

}

2、根据指定的参数

@Configuration
public class WebInterceptorAdapter implementsWebMvcConfigurer {
    @Override
    public voidconfigureContentNegotiation(ContentNegotiationConfigurer configurer) {

        configurer.favorParameter(true)    //是否支持参数化处理请求
            .parameterName("format") //参数的名称, 默认为format
            .defaultContentType(MediaType.APPLICATION_JSON)  //全局的默认返回类型
            .mediaType("xml", MediaType.APPLICATION_XML)     //format 参数值与对应的类型XML
            .mediaType("json", MediaType.APPLICATION_JSON);  //format 参数值与对应的类型JSON
}
    
}

请求url

  http://127.0.0.1:8080/both?format=json

  http://127.0.0.1:8080/both?format=xml

该功能默认未开启

参考源码:

public static classContentnegotiation {

        /*** Whether the path extension in the URL path should be used to determine the
         * requested media type. If enabled a request "/users.pdf" will be interpreted as
         * a request for "application/pdf" regardless of the 'Accept' header.
         */
        private boolean favorPathExtension = false;

        /**
         * Whether a request parameter ("format" by default) should be used to determine
         * the requested media type.
         */
        private boolean favorParameter = false;

        /*** Map file extensions to media types for content negotiation. For instance, yml
         * to text/yaml.
         */
        private Map<String, MediaType> mediaTypes = new LinkedHashMap<>();

        /*** Query parameter name to use when "favor-parameter" is enabled.
         */
        private String parameterName;

浏览器访问以前返回json的现在都返回xml问题

以前的消息转换器不支持xml格式,但有支持json的消息转换器,根据浏览器请求头 中的 Accept 字段,先匹配xml【不支持】在匹配json,所以最后为json

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

引入 fastxml 后支持 xml格式消息转换器,并且Accept中又是优先匹配 xml,故以前所有的接口现在浏览器访问都变成 xml 格式的了,但用postman仍旧为json 【Accept:*/* 】

解决:

@Configuration
public class WebInterceptorAdapter implementsWebMvcConfigurer {
    @Override
    public voidconfigureContentNegotiation(ContentNegotiationConfigurer configurer) {

        //configurer.
configurer
            .ignoreAcceptHeader(true) //忽略头信息中 Accept 字段
.defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML);
            //采用固定的内容协商策略 FixedContentNegotiationStrategy
}
}

配置前内容协商:  

如果没有忽略自动协商【按Accept】

org.springframework.web.accept.ContentNegotiationManager

会自动添加strategies.add(new HeaderContentNegotiationStrategy());

org.springframework.web.accept.HeaderContentNegotiationStrategy#resolveMediaTypes

/*** {@inheritDoc}
     * @throwsHttpMediaTypeNotAcceptableException if the 'Accept' header cannot be parsed
     */@Override
    public List<MediaType>resolveMediaTypes(NativeWebRequest request)
            throwsHttpMediaTypeNotAcceptableException {

        String[] headerValueArray =request.getHeaderValues(HttpHeaders.ACCEPT);
        if (headerValueArray == null) {
            returnMEDIA_TYPE_ALL_LIST;
        }

        List<String> headerValues =Arrays.asList(headerValueArray);
        try{
//根据Accept字段计算 media type List
<MediaType> mediaTypes =MediaType.parseMediaTypes(headerValues); MediaType.sortBySpecificityAndQuality(mediaTypes); return !CollectionUtils.isEmpty(mediaTypes) ?mediaTypes : MEDIA_TYPE_ALL_LIST; } catch(InvalidMediaTypeException ex) { throw newHttpMediaTypeNotAcceptableException( "Could not parse 'Accept' header " + headerValues + ": " +ex.getMessage()); } }

配置后内容协商:

org.springframework.web.accept.FixedContentNegotiationStrategy#resolveMediaTypes

@Override
    public List<MediaType>resolveMediaTypes(NativeWebRequest request) {
//固定返回配置的类型 defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML);
return this.contentTypes; }

controller 中设置 content-type失效问题

1、在带有返回值的情况下,在controller中设置content-type是无效的,会被消息转换器覆盖掉
2、优先使用 produces = MediaType.TEXT_PLAIN_VALUE ,没有则会根据请求头中的 accept 和 HttpMessageConverter 支持的类型
计算出一个

org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch

//寻找合适的 HandlerMapping,找到后执行一写处理逻辑,中间包括处理 @RequestMappin 中的 produces/*** Expose URI template variables, matrix variables, and 【producible media types 】in the request.
     * @seeHandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
     * @seeHandlerMapping#MATRIX_VARIABLES_ATTRIBUTE
     * @seeHandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
     
     */@Override
    protected voidhandleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
        super.handleMatch(info, lookupPath, request);

        String bestPattern;
        Map<String, String>uriVariables;

        Set<String> patterns =info.getPatternsCondition().getPatterns();
        if(patterns.isEmpty()) {
            bestPattern =lookupPath;
            uriVariables =Collections.emptyMap();
        }
        else{
            bestPattern =patterns.iterator().next();
            uriVariables =getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
        }

        request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);

        if(isMatrixVariableContentAvailable()) {
            Map<String, MultiValueMap<String, String>> matrixVars =extractMatrixVariables(request, uriVariables);
            request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
        }

        Map<String, String> decodedUriVariables =getUrlPathHelper().decodePathVariables(request, uriVariables);
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);

        //处理@RequestMapping中的produces属性,后面计算合适的mediatype时会用到
        if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
            Set<MediaType> mediaTypes =info.getProducesCondition().getProducibleMediaTypes();
            request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
        }
    }

#消息转换器写消息
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
    #寻找合适的可以返回的 mediatype
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getProducibleMediaTypes(javax.servlet.http.HttpServletRequest, java.lang.Class<?>, java.lang.reflect.Type)
/*** Returns the media types that can be produced. The resulting media types are:
     * <ul>
     * <li>The producible media types specified in the request mappings, or
     * <li>Media types of configured converters that can write the specific return value, or
     * <li>{@linkMediaType#ALL}
     * </ul>
     * @since4.2
     */@SuppressWarnings("unchecked")
    protected List<MediaType>getProducibleMediaTypes(
            HttpServletRequest request, Class<?>valueClass, @Nullable Type targetType) {

        //如果注解中有则直接使用
        Set<MediaType> mediaTypes =
                (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        if (!CollectionUtils.isEmpty(mediaTypes)) {
            return new ArrayList<>(mediaTypes);
        }
        else if (!this.allSupportedMediaTypes.isEmpty()) {
            //注解中没有在根据支持的消息转换器计算出一个来
            List<MediaType> result = new ArrayList<>();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                if (converter instanceof GenericHttpMessageConverter && targetType != null) {
                    if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
                        result.addAll(converter.getSupportedMediaTypes());
                    }
                }
                else if (converter.canWrite(valueClass, null)) {
                    result.addAll(converter.getSupportedMediaTypes());
                }
            }
            returnresult;
        }
        else{
            returnCollections.singletonList(MediaType.ALL);
        }
    }

org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
    org.springframework.http.converter.AbstractHttpMessageConverter#addDefaultHeaders
/*** Add default headers to the output message.
     * <p>This implementation delegates to {@link#getDefaultContentType(Object)} if a
     * content type was not provided, set if necessary the default character set, calls
     * {@link#getContentLength}, and sets the corresponding headers.
     * @since4.2
     */
    protected void addDefaultHeaders(HttpHeaders headers, T t, @Nullable MediaType contentType) throwsIOException {
        if (headers.getContentType() == null) {
            MediaType contentTypeToUse =contentType;
            if (contentType == null || !contentType.isConcrete()) {
                contentTypeToUse =getDefaultContentType(t);
            }
            else if(MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) {
                MediaType mediaType =getDefaultContentType(t);
                contentTypeToUse = (mediaType != null ?mediaType : contentTypeToUse);
            }
            if (contentTypeToUse != null) {
                if (contentTypeToUse.getCharset() == null) {
                    Charset defaultCharset =getDefaultCharset();
                    if (defaultCharset != null) {
                        contentTypeToUse = newMediaType(contentTypeToUse, defaultCharset);
                    }
                }
                //增加计算出的 content-type , controller中设置的可以存下来,但是不会最终使用到
headers.setContentType(contentTypeToUse);
            }
        }
        if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
            Long contentLength =getContentLength(t, headers.getContentType());
            if (contentLength != null) {
                headers.setContentLength(contentLength);
            }
        }
    }



org.springframework.http.server.ServletServerHttpResponse#getBody
    org.springframework.http.server.ServletServerHttpResponse#writeHeaders
private voidwriteHeaders() {
        if (!this.headersWritten) {
            //上面的设置的头信息
            getHeaders().forEach((headerName, headerValues) ->{
                for(String headerValue : headerValues) {
                    //this.servletResponse 控制器重设置的content-type现在被覆盖掉了
                    this.servletResponse.addHeader(headerName, headerValue);
                }
            });
            //HttpServletResponse exposes some headers as properties: we should include those if not already present
//从 this.servletResponse【原始的request对象,有写会被覆盖,所以会不生效,如content-type 】中补充一些其他的头信息
if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) { this.servletResponse.setContentType(this.headers.getContentType().toString()); } if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null && this.headers.getContentType().getCharset() != null) { this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name()); } this.headersWritten = true; } }

附录

目前的 httpclient 和 okHttp中都不会传 Accept 头

#httpclient post
Array
(
    [Content-Length] => 0[Host] =>jksong.cm
    [Connection] => Keep-Alive
    [User-Agent] => Apache-HttpClient/4.5.6 (Java/1.8.0_251)
    [Accept-Encoding] =>gzip,deflate
)

#httpclient get
(
    [Host] =>jksong.cm
    [Connection] => Keep-Alive
    [User-Agent] => Apache-HttpClient/4.5.6 (Java/1.8.0_251)
    [Accept-Encoding] =>gzip,deflate
)

#okhttp get
(
    [Host] =>jksong.cm
    [Connection] => Keep-Alive
    [Accept-Encoding] =>gzip
    [User-Agent] => okhttp/3.14.4)

#okhttp post
(
    [Content-Type] => text/plain; charset=utf-8[Content-Length] => 0[Host] =>jksong.cm
    [Connection] => Keep-Alive
    [Accept-Encoding] =>gzip
    [User-Agent] => okhttp/3.14.4)
#curl get

Array
(
[Host] => jksong.cm
[User-Agent] => curl/7.64.1
[Accept] => */*
)

参考:

https://stackoverflow.com/questions/39304246/xml-serialization-jaxb-vs-jackson-dataformat-xml

https://blog.csdn.net/jiangchao858/article/details/85346041

免责声明:文章转载自《spring boot 支持返回 xml》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇C# xml显示到treeview及递归遍历、删除、添加节点(两层节点)C#线程学习笔记六:线程同步--信号量和互斥体下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

word的常用操作

using System;using System.Collections.Generic;using System.Text;using Microsoft.Office.Interop.Word;using System.IO;using System.Web;using System.Data;using System.Reflection;usin...

位置动画Android的Activity屏幕切换动画(一)左右滑动切换

查了好多资料,发现还是不全,干脆自己理整吧,至少保证在我的做法正确的,以免误导读者,也是给自己做个记录吧!     在Android发开中程过,经常会到碰Activity之间的换切效果的题问,上面绍介一下如何实现阁下滑动的换切效果,首先懂得一下Activity换切的实现,从Android2.0开始在Activity增加了一个方法:     public v...

thrift在hive中的应用

thrift在hive中的应用 » ORATEA thrift在hive中的应用 hadoop添加评论 六272011 thrift是一种可伸缩的跨语言服务的发展软件框架。它结合了功能强大的软件堆栈的代码生成引擎,以建设服务,工作效率和无缝地与C + +,C#,Java,Python和PHP和Ruby结合。thrift是facebook开...

hive基础1

Hive基础 1、介绍 Hive是OLAP(online analyze process,在线分析处理)。通常称为数据仓库,简称数仓。内置很多分析函数,可进行海量数据的在线分析处理。hive构建在hadoop之上,使用hdfs作为进行存储,计算过程采用的是Mapreduce完成,本质上hive是对hadoop的mr的封装,通过原始的mr方式进行数据处理与分...

phpmyadmin误删表后的恢复过程(心惊胆跳啊)

话说今天不知道是抽风了还是失魂了,在用phpmyadmin删除测试数据时,竟然将整个表删除了: 等程序运行出错时,才出现整个表都没有了,而且之前也没有备份好!这下蛋疼了,这个可是production服务器,里面的数据可不能丢啊! 服务器是linux的,我不是很熟悉,也不知道mysql装在哪。 无奈之下,google,发现有不少人也有像我一样犯傻的一回,...

C#综合揭秘——细说多线程(上)

引言   本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发。   其中委托的BeginInvoke方法以及回调函数最为常用。   而 I/O线程可能容易遭到大家的忽略,其实在开发多线程系统,更应该多留意I/O线程的操作。特别是在ASP.NET开发当中,可能更多人只会留意在客户端使用Ajax...