仿Java实现的,DotNet版本的Feign类库

本文介绍了一款基于.NET的Feign库——Beinet.Feign,详细讲解了其使用方法,包括常规调用、配置读取、添加Header、自定义配置等内容,适合.NET开发者了解如何优雅地封装HTTP调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

- 简介

Feign是Java里的一个声明式的http api请求库,可以通过注解(类似.Net的特性)来快速并优雅的封装对http的调用,并且方便理解和后续的维护,已经广泛的在Spring Cloud的解决方案中应用。

基于这些优点,我也为.Net封装了一个类似的类库:Beinet.Feign,下面简单介绍一下使用方法。
注1:该库基于Framework4.0开发(可以支持WinXP系统),并依赖如下2个库:
LinFu.DynamicProxy.OfficialRelease 1.0.5以上
Newtonsoft.Json 12.0.3以上
注2:完整的调用Demo代码已上传到Git,Beinet.Feign库源代码参考 调用Demo代码参考.

- QuickStart 常规调用代码

1、接口DTO对象定义:

// DTO对象,属性可以跟响应的大小写 不一样
public class FeignDtoDemo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime AddTime { get; set; }
    public Work[] Works { get; set; }
 
    public string Url { get; set; }  // api支持,调用的完整url
    public string Post { get; set; } // api支持,调用的完整Form数据,比如a=1&b=2
    public string Stream { get; set; }// api支持,调用的完整Stream流数据,比如json
    public Dictionary<string, string> Headers { get; set; }// api支持,请求的完整Header
}
 
public class Work
{
    public int Id { get; set; }
    public string Company { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
}

2、HTTP API接口声明:

[FeignClient("", Url = "https://47.107.125.247")]
public interface FeignTestQuick
{
    // http无参接口 无返回值
    [GetMapping("test/api.aspx?flg=1")]
    void Get();
 
    // http无参接口,返回数值
    [GetMapping("test/api.aspx?flg=1")]
    int GetMs();
 
    // http有参接口返回数值,通过RequestParam把参数拼接到url里
    [GetMapping("test/api.aspx?flg=2")]
    int GetAdd([RequestParam]int n1, [RequestParam("n2")]int second2);
 
    // http有参接口,POST返回数值,通过占位符把参数拼接到url里
    [PostMapping("test/api.aspx?flg=2&n1={num1}&n2={num2}")]
    int PostAdd([RequestNone]int num1, [RequestNone]int num2);
 
    // http无参接口返回json字符串,不需要反序列化,想自行处理可以用
    [GetMapping("test/api.aspx")]
    string GetDtoStr();
 
    // http无参接口返回dto对象
    [GetMapping("test/api.aspx")]
    FeignDtoDemo GetDtoObj();
 
    // POST有参,返回dto对象,通过RequestParam把参数拼接到url里
    [PostMapping("test/api.aspx")]
    FeignDtoDemo PostDtoObj([RequestParam]int id, [RequestParam]string name);
 
    // POST参数为对象,并自定义url参数名为urlPara,返回dto对象
    [PostMapping("test/api.aspx")]
    FeignDtoDemo PostDtoObj(FeignDtoDemo dto, [RequestParam("urlPara")]string arg2);
 
