前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >快捷实现http客户端神器-feign(私人定制扩展篇)

快捷实现http客户端神器-feign(私人定制扩展篇)

作者头像
lyb-geek
发布2019-07-22 17:17:45
2.7K0
发布2019-07-22 17:17:45
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

何为feign

直接套用官网的话

Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.

为什么要使用feign

官网使用文档开篇第一句话

Feign允许我们通过注解的方式实现http客户端的功能,Feign能用最小的性能开销,让我们调用web服务器上基于文本的接口。同时允许我们自定义编码器、解码器和错误处理器等等

feign入门

因为本篇主要是介绍feign的一些功能扩展,具体入门可查看如下文章,本篇就不再论述

https://github.com/OpenFeign/feign

前言

有使用过feign的小伙伴大概都知道,feign post提交的时候可以使用bean传输,不需要每个参数注解@Param,feign会把这个bean的内容写入到http的 body中去,且指定contentType为application/json 。controller接收时,需要在接口对应的bean上注解@RequestBody,从body中读取这个bean的内容。

如下示例

代码语言:javascript
复制
@RequestMapping(value = "/saveOrUpdateUser")
   public Long saveOrUpdateUser(@RequestBody UserDTO userDTO){
        log.info("{}",userDTO);
       return userService.saveOrUpdateUser(userDTO);
   }
代码语言:javascript
复制
@RequestLine("POST /u/saveOrUpdateUser")
    @Headers("Content-Type: application/json")
    Long saveOrUpdateUser(UserDTO userDTO);

但当接收方不是用body接收复杂对象,上面的方案就失效了

出现上面情况,假如我们又不想进行额外扩展,我们可以采用如下方案解决

1、使用@Param注解

代码语言:javascript
复制
@RequestMapping(value = "/saveOrUpdateUser")
   public Long saveOrUpdateUser( UserDTO userDTO){
        log.info("{}",userDTO);
       return userService.saveOrUpdateUser(userDTO);
   }
代码语言:javascript
复制
@RequestLine("POST /u/saveOrUpdateUser?id={id}&name={name}&age={age}")
    @Headers("Content-Type: application/x-www-form-urlencoded")
    Long saveOrUpdateUser(@Param("id")Long id,@Param("name")String name,@Param("age") int age);

2、使用@QueryMap

代码语言:javascript
复制
@RequestLine("POST /u/saveOrUpdateUser")
    @Headers("Content-Type: application/x-www-form-urlencoded")
    Long saveOrUpdateUser(@QueryMap Map<String,Object> param);

虽然使用上面两种方案可以解决,但存在不优雅的地方,比如参数太多,用map语意不直观。那有没有其他方案,答案是有的,feign很贴心的提供了feign-form,这玩儿意可以同时支持json和表单。具体文档可以查看如下链接

https://github.com/OpenFeign/feign-form

不过目前的版本并没提供,比如接口提供方有个字段属性名称叫做order-items,或者bean里面又嵌套bean的解决方案。为了解决这些问题,我就在feign-form的基础进行了再次扩展

正文

1、pom.xml

代码语言:javascript
复制
<properties>
        <java.version>1.8</java.version>
        <feign.version>10.1.0</feign.version>
        <feign.form.version>3.5.0</feign.form.version>
        <findbugs.version>3.0.1</findbugs.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-core</artifactId>
            <version>${feign.version}</version>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-jackson</artifactId>
            <version>${feign.version}</version>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>${feign.version}</version>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form</artifactId>
            <version>${feign.form.version}</version>
        </dependency>

        <dependency>
            <groupId>com.google.code.findbugs</groupId>
            <artifactId>annotations</artifactId>
            <version>${findbugs.version}</version>
        </dependency>

    </dependencies>

注意点:

all feign-form releases before 3.5.0 works with OpenFeign 9.* versions; starting from feign-form's version 3.5.0, the module works with OpenFeign 10.1.0 versions and greater.

2、核心代码类

代码语言:javascript
复制
public class CustomFormEncoder implements Encoder {

    private static final String CONTENT_TYPE_HEADER = "Content-Type";
    private static final Pattern CHARSET_PATTERN = Pattern.compile("(?<=charset=)([\\w\\-]+)");
    private final Encoder delegate;
    private final Map<ContentType, ContentProcessor> processors;

    public CustomFormEncoder() {
        this(new Default());
    }

