记一次@EnableJsonFilter引发的springdoc以及spring相关接口的问题

博客讲述了在SpringBoot项目中使用jfilter进行接口数据过滤时,意外导致Swagger界面无法打开及接口返回值变为XML格式的问题。问题源于jfilter引入的依赖添加了XML支持。解决方案包括排除相关jar包、自定义WebMvcConfigurer或修改jfilter源码。
摘要由CSDN通过智能技术生成

项目场景:

环境

此次影响组件
springboot2.6.6
springdoc1.6.6

背景

由于项目中需要在接口中过滤某些多余或者敏感的属性。于是同事采用jsonfilter的方法

  1. springboot中引入了相关maven导入
            <dependency>
                <groupId>com.github.rkonovalov</groupId>
                <artifactId>jfilter</artifactId>
                <version>${jfilter.version}</version>
            </dependency>
  1. 在application头信息上添加@EnableJsonFilter
  2. 在接口方法上添加@FieldFilterSetting
    eg:
@FieldFilterSetting(className = DataSourceDetectionInfo.class, fields = {"id", "pass"}, behaviour = FilterBehaviour.HIDE_FIELDS)

添加完成之后发现我们想要达成的目标完成了,但是同时带来一个问题,那就是我们的springdoc生成的swagger界面打不开了,业务测试接口也出现了问题,还好在开发阶段,于是上手定位


问题描述

  1. swagger的页面报错如下:
Unable to render this definition
The provided definition does not specify a valid version field.

Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0).
  1. 接口报错为json解析不了,发现返回值类似于这样:
<Response>
<success>true</success>
<data>true</data>
</Response>

原因分析:

看到上述现象其实问题已经很明显了,我们接口的返回值被转换成了xml格式,并不是json格式。

为了进一步确认swagger的问题,进一步调用swagger的接口

http://127.0.0.1:9091/v3/api-docs

发现返回值如下:

"{\"openapi\":\"3.0.1\",\"info\":{\"title\":\"workflow's Open API\",\"description\":\"workflow\"},\"servers\":[{\"url\":\"http://127.0.0.1:9091\",\"description\":\"Generated server url\"}],\"tags\":[{}]}"

它是个字符串,并不是json对象
那为什么会出现这种问题呢,我们在头上加了 Accept:application/json,其实是可以解决的,但是毕竟问题是新引入的,我们得知道为什么,最好的办法就是打开代码去debug。

  1. 启动工程,直接调用接口,如果熟悉springmvc过程,可以直接打开类
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite

如果不熟悉可以一步步定位,例如从

org.springframework.web.servlet.FrameworkServlet

开始入手,

org.springframework.web.servlet.FrameworkServlet#processRequest
org.springframework.web.servlet.DispatcherServlet#doService
org.springframework.web.servlet.DispatcherServlet#doDispatch
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)

关键方法如下
在这里插入图片描述
我们看下 producibletypes的值是什么
在这里插入图片描述
acceptableTypes 这个数组的集合是*/* 那也就是要自动推测了,首先我们看到第一个就是xml,再往后看怎么去取的第一个
在这里插入图片描述
首先进行了排序(内部实现了自定义比较器),然后如果是*/那就取第一个值,那就很明显了,最终使用了xml来序列化我们的接口返回值。
接下来要搞清楚的就是MediaType里面并没有
+xml,为什么会有这个,那就要回过头来看getProducibleMediaTypes 这个方法
在这里插入图片描述
数组里第一个值是FilterConverter,这个类做了什么,又是那里来的的呢
在这里插入图片描述
看到他自己添加了这几种类型的支持。
直接看他的父类,往下找所有的子类
在这里插入图片描述
我们找对应jar,来通过mvn dependency:tree 来找一下是哪个pom引进来的
在这里插入图片描述
发现正好是我们新引入的这个类,至此罪魁祸首找到了
在往下看StringHttpMessageConverter 和 FilterConverter 的writeInternal 的实现有什么区别
FilterConverter

    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        MediaType contentType = outputMessage.getHeaders().getContentType();
        if (object instanceof FilterClassWrapper) {
            FilterClassWrapper wrapper = (FilterClassWrapper)object;
            ObjectMapper objectMapper = this.filterConfiguration.getObjectMapperCache().findObjectMapper(wrapper.getMethodParameterDetails());
            objectMapper.writeValue(outputMessage.getBody(), wrapper.getObject());
        } else {
            ObjectMapper objectMapper = this.filterConfiguration.getMapper(contentType);
            objectMapper.writeValue(outputMessage.getBody(), object);
        }

    }