    // 返回类型为object,等效于返回string
    [GetMapping("test/api.aspx")]
    object GetObj();
}

3、发起Http调用的代码:

static void TestQuick()
{
    FeignTestQuick feign = ProxyLoader.GetProxy<FeignTestQuick>();
 
 
    feign.Get();
 
    int ret1 = feign.GetMs();
    WriteMsg(ret1);
 
    int ret2 = feign.GetAdd(12, 34);
    WriteMsg(ret2);
 
    int ret3 = feign.PostAdd(56, 78);
    WriteMsg(ret3);
 
    string json = feign.GetDtoStr();
    WriteMsg(json);
 
    FeignDtoDemo dto1 = feign.GetDtoObj();
    WriteMsg(JsonConvert.SerializeObject(dto1));
 
    FeignDtoDemo dto2 = feign.PostDtoObj(11, "fankuai");
    WriteMsg(JsonConvert.SerializeObject(dto2));
 
 
    FeignDtoDemo dto3 = feign.PostDtoObj(dto2, "xxx");
    WriteMsg(JsonConvert.SerializeObject(dto3));
 
    object obj = feign.GetObj();
    WriteMsg($"返回类型:{dto3.GetType()}");
    WriteMsg(JsonConvert.SerializeObject(obj));
}
private static int _idx = 0;
public static void WriteMsg(object msg)
{
    var ret = Interlocked.Increment(ref _idx);
    Console.WriteLine($"{ret.ToString()}: {msg}\r\n");
}

- URL或路由从配置读取的Demo代码

1、接口DTO对象定义参考上面的定义;
2、在App.Config或Web.Config里添加如下配置:

<configuration>
    <appSettings>
        <add key="env" value="prod"/>
        <add key="ConfigKey" value="123456"/>

3、HTTP API接口声明如下:

// {env} 从app.config文件中读取配置,也可以整个Url读取配置,如 Url="{env}"
[FeignClient("", Url = "https://47.107.125.247/{env}/cc")]
public interface FeignTestPlace
{
    // 占位符 num1和num2从方法参数读取,
    // 占位符 ConfigKey从app.config文件中读取配置
    [GetMapping("test/api.aspx?n1={num1}&n2={num2}&securekey={ConfigKey}")]
    FeignDtoDemo GetDtoObj([RequestNone]int num1, [RequestNone]int num2);
}

4、发起Http调用的代码,最终url,经过读取配置和参数组合后,是: https://47.107.125.247/prod/cc/test/api.aspx?n1=12&n2=45&securekey=123456

static void TestPlace()
{
    FeignTestPlace feign = ProxyLoader.GetProxy<FeignTestPlace>();
 
    // 如下代码发起的HTTP请求,最终的url是: https://47.107.125.247/cc/test/api.aspx?n1=12&n2=45&securekey=123456
    FeignDtoDemo dto1 = feign.GetDtoObj(12, 45);
    WriteMsg(JsonConvert.SerializeObject(dto1));
}

- 给请求添加Header的Demo代码

1、接口DTO对象定义参考上面的定义;
2、HTTP API接口声明如下:

[FeignClient("", Url = "https://47.107.125.247")]
public interface FeignTestHeader
{
    // 在方法特性里增加header
    [GetMapping("test/api.aspx", Headers = new string[] { "headerName=headerValue", "user-agent=beinet feign1234" })]
    FeignDtoDemo GetDtoObj();
 
    // 在参数特性里增加header,一个使用参数名作为header name,一个使用自定义header name
    [GetMapping("test/api.aspx")]
    FeignDtoDemo GetDtoObj([RequestHeader]string headerName, [RequestHeader("RealHeaderName")]string arg2);
}

3、发起Http调用的代码:

static void TestHeader()
{
    FeignTestHeader feign = ProxyLoader.GetProxy<FeignTestHeader>();
 
    // http调用前,会添加header:"User-Agent":"beinet feign1234", "headerName":"headerValue"
    FeignDtoDemo dto1 = feign.GetDtoObj();
    WriteMsg(JsonConvert.SerializeObject(dto1));
 
    // http调用前,会添加header:"headerName":"header1","RealHeaderName":"header2"
    FeignDtoDemo dto2 = feign.GetDtoObj("header1", "header2");
    WriteMsg(JsonConvert.SerializeObject(dto2));
}

- 使用System.Uri类型参数,修改方法发起请求的url

1、接口DTO对象定义参考上面的定义;
2、HTTP API接口声明如下:

[FeignClient("", Url = "https://47.107.125.247")]
public interface FeignTestURI
{
    // 参数中存在URI类型,且不为空时,会忽略FeignClient的Url配置
    [GetMapping("test/api.aspx")]
    FeignDtoDemo GetDtoObj(Uri uri);
 
