欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Cloud中使用Feign,@RequestBody无法继承的解决方案

程序员文章站 2022-06-19 14:44:28
目录spring cloud中使用feign,@requestbody无法继承的问题3、feignclient接口中,如果使用到@pathvariable,必须指定其valuespring cloud...

spring cloud中使用feign,@requestbody无法继承的问题

根据官网feignclient的例子,编写一个简单的updateuser接口,定义如下

@requestmapping("/user")
public interface userservice {
    @requestmapping(value = "/{userid}", method = requestmethod.get)
    userdto finduserbyid(@pathvariable("userid") integer userid);
    @requestmapping(value = "/update", method = requestmethod.post)
    boolean updateuser(@requestbody userdto user);
}

实现类

 @override
    public boolean updateuser(userdto user)
    {   
        logger.info("===updateuser, id = " + user.getid() + " ,name= " + user.getusername());
        return false;
    }

执行单元测试,发现没有获取到预期的输入参数

2018-09-07 15:35:38,558 [http-nio-8091-exec-5] info [com.springboot.user.controller.usercontroller] {} - ===updateuser, id = null ,name= null

原因分析

springmvc中使用requestresponsebodymethodprocessor类进行入参、出参的解析。以下方法根据参数是否有@requestbody注解判断是否进行消息体的转换。

@override
    public boolean supportsparameter(methodparameter parameter) {
        return parameter.hasparameterannotation(requestbody.class);
    }

解决方案

既然mvc使用requestresponsebodymethodprocessor进行参数解析,可以实现一个定制化的processor,修改supportparameter的判断方法。

 @override
    public boolean supportsparameter(methodparameter parameter)
    {
        //springcloud的接口入参没有写@requestbody,并且是自定义类型对象 也按json解析
        if (annotatedelementutils.hasannotation(parameter.getcontainingclass(), feignclient.class) && iscustomizedtype(parameter.getparametertype())) {
            return true;
        }
        return super.supportsparameter(parameter);
    }

此处的判断逻辑可以根据实际框架进行定义,目的是判断到为spring cloud定义的接口,并且是自定义对象时,使用@requestbody相同的内容转换器。

实现定制化的processor后,还需要让自定义的配置生效,有两种方案可选:

  • 直接替换requestresponsebodymethodprocessor,在springboot下需要自定义requestmappinghandleradapter。
  • 实现webmvcconfigurer中的addargumentresolvers接口

这里采用较为简单的第二种方式,初始化时的消息转换器根据需要进行加载:

public class xxxwebmvcconfig implements webmvcconfigurer
{
@override
    public void addargumentresolvers(list<handlermethodargumentresolver> resolvers)
    {
        stringhttpmessageconverter stringhttpmessageconverter = new stringhttpmessageconverter();
        stringhttpmessageconverter.setwriteacceptcharset(false);
        list<httpmessageconverter<?>> messageconverters = new arraylist<>(5);
        messageconverters.add(new bytearrayhttpmessageconverter());
        messageconverters.add(stringhttpmessageconverter);
        messageconverters.add(new sourcehttpmessageconverter<>());
        messageconverters.add(new allencompassingformhttpmessageconverter());
        customizedmappingjackson2httpmessageconverter jackson2httpmessageconverter = new customizedmappingjackson2httpmessageconverter();
        jackson2httpmessageconverter.setobjectmapper(defaultobjectmapper());
        messageconverters.add(jackson2httpmessageconverter);
        viomimvcrequestresponsebodymethodprocessor resolver = new viomimvcrequestresponsebodymethodprocessor(messageconverters);
        resolvers.add(resolver);
    }

修改完成后,微服务的实现类即可去掉@requestbody注解。

spring cloud 使用feign遇到的问题

spring cloud 使用feign 项目的搭建 在这里就不写了,本文主要讲解在使用过程中遇到的问题以及解决办法

1、示例

@requestmapping(value = "/generate/password", method = requestmethod.post)
keyresponse generatepassword(@requestbody string passwordseed);

在这里 只能使用 @requestmapping(value = "/generate/password", method = requestmethod.post) 注解 不能使用

@postmapping 否则项目启动会报

caused by: java.lang.illegalstateexception: method generatepassword not annotated with http method type (ex. get, post) 异常

2、首次访问超时问题

原因:hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。

而首次请求往往会比较慢(因为spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了。

解决方法:

<1:配置hystrix的超时时间改为5秒

hystrix.command.default.execution.isolation.thread.timeoutinmilliseconds: 5000

<2:禁用hystrix的超时时间

hystrix.command.default.execution.timeout.enabled: false

<3:禁用feign的hystrix 功能

feign.hystrix.enabled: false

注:个人推荐 第一 或者第二种 方法

3、feignclient接口中,如果使用到@pathvariable,必须指定其value

spring cloud feign 使用 apache httpclient

问题:1 没有指定 content-type 是情况下 会出现如下异常 ? 这里很纳闷?

caused by: java.lang.illegalargumentexception: mime type may not contain reserved characters

在这里有兴趣的朋友可以去研究下源码

apachehttpclient.class 
  private contenttype getcontenttype(request request) {
    contenttype contenttype = contenttype.default_text;
    for (map.entry<string, collection<string>> entry : request.headers().entryset())
    // 这里会判断 如果没有指定 content-type 属性 就使用上面默认的 text/plain; charset=iso-8859-1
    // 问题出在默认的 contenttype : 格式 text/plain; charset=iso-8859-1 
    // 转到 contenttype.create(entry.getvalue().iterator().next(), request.charset()); 方法中看
    if (entry.getkey().equalsignorecase("content-type")) {
      collection values = entry.getvalue();
      if (values != null && !values.isempty()) {
        contenttype = contenttype.create(entry.getvalue().iterator().next(), request.charset());
        break;
      }
    }
    return contenttype;
  }
contenttype.class
   public static contenttype create(final string mimetype, final charset charset) {
        final string normalizedmimetype = args.notblank(mimetype, "mime type").tolowercase(locale.root);
 // 问题在这 check  中 valid f方法中
        args.check(valid(normalizedmimetype), "mime type may not contain reserved characters");
        return new contenttype(normalizedmimetype, charset);
    }
   private static boolean valid(final string s) {
        for (int i = 0; i < s.length(); i++) {
            final char ch = s.charat(i);
     // 这里 在上面 text/plain;charset=utf-8 中出现了 分号 导致检验没有通过 
            if (ch == '"' || ch == ',' || ch == ';') {
                return false;
            }
        }
        return true;
    }

解决办法 :

@requestmapping(value = "/generate/password", method = requestmethod.post, consumes = mediatype.application_json_value)

注解中指定: content-type 即 指定 consumes 的属性值 : 这里 consumes 属性的值在这不做具体讲解,有兴趣的可以去研究下

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。