StringHttpMessageConverter

 protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
        HttpHeaders headers = outputMessage.getHeaders();
        if (this.writeAcceptCharset && headers.get("Accept-Charset") == null) {
            headers.setAcceptCharset(this.getAcceptedCharsets());
        }

        Charset charset = this.getContentTypeCharset(headers.getContentType());
        StreamUtils.copy(str, charset, outputMessage.getBody());
    }

由上述代码看到具体的实现类上一个是用jackson去write,另一个是用StringUtils去拷贝的,而springdoc的swagger的借口返回值是string,用json去格式话就会出现将原来的String的引号做转义,测试代码如下:

    public static void main(String[] args) throws IOException {
	        String s = "{\"openapi\":\"3.0.1\"}";
        final ObjectMapper build = Jackson2ObjectMapperBuilder.json().build();
        build.writeValue(new File("./openapi.text"),s);
        final FileOutputStream fileOutputStream = new FileOutputStream(new File(".openapi-string.text"));

        StreamUtils.copy(s, Charset.defaultCharset(),fileOutputStream);
        fileOutputStream.close();

解决方案:

看到上述分析,其实问题很明显,我们尝试一下几种方案:

  1. 排除掉这个jar包
<dependency>
                <groupId>com.github.rkonovalov</groupId>
                <artifactId>jfilter</artifactId>
                <version>${jfilter.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>com.fasterxml.jackson.dataformat</groupId>
                        <artifactId>jackson-dataformat-xml</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>

但是排除了之后,我们过滤的功能就会报错
2. 第二种是通过实现 WebMvcConfigurer

@Configuration
@EnableWebMvc
public class WebInterceptorAdapter implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON, MediaType.TEXT_XML, MediaType.APPLICATION_XML,MediaType.ALL);
    }
}

然后发现接口的问题解决了,但是springdoc的swagger的界面还是打不开。
3. 拉取工程https://github.com/rkonovalov/jfilter,修改源码如下

@Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        MediaType contentType = outputMessage.getHeaders().getContentType();


        //If object is FilterClassWrapper try to serialize object using filters(if configured)
        if (object instanceof FilterClassWrapper) {
            FilterClassWrapper wrapper = (FilterClassWrapper) object;

            //Retrieving ObjectMapper from ObjectMapperCache
            ObjectMapper objectMapper = filterConfiguration.getObjectMapperCache()
                    .findObjectMapper(wrapper.getMethodParameterDetails());

            //Serialize object with ObjectMapper
            objectMapper.writeValue(outputMessage.getBody(), wrapper.getObject());
        } else if(object instanceof String){
            HttpHeaders headers = outputMessage.getHeaders();
            Charset charset = this.getContentTypeCharset(headers.getContentType());
            StreamUtils.copy(String.valueOf(object), charset, outputMessage.getBody());
        } else {
            //Otherwise try to serialize object without filters by default ObjectMapper from filterConfiguration
            ObjectMapper objectMapper = filterConfiguration.getMapper(contentType);
            objectMapper.writeValue(outputMessage.getBody(), object);
        }
    }

打包测试,返回值正常


但是修改之后需要完整测试,所以临时将@EnableJsonFilter 拿掉,等测试没有问题之后再使用此注解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值