    // 参数中存在URI类型,且不为空时,会忽略FeignClient的Url配置
    [GetMapping("test/api.aspx")]
    FeignDtoDemo GetDtoObj(string arg1, Uri uri);
}

3、发起Http调用的代码:

// 参数中存在URI类型,且不为空时,会忽略FeignClient的Url配置
static void TestURI()
{
    FeignTestURI feign = ProxyLoader.GetProxy<FeignTestURI>();
    Uri uri = new Uri("https://47.107.125.247/cc");
 
    // 请求为 GET https://47.107.125.247/cc/test/api.aspx
    FeignDtoDemo dto1 = feign.GetDtoObj(uri);
    WriteMsg(JsonConvert.SerializeObject(dto1));
 
    // 请求为 POST https://47.107.125.247/cc/test/api.aspx Stream为abc
    FeignDtoDemo dto2 = feign.GetDtoObj("abc", uri);
    WriteMsg(JsonConvert.SerializeObject(dto2));
 
    // uri参数传空,使用类定义的url,即 GET https://47.107.125.247/test/api.aspx
    FeignDtoDemo dto3 = feign.GetDtoObj(null);
    WriteMsg(JsonConvert.SerializeObject(dto3));
}

- 使用Type类型参数,修改方法返回数据类型

1、接口DTO对象定义参考上面的定义;
2、HTTP API接口声明如下:

[FeignClient("", Url = "https://47.107.125.247")]
public interface FeignTestArgType
{
    // 参数中存在Type类型,且不为空时,会把返回值反序列化为该Type,注意type必须是返回类型的子类
    [GetMapping("test/api.aspx")]
    object GetDtoObj(Type type);
 
    // 参数中存在URI类型,且不为空时,会忽略FeignClient的Url配置
    [GetMapping("test/api.aspx")]
    object GetDtoObj(string arg1, Type type);
 
    // 参数中存在Type类型,且Type不是返回类型的子类时,会抛异常
    [GetMapping("test/api.aspx")]
    FeignDtoDemo GetErr(Type type);
}

3、发起Http调用的代码:

static void TestArgType()
{
    FeignTestArgType feign = ProxyLoader.GetProxy<FeignTestArgType>();
    Type type = typeof(FeignDtoDemo);
 
    object dto1 = feign.GetDtoObj(type);
    WriteMsg($"返回类型:{dto1.GetType()}");
    WriteMsg(JsonConvert.SerializeObject(dto1));
 
    object dto2 = feign.GetDtoObj("123", type);
    WriteMsg($"返回类型:{dto2.GetType()}");
    WriteMsg(JsonConvert.SerializeObject(dto2));
 
    object dto3 = feign.GetDtoObj(null);
    WriteMsg($"返回类型:{dto3.GetType()}");
    WriteMsg(JsonConvert.SerializeObject(dto3));
 
    try
    {
        feign.GetErr(typeof(object));
    }
    catch (Exception exp)
    {
        WriteMsg(exp);
    }
}

- 自定义配置:拦截请求,自定义序列化和自定义异常处理等

1、接口DTO对象定义参考上面的定义;
2、添加自定义配置类,继承自FeignDefaultConfig(也可以从 IFeignConfig 接口继承),定义如下:

public class FeignConfigDeom : FeignDefaultConfig
{
    // 返回HTTP请求前后的拦截器
    public override List<IRequestInterceptor> GetInterceptor()
    {
        return new List<IRequestInterceptor>()
        {
            new RequestInterceptDemo()
        };
    }
 
    // 如果要对post数据,自定义序列化器,可以重写此方法
    public override string Encoding(object arg)
    {
        return base.Encoding(arg);
    }
 
    // 如果要对api返回的数据,自定义反序列化器,可以重写此方法
    public override object Decoding(string str, Type returnType)
    {
        // 注意:返回的object必须是returnType类型
        return base.Decoding(str, returnType);
    }
 
    // 如果要自行处理http请求返回的异常,重写此方法,返回null将不抛出异常
    public override Exception ErrorHandle(Exception exp)
    {
        return base.ErrorHandle(exp);
    }
}
 
public class RequestInterceptDemo : IRequestInterceptor
{
    private DateTime _beginTime;
 
