首页 > 其他分享 >二、原生 API 的注解使用示例

二、原生 API 的注解使用示例

时间:2023-08-20 19:15:00浏览次数:47  
标签:Body feign String 示例 API RequestLineClient 注解 public name

RequestLine

@java.lang.annotation.Target(METHOD)
@Retention(RUNTIME)
public @interface RequestLine {
  // 请求方式 + uri
  String value();
  // 斜线是否 base64 编码
  boolean decodeSlash() default true;
  // 默认支持URL传多值,是通过key来传输的。形如:key=value1&key=value2&key=value3
  // CollectionFormat不同的取值对应不同的分隔符,一般不建议改
  CollectionFormat collectionFormat() default CollectionFormat.EXPLODED;
}

它只能标注在Method方法上。为请求定义HttpMethodUriTemplate(标注在方法上的就是一个HttpMethod,且写好了URI(可是绝对路径,也可是相对的,一般写后部分即可))。表达式、用大括号括起来的值{expression}最终会用对应的@Param注解填进去(根据key匹配)。

使用示例
Feign内置的Logger实现:

  • feign.Logger.JavaLogger:使用的java.util.logging.Logger输出,但是日志级别的FINE级别,默认不会输出到控制台
  • feign.Logger.ErrorLogger:错误输出。使用的System.err.printf()输出
  • feign.Logger.NoOpLogger:什么都不输出,它是Feign的默认使用的Logger实现,也就是不会给控制台输出
abstract class FeignClientFactory {
    static <T> T create(Class<T> clazz, int port) {
        return Feign.builder()
                // 日志
                .logger(new Logger.ErrorLogger())
                .logLevel(Logger.Level.FULL)
                // 不重试
                .retryer(Retryer.NEVER_RETRY)
                // 把404也解码 -> 这样就不会以异常形式抛出
                .decode404()
                .target(clazz, "http://localhost:" + port);
    }
}

Feign 接口

interface RequestLineClient {
    // 1、正常使用、正常书写
    @Headers({"Accept:*/*", "Accept-Language:    zh-cn"})
    @RequestLine("GET /feign/demo1?name={name}")
    String testRequestLine(@Param("name") String name);

    // 2、GET后不止一个空格,有多个空格
    @RequestLine("GET             /feign/demo1?name={name}")
    String testRequestLine2(@Param("name") String name);

    // 3、使用Map一次性传递多个查询参数,使用注解为@QueryMap
    @RequestLine("GET /feign/demo1")
    String testRequestLine3(@QueryMap Map<String, Object> params);

    // 4、方法参数上不使用任何注解
    @RequestLine("GET /feign/demo1")
    String testRequestLine4(String name);

    // 5、方法上标注有@Body注解,然后把方法参数传递给它
    @RequestLine("GET /feign/demo1")
    @Body("{name}")
    String testRequestLine5(@Param("name") String name);

     // 6、方法两个参数,均不使用注解标注
     // 启动直接报错:Method has too many Body parameters:
     @RequestLine("GET /feign/demo1")
     String testRequestLine6(String name,Integer age);

     // 7、启动直接报错:Body parameters cannot be used with form parameters.
     @RequestLine("GET /feign/demo1")
     @Body("{name}")
     String testRequestLine7(@Param("name") String name, Integer age);

    // 8、如果你既想要body参数,又想要查询参数,请这么写
    @RequestLine("GET /feign/demo1?name={name}")
    @Body("{age}")
    String testRequestLine8(@Param("name") String name, @Param("age") Integer age);
}

这是在 Feign 的源码的测试的包内写的,模仿 feign 的单元测试的写法

import feign.*;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.Rule;
import org.junit.Test;

import java.util.Map;

public class Demo {

    @Rule
    public final MockWebServer server = new MockWebServer();

    // 1、正常使用、正常书写
    @Test
    public void testRequestLine() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        client.testRequestLine("YourBatman");
    }

日志

