Feign 多文件未知异常!原因:null

问题说明

项目中需要用到feign调用多文件的接口,出现这个问题的前提是已经参照多数博客中的方法,为feign客户端重写了编码器的配置,可以参照feign多文件上传。我不知道这些博主有没有出现我这个问题,但是我按照他们的配置后,确出现了以下问题,前台返回了这个错误

未知异常!原因是: null

后台的异常是,无关紧要的省略了

feign.codec.EncodeException
	.
	.
	.
Caused by: java.lang.NullPointerException
	at feign.form.util.PojoUtil.isUserPojo(PojoUtil.java:50)
	at feign.form.multipart.PojoWriter.isApplicable(PojoWriter.java:40)
	at feign.form.MultipartFormContentProcessor.findApplicableWriter(MultipartFormContentProcessor.java:159)
	at feign.form.MultipartFormContentProcessor.process(MultipartFormContentProcessor.java:86)
	at feign.form.FormEncoder.encode(FormEncoder.java:105)
	at cn.farmall.file.config.FeignSpringFormEncoder.encode(FeignSpringFormEncoder.java:42)
	at feign.ReflectiveFeign$BuildEncodedTemplateFromArgs.resolve(ReflectiveFeign.java:372)
	... 100 more

解决办法

在自定义的在自定义的的编码中,修改传输文件的委托编码器顺序

public FeignSpringFormEncoder(Encoder delegate) {
        super(delegate);
        MultipartFormContentProcessor processor = (MultipartFormContentProcessor)this.getContentProcessor(ContentType.MULTIPART);
        //前移添加MultipartFiles的委托编码器(多文件)
        processor.addFirstWriter(new SpringManyMultipartFilesWriter());
        //前移添加MultipartFile的委托编码器(单文件)
        processor.addFirstWriter(new SpringSingleMultipartFileWriter());
        //别人的方法,添加MultipartFiles的委托编码器(多文件)
//        processor.addWriter(new SpringManyMultipartFilesWriter());
        //别人的方法,添加MultipartFile的委托编码器(单文件)
//        processor.addWriter(new SpringSingleMultipartFileWriter());
    }

问题分析(以下不感兴趣可跳过,只是记录自己思考的过程,内容顺序上可能有些杂乱)

开始出现这问题的时候,我一直以为,我经过一番删代码,找其他办法重写,再删代码,再找其他办法重写,三删代码,三找其他办法重写,一番体验和效果都极佳的百度处理后。我对未来充满了信心,喜悦之情油然而生。在这里插入图片描述
找你妹啊,全都是按照一个套路,我不要面子的。于是乎我只能忽略我是个菜鸡,开始调试源码。(这个反反复复的弄了几次,虽然已经找到错误位置,可是开始并没有想到怎么处理,主要是刚刚开始学这个,对一些处理并不懂,哪怕是参考人家的代码,也不懂为什么要这么做,自己菜就不多说了。)

  1. 为什么出现空指针的异常,从异常日志可以看到是 feign.form.util.PojoUtil.isUserPojo(PojoUtil.java:50) 这个地方出现的异常,这是什么,不能着急,从根源跟踪一些它在搞什么东西,打个断点跟踪(以下代码说明断点位置而已,防止有人看,多说一点废话,万一有跟我一样的萌新呢,要一步一步来),这个方法在参考文章中的FeignSpringFormEncoder 方法
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
		//单个文件
        if (bodyType.equals(MultipartFile.class)) {
            MultipartFile file = (MultipartFile) object;
            Map<String, Object> data = Collections.singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
            return;
        } else if (bodyType.equals(MultipartFile[].class)) { //多个文件
            MultipartFile[] file = (MultipartFile[]) object;
            if(file != null) {
                Map<String, Object> data = Collections.singletonMap(file.length==0?"":file[0].getName(), object);
                //断点打在下面这里              
                super.encode(data, MAP_STRING_WILDCARD, template);
                return;
            }
        }
        super.encode(object, bodyType, template);
    }
  1. 自定义的编码器都调用的父类的方法,看看父类做了什么