    // 需要对发起请求的url进行处理时,在这里操作
    public Uri OnCreate(Uri url)
    {
        if(url.ToString().EndsWith("xxx"))
            return new Uri("https://www.beinet.com/xxx");  // 返回一个错误地址用于测试
        return url;
    }
 
    // 在HttpWebRequest.GetResponse之前执行的方法,比如记录日志,添加统一header
    public void BeforeRequest(HttpWebRequest request)
    {
        request.Headers.Add("aaa", "bbb");
        request.UserAgent = "bbbbb";
        request.Timeout = 1000;
 
        Console.WriteLine(request.Method + " " + request.RequestUri);
        Console.WriteLine(request.Headers);
 
        _beginTime = DateTime.Now;
    }
 
    // 在HttpWebRequest.GetResponse之后执行的方法,比如记录日志
    public void AfterRequest(HttpWebRequest request, HttpWebResponse response, Exception exp)
    {
        var costTime = (DateTime.Now - _beginTime).TotalMilliseconds.ToString("N0");
        Console.WriteLine($"{request.RequestUri} 耗时:{costTime}毫秒");
        if (response != null)
            Console.WriteLine(((int)response.StatusCode).ToString() + ":" + response.Headers);
        else if (exp != null)
            Console.WriteLine($"出错了:{exp.Message}");
    }
}

3、HTTP API接口声明如下:

[FeignClient("", Url = "https://47.107.125.247", Configuration = typeof(FeignConfigDeom))]
public interface FeignTestConfig
{
    // 发起正常请求
    [GetMapping("test/api.aspx")]
    FeignDtoDemo GetDtoObj();
 
    // 发起404请求
    [GetMapping("xxx")]
    FeignDtoDemo GetErr();
}

4、发起Http调用的代码:

static void TestConfig()
{
    FeignTestConfig feign = ProxyLoader.GetProxy<FeignTestConfig>();
    // 可以看到调用前后会输出日志,和请求耗时
    FeignDtoDemo dto = feign.GetDtoObj();
    WriteMsg(JsonConvert.SerializeObject(dto));
 
    try
    {
        feign.GetErr();// 可以看到调用后会输出错误信息
    }
    catch { }
}

- 常见问题或建议:

  1. FeignClient标记的接口,必须声明为 public。

  2. Feign方法参数,不添加特性声明时,默认为[RequestBody],即该参数将作为POST的数据内容,不限参数类型;

  3. Feign方法参数,只允许一个参数声明为[RequestBody],超过1个,将会抛出异常。
    注1:不要忘记第1点,参数无特性声明,默认为[RequestBody];
    注2:如果超过1个参数,那其它参数必须标记为[RequestNone]、[RequestParam]或[RequestHeader]

  4. Feign方法参数,如果有参数为 [RequestBody],且方法声明为GetMapping,则强制转为PostMapping。

  5. Feign方法参数,如果声明为[RequestParam],则会把它以key=value形式,追加到url后面。

  6. 如果不希望抛出异常,要在自定义配置类的 ErrorHandle 方法里,返回null即可。

  7. 如果需要修改FeignClient里的某个方法的url,不使用默认的类级Url,请给方法添加一个System.Uri类型的参数,请参考上面示例:【使用System.Uri类型参数,修改方法发起请求的url】

  8. 如果需要在运行时才能确定方法返回值类型,请给方法添加一个System.Type类型参数,并在调用时传递即可,请参考上面示例:【使用Type类型参数,修改方法返回数据类型】

  9. 目前FeignClient仅支持读取App.Config或Web.Config配置,如果需要读取自定义配置,请在自定义配置类的OnCreate方法里处理,请参考上面示例:【自定义配置:拦截请求,自定义序列化和自定义异常处理等】

  10. 可以把该库结合Autofac等Ioc容器,进行统一管理,如:

var builder = new ContainerBuilder();
builder.Register(c => ProxyLoader.GetProxy<IFeigntest>()).As<IFeigntest>();
var container = builder.Build();
var feign = container.Resolve<IFeigntest>();
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游北亮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值