项目开发中,我们有时会有调用远程接口的需求。一般,我们会用到Appache的HttpClient,这个方式会有很多代码量,并且每个人的写的HttpClientUtil可能都不一样,这样很不利于代码的同一,也很不优雅。

下面介绍一种优雅的方式来实现远程http接口调用。
feign-client

首先我们基于springboot,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.wandou</groupId>
  <artifactId>spring-boot-feign</artifactId>
  <version>1.0.0</version>
  <name>spring-boot-feign</name>
  <description>Demo project for Spring Boot Use Feign</description>

  <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
  </properties>

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

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

  <dependencyManagement>
    <dependencies>
      <!-- 虽然是springboot项目,但是用到了springcloud的组件,统一管理springcloud版本,openfeign就不需要指定版本了-->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

  <repositories>
    <repository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
    </repository>
  </repositories>

</project>

feign接口代码如下:

package com.wandou.springbootfeign.feign;

import com.wandou.springbootfeign.config.FeignConfig;
import com.wandou.springbootfeign.dto.BaiduParamDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author liming
 * @date 2020/11/16
 * @description
 */

@FeignClient(name = "baidu", url = "http://baidu.com/", configuration = FeignConfig.class, primary = false)
public interface BaiduFeign {

    @RequestMapping("/s")
    String search(@RequestParam("wd") String wd);

    @RequestMapping(path = "/s", method = RequestMethod.GET)
    String searchV2(@SpringQueryMap BaiduParamDTO param);

}

启动类需要加 @EnableFeignClients ,然后启动测试即可。

我写的测试用例:

package com.wandou.springbootfeign;

import com.wandou.springbootfeign.feign.BaiduFeign;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootFeignApplicationTests {

    @Autowired
    private BaiduFeign baiduFeign;

    @Test
    void contextLoads() {
    }

    @Test
    public void testFeign() {
        String result = baiduFeign.search("如何写出一手好字");
        System.out.println("result = " + result);
    }

}

测试结果:

result = <!DOCTYPE html>
<!--STATUS OK-->



//中间很多空白




<html>
	<head>
		
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<meta http-equiv="content-type" content="text/html;charset=utf-8">
		<meta content="always" name="referrer">
        <meta name="theme-color" content="#2932e1">
        <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
        <link rel="icon" sizes="any" mask href="//www.baidu.com/img/baidu_85beaf5496f291521eb75ba38eacbd87.svg">
        <link rel="search" type="application/opensearchdescription+xml" href="/content-search.xml" title="百度搜索" />
		
		
<title>如何写出一手好字_百度搜索</title>

		

		
<style data-for="result" type="text/css" id="css_newi_result">body{color:#333;background:#fff;padding:6px 0 0;margin:0;position:relative}
body,th,td,.p1,.p2{font-family:arial}
p,form,ol,ul,li,dl,dt,dd,h3{margin:0;padding:0;list-style:none}
input{padding-top:0;padding-bottom:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
table,img{border:0}
td{font-size:9pt;line-height:18px}
em{font-style:normal}

.......... 省略 ..........

由此可见我们已经请求通百度,并返回了数据。

集成项目可能出现的问题及解决方向

1. springboot和springcloud版本不匹配

解决方案:spring官网,有介绍各版本的支持与匹配
https://spring.io/projects/spring-cloud#learn
https://docs.spring.io/spring-cloud/docs/Hoxton.SR9/reference/html/

2. Maven Jar包间有版本冲突(在开发中常见)

因为我们工作中的项目都会比较复杂,引入很多相关依赖,不会像我这个示例程序这么简单,所以Maven Jar间容易产生版本号冲突,引起一些莫名的问题。
解决方案:看我前面的文章
《IDEA解决maven包冲突》

3. 方法上使用 @RequestMapping 报错

报错如下:

Caused by: java.lang.IllegalStateException: Method BaiduFeign#search(String) not annotated with HTTP method type (ex. GET, POST)
Warnings:
- Class BaiduFeign has annotations [FeignClient] that are not used by contract Default
- Method search has an annotation RequestMapping that is not used by contract Default
	at feign.Util.checkState(Util.java:129)
	at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:100)
	at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:62)
	at feign.DeclarativeContract.parseAndValidateMetadata(DeclarativeContract.java:38)
	at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:151)
	at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:49)
	at feign.Feign$Builder.target(Feign.java:269)
	at org.springframework.cloud.openfeign.HystrixTargeter.target(HystrixTargeter.java:38)
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:369)
	at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:327)
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:169)
	... 93 more

是因为在 config 中用了 Contract.Default()

@Configuration
public class FeignConfig {

    /**
     * 如需使用spring系的注解(RequestMapping、RequestParam等)可以不加如下 Contract 配置,
     * 或者返回 SpringMvcContract。
     */
    @Bean
    public Contract feignContract() {
        //SpringMvcContract springMvcContract = new SpringMvcContract();
        Contract.Default defaultContract = new Contract.Default();
        return defaultContract;
    }
}

Contract 源码摘录:

package feign;

/**
 * Defines what annotations and values are valid on interfaces.
 * 定义在接口上哪些注释和值是有效的。
 */
public interface Contract { 

	// ....... some code ......

}

这个接口的作用是,定义在 feign 接口上哪些注释和值是有效的。那 Contract.Default() 中只处理了feign包自己一些注解(如 feign.RequestLine),所以使用 @RequestMapping 报错。去掉配置,或者换成 SpringMvcContract 即可解决。


源码:https://github.com/wandou9527/spring-boot-feign

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