@Override
  @SuppressWarnings("unchecked")
  public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
    String contentTypeValue = getContentTypeValue(template.headers());
    val contentType = ContentType.of(contentTypeValue);
    if (!processors.containsKey(contentType)) {
      delegate.encode(object, bodyType, template);
      return;
    }
	//通过判断传输数据的类型转换数据,我们指明数据是以MAP_STRING_WILDCARD(Form)提交
    Map<String, Object> data;
    if (MAP_STRING_WILDCARD.equals(bodyType)) {
      data = (Map<String, Object>) object;
    } else if (isUserPojo(bodyType)) {
      data = toMap(object);
    } else {
      delegate.encode(object, bodyType, template);
      return;
    }
	
    val charset = getCharset(contentTypeValue);
    //processors是一个Map,里面有两个processor
    //MULTIPART-> MultipartFormContentProcessor
    //URLENCODED-> UrlencodedFormContentProcessor
	//这里是去处理数据了,跟进去看看(contentType = MULTIPART)
    processors.get(contentType).process(template, charset, data);
  }
  1. MultipartFormContentProcessor.process 方法在定义的writers中找到第一个适合处理数据的writer去处理数据
@Override
  public void process (RequestTemplate template, Charset charset, Map<String, Object> data) throws EncodeException {
    val boundary = Long.toHexString(System.currentTimeMillis());
    val output = new Output(charset);

    for (val entry : data.entrySet()) {
      if (entry == null || entry.getKey() == null || entry.getValue() == null) {
        continue;
      }
      //这里查找适用的编码器,看看它做什么
      val writer = findApplicableWriter(entry.getValue());
      writer.write(output, boundary, entry.getKey(), entry.getValue());
    }

    output.write("--").write(boundary).write("--").write(CRLF);

    val contentTypeHeaderValue = new StringBuilder()
        .append(getSupportedContentType().getHeader())
        .append("; charset=").append(charset.name())
        .append("; boundary=").append(boundary)
        .toString();

    template.header(CONTENT_TYPE_HEADER, Collections.<String>emptyList()); // reset header
    template.header(CONTENT_TYPE_HEADER, contentTypeHeaderValue);

    // Feign's clients try to determine binary/string content by charset presence
    // so, I set it to null (in spite of availability charset) for backward compatibility.
    val bytes = output.toByteArray();
    val body = Request.Body.encoded(bytes, null);
    template.body(body);

    try {
      output.close();
    } catch (IOException ex) {
      throw new EncodeException("Output closing error", ex);
    }
  }

MultipartFormContentProcessor.findApplicableWriter 方法调用各个子类的isApplicable方法判断writer适不适合

private Writer findApplicableWriter (Object value) {
	//循环writer集,看看哪个可以处理这个数据直接返回,找不到就用默认的
    for (val writer : writers) {
      if (writer.isApplicable(value)) {
        return writer;
      }
    }
    return defaultPerocessor;
  }
  1. writers 有什么writer,通过端口可以发现有以下的编码器
    在这里插入图片描述
    它们都是哪来的,在MultipartFormContentProcessor 构造函数里面默认添加了集中,而后两种是在自定义的 FeignSpringFormEncoder 中为MultipartFormContentProcessor添加的
public MultipartFormContentProcessor (Encoder delegate) {
    writers = new LinkedList<Writer>();
    addWriter(new ByteArrayWriter());
    addWriter(new FormDataWriter());
    addWriter(new SingleFileWriter());
    addWriter(new ManyFilesWriter());
    addWriter(new SingleParameterWriter());
    addWriter(new ManyParametersWriter());
    addWriter(new PojoWriter(writers));
    defaultPerocessor = new DelegateWriter(delegate);
  }
public FeignSpringFormEncoder(Encoder delegate) {
        super(delegate);
        MultipartFormContentProcessor processor = (MultipartFormContentProcessor)this.getContentProcessor(ContentType.MULTIPART);
        //前移添加MultipartFiles的委托编码器(多文件)
        processor.addFirstWriter(new SpringManyMultipartFilesWriter());
        //前移添加MultipartFile的委托编码器(单文件)
        processor.addFirstWriter(new SpringSingleMultipartFileWriter());
        //别人的方法,添加MultipartFiles的委托编码器(多文件)
//        processor.addWriter(new SpringManyMultipartFilesWriter());
        //别人的方法,添加MultipartFile的委托编码器(单文件)
//        processor.addWriter(new SpringSingleMultipartFileWriter());
    }

