分布式远程调用SpringCloud-Feign的两种具体操作方式(精华)
一 前言
几大rpc框架介绍
spring cloud远程调用方式---feign
feign是一个声明似的web服务客户端,它使得编写web服务客户端变得更加容易。使用fegin创建一个接口并对它进行注解。它具有可插拔的注解支持包括feign注解与jax-rs注解,feign还支持可插拔的编码器与解码器,spring cloud 增加了对 spring mvc的注解,spring web 默认使用了httpmessageconverters, spring cloud 集成 ribbon 和 eureka 提供的负载均衡的http客户端 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的两种调用方式
1 直接在调用者声明feign客户端
如下图所示, service-b 声明一个接口, 去调用路径为 /user/get 的服务 service-a 的服务
2 在被调用者接口api中声明feign客户端
如下图, 在被调用者中声明接口, 去调用自己, 这种方法遵循面向接口编程, 而且使用起来, 就类似dubbo一样, @autowire直接注入就可以使用了.
以上可能看得读者一头雾水, 以下具体的代码流程, 可以方便更加具体的了解
二 案例1直接在调用者声明feign客户端代码实现
以下步骤为手把手教学, 请明白我的良苦用心
步骤0 创建一个springcloud-eureka注册中心
首先创建一个父项目
把其他都删除, 剩下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> <groupid>com.fegin</groupid> <artifactid>test</artifactid> <version>0.0.1-snapshot</version> <packaging>pom</packaging> <!--springboot version 2.1.4--> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>2.1.4.release</version> <relativepath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>greenwich.sr1</spring-cloud.version> </properties> <!--springcloud version greenwish.sr1--> <dependencymanagement> <dependencies> <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> </project>
创建eureka项目
项目结构如图
添加eureka的依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactid>test</artifactid> <groupid>com.fegin</groupid> <version>0.0.1-snapshot</version> </parent> <modelversion>4.0.0</modelversion> <artifactid>eureka</artifactid> <!--eureka服务端配置--> <dependencies> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-netflix-eureka-server</artifactid> </dependency> </dependencies> </project>
添加application.yml
server: port: 8761 eureka: instance: hostname: localhost client: # 是否把自己作为服务注册到其他服务注册中心 registerwitheureka: false # 是否从其他的服务中心同步服务列表 fetchregistry: false serviceurl: defaultzone: http://${eureka.instance.hostname}:${server.port}/eureka/ server: # 关闭保护机制,默认true enable-self-preservation: false # 剔除失效服务间隔,默认60000 eviction-interval-timer-in-ms: 3000
添加启动类代码eurekaapplication
/** * @author c-can-z */ @enableeurekaserver @springbootapplication public class eurekaapplication { public static void main(string[] args) { springapplication.run(eurekaapplication.class,args); } }
浏览器输入 http://localhost:8761/
步骤1 准备一个服务servicea --- 该服务为被调用者
步骤2 准备一个服务serviceb --- 该服务为调用者
创建一个serviceb
添加一下serviceb必须的依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactid>test</artifactid> <groupid>com.fegin</groupid> <version>0.0.1-snapshot</version> </parent> <modelversion>4.0.0</modelversion> <artifactid>serviceb</artifactid> <dependencies> <!--springboot web--> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <!--springboot 测试--> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <!--lombok--> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> </dependency> <!--eureka客户端配置--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-netflix-eureka-client</artifactid> </dependency> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-netflix-ribbon</artifactid> </dependency> <!--微服务调用--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-openfeign</artifactid> </dependency> <!--zipkin客户端配置, 已经包含sleuth--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-zipkin</artifactid> </dependency> </dependencies> </project>
把 application.properties 修改为 application.yml, 本服务名称为 service-b 端口为 9992
server: port: 9992 spring: application: name: service-b zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 eureka: client: serviceurl: defaultzone: http://localhost:8761/eureka/ registry-fetch-interval-seconds: 5 #eureka client刷新本地缓存时间,默认30 instance: prefer-ip-address: true #eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则),默认30 lease-renewal-interval-in-seconds: 5 #eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己),默认90 lease-expiration-duration-in-seconds: 7 feign: client: config: default: connecttimeout: 7000 readtimeout: 7000 service-b: ribbon: nfloadbalancerruleclassname: com.netflix.loadbalancer.randomrule logging: level: root: info
导入启动类文件
@springbootapplication @enablefeignclients public class serivebapplication { public static void main(string[] args) { springapplication.run(serivebapplication.class,args); } }
创建feign客户端
//在创建该步骤的时候, 需要关注一下步骤1的说明 //@feignclient(name = "service-a")注解来绑定该接口对应servic-a服务 @feignclient(name = "service-a") public interface userfeginclient { //service-a服务对应资源路径.必须加上@requestparam, 否则会报错,返回参数也必须对应上 @requestmapping("user/get") string get(@requestparam("id")long id); }
在controller中直接进行调用
@restcontroller @requestmapping("/product") public class productcontroller { @autowired private userfeginclient userfeginclient; @requestmapping("/get") public string get(long id){ return "产品服务抽奖: "+userfeginclient.get(id); } }
步骤3 测试feign调用效果
三 案例2 在被调用者接口api中声明feign客户端代码实现
步骤1 创建servicecapi, 该api用来创建feign客户端
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactid>test</artifactid> <groupid>com.fegin</groupid> <version>0.0.1-snapshot</version> </parent> <modelversion>4.0.0</modelversion> <artifactid>servicec-api</artifactid> <dependencies> <!--lombok--> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> </dependency> <!--微服务调用--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-openfeign</artifactid> </dependency> </dependencies> </project>
实体类product
public class product implements serializable { private long id; private string name; public long getid() { return id; } public void setid(long id) { this.id = id; } public string getname() { return name; } public void setname(string name) { this.name = name; }
}
feign客户端
/** * 服务名称 * @author c-can-z */ @feignclient(name="service-c") public interface productfeignapi { //动态代理需要的地址, 但是我们实际操作不到 @requestmapping("/servicec/get") product get(@requestparam("id") long id); }
步骤2 创建servicec服务
项目结构
service项目依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactid>test</artifactid> <groupid>com.fegin</groupid> <version>0.0.1-snapshot</version> </parent> <modelversion>4.0.0</modelversion> <artifactid>servicec</artifactid> <dependencies> <!--springboot web--> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <!--eureka客户端配置--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-netflix-eureka-client</artifactid> </dependency> <!--zipkin客户端配置, 已经包含sleuth--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-zipkin</artifactid> </dependency> <!--springboot 测试--> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <!--导入api--> <dependency> <groupid>com.fegin</groupid> <artifactid>servicec-api</artifactid> <version>0.0.1-snapshot</version> </dependency> </dependencies> </project>
servicec的 application.yml
server: port: 9993 spring: application: name: service-c zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 eureka: client: serviceurl: defaultzone: http://localhost:8761/eureka/ registry-fetch-interval-seconds: 5 #eureka client刷新本地缓存时间,默认30 instance: prefer-ip-address: true #eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则),默认30 lease-renewal-interval-in-seconds: 5 #eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己),默认90 lease-expiration-duration-in-seconds: 7 feign: client: config: default: connecttimeout: 7000 readtimeout: 7000 logging: level: root: info
实现feign客户端接口, 也是该文章的核心代码
采用实现的方式
/** * 远程调用接口的实现类 * @author c-can-z */ @restcontroller public class productfeignclient implements productfeignapi { @override public product get(long id) { product product = new product(); product.setid(id); product.setname("我是服务c"); return product; } }
servicec的启动类
@springbootapplication public class productserverapplication { public static void main(string[] args) { springapplication.run(productserverapplication.class, args); } }
步骤3 创建serviced服务去调用servicec
项目结构
注意启动类的位置, servicecapi的路径必须被启动类扫描到
serviced的依赖,
注意, 必须引入servicec的api, 有人会说有代码侵入的问题, 但是对比案例1, 如果多个项目调用, 要创建多个feign客户端, 孰是孰非, 还得看项目的具体需求
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactid>test</artifactid> <groupid>com.fegin</groupid> <version>0.0.1-snapshot</version> </parent> <modelversion>4.0.0</modelversion> <artifactid>serviced</artifactid> <dependencies> <!--springboot web--> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <!--springboot 测试--> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <!--lombok--> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> </dependency> <!--eureka客户端配置--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-netflix-eureka-client</artifactid> </dependency> <!--微服务调用--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-openfeign</artifactid> </dependency> <!--zipkin客户端配置, 已经包含sleuth--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-zipkin</artifactid> </dependency> <dependency> <groupid>com.fegin</groupid> <artifactid>servicec-api</artifactid> <version>0.0.1-snapshot</version> </dependency> </dependencies> </project>
serviced的 application.yml
server: port: 9994 spring: application: name: service-d zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 eureka: client: serviceurl: defaultzone: http://localhost:8761/eureka/ registry-fetch-interval-seconds: 5 #eureka client刷新本地缓存时间,默认30 instance: prefer-ip-address: true #eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则),默认30 lease-renewal-interval-in-seconds: 5 #eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己),默认90 lease-expiration-duration-in-seconds: 7 feign: client: config: default: connecttimeout: 7000 readtimeout: 7000 logging: level: root: info
serviced的控制类
/** * @author c-can-z */ @restcontroller @requestmapping("/order") public class ordercontroller { @autowired private productfeignapi productfeignapi; @requestmapping("/get") public string get(long id){ product product = productfeignapi.get(id); return "订单为: 货品:" + product.getname() + ", 货品id:"+product.getid(); } }
serviced的启动类
/** * @author c-can-z */ @springbootapplication @enablefeignclients public class servicedapplication { public static void main(string[] args) { springapplication.run(servicedapplication.class,args); } }
步骤4 测试feign调用效果
http://localhost:9994/order/get?id=5
四 总结
到了这里, 不知道你是否理解 直接在调用者声明feign客户端 或者 在被调用者接口api中声明feign客户端
直接在调用者声明feign客户端:
每一个服务调用其他服务, 就需要创建一个客户端, 从代码方面来说, 相对比较麻烦
在被调用者接口api中声明feign客户端:
从调用者来看, 使用起来就跟使用淘宝的dubbo一样方便, 但是每一个服务调用其他服务, 就需要引入其他服务的api依赖, 从项目之间的互相依赖来看, 相对来说, 也会比较麻烦.