    public CustomFormEncoder(Encoder delegate) {
        this.delegate = delegate;
        List<ContentProcessor> list = Arrays.asList(new MultipartFormContentProcessor(delegate), new UrlencodedFormContentProcessor());
        this.processors = new HashMap(list.size(), 1.0F);
        Iterator iterator = list.iterator();

        while(iterator.hasNext()) {
            ContentProcessor processor = (ContentProcessor)iterator.next();
            this.processors.put(processor.getSupportedContentType(), processor);
        }

    }

    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        String contentTypeValue = this.getContentTypeValue(template.headers());
        ContentType contentType = ContentType.of(contentTypeValue);
        if (!this.processors.containsKey(contentType)) {
            this.delegate.encode(object, bodyType, template);
        } else {
            Map data;
            if (MAP_STRING_WILDCARD.equals(bodyType)) {
                data = (Map)object;
            } else {
                if (!PojoUtils.isUserPojo(object)) {
                    this.delegate.encode(object, bodyType, template);
                    return;
                }

                data = PojoUtils.toMap(object);
            }

            Charset charset = this.getCharset(contentTypeValue);
            ((ContentProcessor)this.processors.get(contentType)).process(template, charset, data);
        }
    }

    public final ContentProcessor getContentProcessor(ContentType type) {
        return (ContentProcessor)this.processors.get(type);
    }

    private String getContentTypeValue(Map<String, Collection<String>> headers) {
        Iterator iterator = headers.entrySet().iterator();

        while(true) {
            Map.Entry entry;
            do {
                if (!iterator.hasNext()) {
                    return null;
                }

                entry = (Map.Entry)iterator.next();
            } while(!((String)entry.getKey()).equalsIgnoreCase(CONTENT_TYPE_HEADER));

            Iterator var = ((Collection)entry.getValue()).iterator();

            while(var.hasNext()) {
                String contentTypeValue = (String)var.next();
                if (contentTypeValue != null) {
                    return contentTypeValue;
                }
            }
        }
    }

    private Charset getCharset(String contentTypeValue) {
        Matcher matcher = CHARSET_PATTERN.matcher(contentTypeValue);
        return matcher.find() ? Charset.forName(matcher.group(1)) : CharsetUtil.UTF_8;
    }


}

1、使用入门

1、创建示例bean

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserPageDTO {

    @FieldAlias(isPojo = true)
    private UserDTO userDTO;

    private int pageSize;

    @FieldAlias("pageIndex")
    private int pageNo;
}

@FieldAlias 为自定义注解,用来标注接口提供方的特殊字段属性名,其中还有一个属性isPojo来指定字段是不是复杂对象,当使用isPojo对象里面又包含特殊字段属性名,则该特殊字段属性名上方要加上@JsonProperty注解,比如@JsonProperty("user.name")

private String userName

2、示例接口提供方

代码语言:javascript
复制
@RequestMapping(value = "/listPage")   public List<User> listPage( UserDTO userDTO, int pageIndex, int pageSize){
        log.info("userDTO:{},pageIndex:{},pageSize:{}",userDTO,pageIndex,pageSize);
       return userService.listPage(userDTO,pageIndex,pageSize);
   }

3、示例客户端调用

代码语言:javascript
复制
@CustomFeignClient(url="http://localhost:8080")
public interface UserHttpClient {



    @RequestLine("POST /u/listPage")
    @Headers("Content-Type: application/x-www-form-urlencoded")
    List<User> listPage(UserPageDTO userPageDTO);





}

当客户端接口上有CustomFeignClient注解,且springboot启动类上面有配置EnableCustomFeignClients注解时,程序会自动把接口注册到spring容器中,如果不在springboot启动类上加EnableCustomFeignClients,则可以额外编写一个配置类。配置类上加入EnableCustomFeignClients注解并指定扫描的类包,比如@EnableCustomFeignClients(basePackages = "abc.com")

如果不和spring集成,也可以单独使用,如下

代码语言:javascript
复制
UseUserHttpClient userHttpClient = FeignClientUtils.getInstance().getClient(UserHttpClient.class, Constant.REMOTE_URL);

4、测试

代码语言:javascript
复制
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserHttpClientSpringTest {

    @Autowired
    private UserHttpClient userHttpClient;


    @Test
    public void testListPage(){
        UserDTO userDTO = new UserDTO();
//        userDTO.setAge(25);
//        userDTO.setId(2L);
//        userDTO.setName("李四");
        userDTO.setSex("男");

        UserPageDTO userPageDTO = UserPageDTO.builder().userDTO(userDTO).pageNo(1).pageSize(5).build();

        List<User> users = userHttpClient.listPage(userPageDTO);

        users.forEach(user-> System.out.println(user));
    }
}
代码语言:javascript
复制
User(id=1, name=张三, age=20, sex=男, password=zhangsan)
User(id=2, name=李四, age=25, sex=男, password=lisi)
User(id=4, name=王五, age=30, sex=男, password=wangwu)

demo链接

https://github.com/lyb-geek/feign-complex-demo

总结

feign是一个挺好用的http客户端类库,其通过注解加接口的实现方式确实比传统用apache的HttpComponents方便很多,且性能也比较优越。之前接单,为了简化http的客户端代码的编写,我也造了一个类似的接口+注解http客户端,在maven的中央仓库就可以搜到,感兴趣的小伙伴可以蛮看一下,后面有机会会介绍一下

https://mvnrepository.com/artifact/com.github.easilyuse/easily-http

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要使用feign
  • feign入门
  • 前言
  • 正文
  • 1、使用入门
  • demo链接
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档