在这些writer中,猛然发现一个扎眼的词PojoWriter, 错误里面不就有Pojo PojoWriter 里面判断用的方法就是PojoUtil.isUserPojo

public static boolean isUserPojo (@NonNull Object object) {
    val type = object.getClass();
    //这里,我们传过来的数据是MultipartFile[],它是找不到包名的,所以这里出现了空指针
    val packageName = type.getPackage().getName();
    return !packageName.startsWith("java.");
  }

阿弥陀佛,我找到你了,我要度化你这顽劣的代码。可是这是框架上的源码啊,就找这都搞了半天。
Artem Labazin:你敢动吗?
我:我不敢动。
鉴于对大佬的敬畏心,姑且放过这顽码,我只能另谋他路。
发动脑力技能:正常思考:按照正常来说,这里应该是返回false,但是因为找不到包名,直接空指针异常打断了查找writer的过程,而自己添加的文件writer就不能拥有姓名。其实在 MultipartFormContentProcessor.findApplicableWriter只要找到合适的writer就直接返回了,所以并不需要进到PojoWriter。那是不是可以把我们自己添加的writer 排在 PojoWriter前就可以了呢,于是我就找了一些addWriter 都有哪些方法,就发现了addFirstWriter,直接加在最前面,这里本来就是用来处理文件的嘛,加在最前面它不香吗

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: "feign.FeignException$NotFound" 是一个 Java 编程语言中的异常类,通常在使用 Feign 客户端进行 HTTP 请求时出现。当 Feign 客户端尝试访问一个不存在的 URL 资源时,就会抛出这个异常。这种异常表示在服务器上找不到请求的资源,可能是因为 URL 地址不正确或请求的资源不存在。要解决这个问题,可以检查请求的 URL 是否正确,并确保所请求的资源确实存在于服务器上。 ### 回答2: Feign 是一个轻量级的 HTTP 客户端库,可以让我们通过简单的接口定义来模拟 RESTful 服务的接口调用,从而使得我们能够更加高效的编写客户端代码。当我们使用 Feign 进行接口调用时,有时候会遇到 `feign.FeignException$NotFound` 的错误。那么这个错误是什么原因引起的呢? `feign.FeignException$NotFound` 表示 Feign 调用远程服务时,服务返回了 404 错误,即资源没有找到。这种情况可以有以下常见原因: 1. 接口地址输入错误:可能是因为 URL 不正确,或者是因为接口已经改变了路径,需要确认接口地址是否正确。 2. 请求方式不正确:可能是因为客户端使用了不正确的 HTTP 请求方式,服务端不支持当前请求方式。 3. 接口逻辑不正确:可能是因为服务端没有根据接口规范实现接口逻辑,导致客户端调用时产生错误。 4. 非法参数:可能是因为将非法的参数传递给了服务端,导致服务端无法处理请求。 为了解决 `feign.FeignException$NotFound` 的错误,我们需要根据具体的情况进行排查。可以通过查看日志、调试代码、查看接口文档等方式来找到错误的原因,最终根据具体情况进行修复。需要注意的是,当服务端返回 404 错误时,我们需要优雅的处理这种错误结果,而不是简单的抛出异常,否则会影响系统的稳定性。 ### 回答3: Feign是一个优秀的Java HTTP客户端框架。在进行Feign调用时,有时候可能会遇到FeignException$NotFound异常。这种异常通常表示发起的请求并没有在服务器上找到对应的资源或接口。 出现这种异常原因有很多种。有可能是因为请求的URL路径错误,或者服务器端已经将该接口删除。也可能是因为请求的参数有误,或者是请求头缺失导致服务器无法识别该请求。另外,还有一种可能是服务器端出现了异常导致资源无法被正确地获取。 如果我们需要排查这种异常,通常需要先检查请求参数和请求头是否正确并完整,并且要确保请求的URL路径是否与服务器端的路径一致。如果以上检查都正确,那么我们需要查看服务器端接口实现是否正确、是否能够正常调用。此外,还需要检查服务器端的日志,以查看是否存在其他的异常信息。 在我们排查完各种可能情况后,如果问题还未得到解决,那么我们可以考虑与服务器端的开发人员进行联系,通过进行沟通来解决这个问题。 总之,当我们遇到这种异常时,我们需要通过仔细地检查请求参数、请求头和服务器端的实现等方面来解决这个问题,从而保障接口调用的正确性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值