Aug 20, 2023 5:06:31 PM okhttp3.mockwebserver.MockWebServer$3 execute
INFO: MockWebServer[56615] starting to accept connections
[RequestLineClient#testRequestLine] ---> GET http://localhost:56615/feign/demo1?name=YourBatman HTTP/1.1
[RequestLineClient#testRequestLine] Accept: */*
[RequestLineClient#testRequestLine] Accept-Language: zh-cn
[RequestLineClient#testRequestLine] ---> END HTTP (0-byte body)
Aug 20, 2023 5:06:31 PM okhttp3.mockwebserver.MockWebServer$4 processOneRequest
INFO: MockWebServer[56615] received request: GET /feign/demo1?name=YourBatman HTTP/1.1 and responded: HTTP/1.1 200 OK
[RequestLineClient#testRequestLine] <--- HTTP/1.1 200 OK (26ms)
[RequestLineClient#testRequestLine] content-length: 13
[RequestLineClient#testRequestLine] 
[RequestLineClient#testRequestLine] response data
[RequestLineClient#testRequestLine] <--- END HTTP (13-byte body)
Aug 20, 2023 5:06:31 PM okhttp3.mockwebserver.MockWebServer$3 acceptConnections
INFO: MockWebServer[56615] done accepting connections: socket closed

其他测试方法

public class Demo {

    @Rule
    public final MockWebServer server = new MockWebServer();

    // 1、正常使用、正常书写
    @Test
    public void testRequestLine() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        client.testRequestLine("YourBatman");
    }

    // 2、GET后不止一个空格,有多个空格
    @Test
    public void testRequestLine2() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        client.testRequestLine2("YourBatman");
    }

    // 3、使用Map一次性传递多个查询参数,使用注解为@QueryMap
    @Test
    public void testRequestLine3() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        // 使用Map一次传多个请求参数
        Map<String, Object> map = new HashMap<>();
        map.put("name", "YourBatman3");
        map.put("age", Arrays.asList(16, 18, 20));
        client.testRequestLine3(map);
    }

    // 4、方法参数上不使用任何注解
    @Test
    public void testRequestLine4() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        client.testRequestLine4("YourBatman");
    }


    // 5、一个方法参数使用注解但是不使用
    // class java.util.LinkedHashMap is not a type supported by this encoder.
    @Test
    public void testRequestLine5() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        // client.testRequestLine5("YourBatman");
    }

    // 6、方法上标注有@Body注解,然后把方法参数传递给它
    @Test
    public void testRequestLine6() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        client.testRequestLine6("YourBatman");
    }

    // 7、方法两个参数,均不使用注解标注
    // 启动直接报错(FeignClientFactory.create时报错):Method has too many Body parameters:
    // @RequestLine("GET /feign/demo1")
    @Test
     public void testRequestLine7(){
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        // client.testRequestLine7("YourBatman", 18);
    }

    // 8、启动直接报错:Body parameters cannot be used with form parameters.
    // @RequestLine("GET /feign/demo1")
    // @Body("{name}")
     public void testRequestLine8(){
         server.enqueue(new MockResponse().setBody("response data"));
         RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
         // client.testRequestLine8("YourBatman", 18);
     }

    // 9、如果你既想要body参数,又想要查询参数,请这么写
    @Test
    public void testRequestLine9() {
        server.enqueue(new MockResponse().setBody("response data"));
        RequestLineClient client = FeignClientFactory.create(RequestLineClient.class, server.getPort());
        client.testRequestLine9("YourBatman", 18);
    }

}

从日志中也能看出一点小细节

  • @RequestLine注解的首个单词必须是HTTP方法,且必须顶格写(前面不允许有空格),但后面是需要有空格的且可以是多个空格
  • @Headers它的key连接符用的是:而不是=,请务必注意。另外:对空格不敏感
  • 但凡只要body体不为空,最终就会以POST请求的形式发出,这是由默认实现:JDK底层实现HttpURLConnection决定的,如果你换成OkHttp将不会是这样
  • 方法参数若没标注@Param注解,最终会被放进请求Body体里
  • 方法参数中URI、feign.Request.Options类型除外,他俩不用注解
  • 若你@Body既想用模版,@RequestLine里也想用模版,那么请务必保证每个方法参数都有@Param注解

@Param

只能标注在方法参数Parameter上。 通过名称定义模板变量,其值将用于填入上面的模版:@Headers/@RequestLine/@Body均可使用模版表达式

@Retention(RUNTIME)
@java.lang.annotation.Target({PARAMETER, FIELD, METHOD})
public @interface Param {

  /**
   * 名称(key),和模版会进行匹配然后填充 必填项
   */
  String value();

  /**
   * 如何把值填充上去,默认是调用其toString方法直接填上去
   */
  Class<? extends Expander> expander() default ToStringExpander.class;

  /**
   * 是否转义,默认不转义,直接放上去
   */
  boolean encoded() default false;

  /**
   * 转 String, 类似于 toString 的东西
   */
  interface Expander {

    /**
     * Expands the value into a string. Does not accept or return null.
     */
    String expand(Object value);
  }

  final class ToStringExpander implements Expander {

    @Override
    public String expand(Object value) {
      return value.toString();
    }
  }
}

如果是Collection类型是能够很好的被解析成多值的,但是数组不行,因此多用集合少用数组哦(数组直接调用toString()方法了)

@Headers

@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Headers {

  String[] value();
}

能标注在类上和方法上。用于传请求头,使用起来比较简单,形如这样即可:

@Headers({"Accept:*/*", "Accept-Language:zh-cn"})

唯一注意的一点:k-v使用的是冒号连接,而不是等号。

@QueryMap

@SuppressWarnings("deprecation")
@Retention(RUNTIME)
@java.lang.annotation.Target(PARAMETER)
public @interface QueryMap {
  boolean encoded() default false;
}

只能标注在方法参数上。用于传递多个查询值,拼接在URL后面。
仅需注意一点:只能标注在Map类型的参数前面,否则报错。

@HeaderMap

@Retention(RUNTIME)
@java.lang.annotation.Target(PARAMETER)
public @interface HeaderMap {
}

类似 QueryMap,不过 QueryMap 是解析 Map 为表单参数,HeaderMap 解析 Map 为请求头

@Body

@Target(METHOD)
@Retention(RUNTIME)
public @interface Body {

  String value();
}

比如:@Body("{body}"),这样就可以通过方法参数的@Param("body") String body传值。注意:这个值最终是以http body体的形式发送的(并非URL参数),body体的内容并不要求必须是json,一般请配合请求头使用。

@Getter
@Setter
@ToString
public class Person {
    private String name = "YourBatman";
    private Integer age = 18;
}
public interface BodyClient {

    // 1、@Body里可以是写死的字符串
    @Body("{\"name\" : \"YourBatman\"}")
    @RequestLine("POST /feign/demo3")
    String testBody();

    // 2、@Body可以使用模版{} 取值
    @Body("{body}")
    @RequestLine("POST /feign/demo3")
    String testBody2(@Param("body") String name);

    // 3、@Body里取值来自于一个JavaBean
    @Body("{person}")
    @RequestLine("POST /feign/demo3")
    String testBody3(@Param("person") Person person);
}

可以看到body里是可以是任意格式的数据的,包括POJO(只不过默认是调用它的toString方法而已)。

说明:Feign默认情况下只能支持文本消息,但后来feign提供了feign-form这个扩展模块,所以也就能够支持二进制、文件上传喽。
需要说明的是:feign-form并不属于官方直接子模块,是后续新增的所以它的大版本号不跟主版本号走,GAV也有所不同

// feign-form和feign-form-spring共用一个父工程,版本号保持一致
// feign-form-spring依赖于feign-form工程

<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <!-- <artifactId>feign-form-spring</artifactId> -->
    <version>3.8.0</version>
</dependency>

Spring Cloud的Feign除了导入了core包,也导入了feign-form-spring(包含feign-form),所以默认也是支持到了二进制数据传递的

关于POJO那个Person对象为何最终调用的是toString()而非序列化成了一个JSON,这和RequestTemplate的构建有关。以及为何在Spring Cloud下是能成为JSON的,这些原因后文会分解。。。

总结

关于原生Feign的原生注解就讲解到这了,还是蛮有意思的。总体来说这些原生注解使用起来并不难,它的语法规范遵循的是RFC6570规范,这是区别于Spring MVC的(它是Ant规范)。

标签:Body,feign,String,示例,API,RequestLineClient,注解,public,name
From: https://www.cnblogs.com/chenxingyang/p/17641899.html

相关文章

  • Airtest1.2.7新增的14个断言API解析
    以下基于python3.8;airtestIDE1.2.14;airtest1.2.7;pocoui1.0.87Airtest1.2.7新增了14个断言API,使得断言更多丰富,之前就有的4个断言:assert_exists、assert_not_exists、assert_equal、assert_not_equal,详细可以看:AirtestAPI精讲之断言,这里就不再过多介绍。之前想断言一个变量是......
  • FastApi-1-结合sql 增/查demo
    目录FastAPI学习记录项目结构部分接口/代码展示感受全部代码FastAPI学习记录fastapi已经学习有一段时间,今天抽时间简单整理下。官网介绍:FastAPI是一个用于构建API的现代、快速(高性能)的web框架,使用Python3.6+并基于标准的Python类型提示。快速:可与NodeJS和Go......
  • Python selenium 的日常使用示例
    importos.pathimporttimefromseleniumimportwebdriverfromselenium.webdriver.common.keysimportKeysfrombs4importBeautifulSoup#创建一个Firefox浏览器实例,需要提前下载好文件,设置好环境变量#Chrome:https://sites.google.com/a/chromium.org/chromed......
  • python采集京东商品详情页面数据,京东API接口,京东h5st签名(2023.08.20)
    一、原理与分析1、目标页面https://item.jd.com/6515029.html  在chrome中打开,按f12键进入开发者模式,找到商品详情数据接口,如下:2、URL链接:https://api.m.jd.com/?appid=pc-item-soa&functionId=pc_detailpage_wareBusiness&client=pc&clientVersion=1.0.0&t=1692499380806&bod......
  • 第二十三节 API(算法,lambda,练习)
    常见的七种查找算法:​ 数据结构是数据存储的方式,算法是数据计算的方式。所以在开发中,算法和数据结构息息相关。今天的讲义中会涉及部分数据结构的专业名词,如果各位铁粉有疑惑,可以先看一下哥们后面录制的数据结构,再回头看算法。1.基本查找​ 也叫做顺序查找​说明:顺序......
  • PHP简单ChatGPT API对接方法
    <?php$chat=$_GET['chat'];//设置参数$data=array('model'=>'gpt-3.5-turbo','messages'=>array(array('role'=>'system','content'=>'Your_GP......
  • api分享103.216.155.x
    在日常生活中,我们有很多类似api场景,比如电脑需要调用手机里面的信息,这个时候会拿一个数据线将电脑和手机连接起来,电脑和手机连接数据线的接口就是我们所说的api接口。常见web接口是http/https协议的接口,API是处理系统之间数据传输的媒介。在API调用过程中,客户端会通过API发送请求,A......
  • mondb核心api
    和mysql一样需要先定义一个类加上注解@Document("ap_associate_words"),ap_associate_words表示哪个表的名称保存或者修改,该对象有id表示修改,无id表示新增mongoTemplate.save(保存的对象)查询一个对象mongotemplate.findById("id",类.class)多条件查询Queryquery=Query.......
  • 8-20|https://gitlab.xx.com/api/v4/projects/4/trigger/pipeline Request failed 状
    当你使用GitLabAPI并收到状态码400,这通常意味着你发送的请求是“坏的”或格式不正确。以下是一些建议,帮助你解决问题:1.**验证请求正文**:确保你提供的请求正文(如果有的话)是正确的并符合API的预期格式。对于触发管道的API,你可能需要提供有关分支、变量等的信息。2.**检查URL*......
  • yaml文件示例
     apiVersion:v1kind:Podmetadata:name:my-podspec:containers:-name:myhttpd#只要是包含子项的第一行都需要有”-“,且与上一行对齐image:httpdimagePullPolicy:IfNotPresentports:-containerPort:80-name:mynginx......