在微服务架构中客户端负载均衡和断路器保护,作为基础工具类框架广泛的应用在各个微服务的实现中,不仅包括我们自身的业务服务类,也包括一些基础设施类微服务,那么是否有更高层次的封装来整合这两个基础工具以简化开发。
Feign是Netflix开发的声明式、模板化的HTTP客户端,Netflix Feign整合了Spring Cloud Ribbon与Spring Cloud Hystrix,除了提供这两者强大功能之外,它还提供了一种声明式的web服务端定义方式。
在Spring Cloud中使用Feign非常简单,只需创建一个接口,并在接口上添加一个注解代码就完成了。Feign支持多种注解,如Fegin自带注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使用Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而上Feign的使用更加方便。
注册中心
注册中心之前的博文已经实现,不在过多强调
服务提供者
修改controller
/** * 不接收参数 * @return */ @RequestMapping(value = "/hello1", method = RequestMethod.GET) public String hello() { return "hello spring cloud"; } /** * 接收单个参数 * @param name * @return */ @RequestMapping(value = "/hello2", method = RequestMethod.GET) public String hello1(@RequestParam String name) { return "hello "+name; } /** * 参数用对象封装,请求为Post请求 * @param user * @return */ @RequestMapping(value = "/hello3", method = RequestMethod.POST) public String hello1(@RequestBody User user) { return "hello "+user.getUsername()+" "+user.getPhone(); }
修改entity
package com.niugang.controller.entity; import java.io.Serializable; /** * 用户实体 * * @author niugang * */ public class User implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String username; private String phone; private int randNum; // public User() { super(); } public User(Integer id, String username, String phone) { super(); this.id = id; this.username = username; this.phone = phone; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public int getRandNum() { return randNum; } public void setRandNum(int randNum) { this.randNum = randNum; } }
pom.xml
服务消费者(Feign版)
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>902.spring-cloud-feign-consumer</groupId> <artifactId>feign-consumer</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud Maven Webapp</name> <url>http://maven.apache.org</url> <!--springboot采用1.5.x 对应springcloud版本为 Dalston --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.RELEASE</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</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--引入feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> </dependencies> <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> <!-- 这样变成可执行的jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
启动类
package com.niugang; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; /** * feign版消费者 * * @author niugang * */ @SpringBootApplication @EnableDiscoveryClient //扫描声明它们是feign客户端的接口(通过@FeignClient) @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
配置
#指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问 spring.application.name=feign-consumer server.port=9001 #注册中心地址 eureka.client.serviceUrl.defaultZone=http://testhost:8000/eureka/
entity
实体类配置和上面服务提供者一样
controller
package com.niugang.controller; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.niugang.entity.User; import com.niugang.service.ServiceProvide; @RestController public class ComputeController { private final Logger logger = org.slf4j.LoggerFactory.getLogger(ComputeController.class); @Autowired private ServiceProvide serviceProvide; /** * 无参数调用 * @return */ @RequestMapping(value = "/feign-consumer", method = RequestMethod.GET) public String feign() { logger.info("start invoke sevice provide"); return serviceProvide.hello1(); } @RequestMapping(value = "/feign-consumer1/{name}", method = RequestMethod.GET) public String feign1(@PathVariable String name) { logger.info("start invoke sevice provide"); return serviceProvide.hello2(name); } @RequestMapping(value = "/feign-consumer2/{username}/{phone}", method = RequestMethod.GET) public String feign2(@PathVariable String username, @PathVariable String phone) { logger.info("start invoke sevice provide"); User user = new User(); user.setUsername(username); user.setPhone(phone); return serviceProvide.hello3(user); } }
调用服务接口类
package com.niugang.service; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import com.niugang.entity.User; /** * 定义服务绑定接口 * @author Administrator * */ //声明调用的服务 @FeignClient(value="service-provide",fallback=ServiceProvideFallback.class) public interface ServiceProvide { /** * 调用服务的路径,不传参数 * @return */ @RequestMapping(value="/hello1",method = RequestMethod.GET) String hello1(); /** * * 传递一个参数 */ @RequestMapping(value="/hello2",method = RequestMethod.GET) //@RequestParam里面一定要写参数名,否则读取不到 String hello2(@RequestParam("name") String name); /** * 传递参数对象,这个需要Post请求 * @return */ @RequestMapping(value="/hello3",method = RequestMethod.POST) String hello3(@RequestBody User user); }
调用降级类
Spring Cloud Fegin在定义服务客户端的时候与Spring Cloud Ribbon有很大的差别,HystrixCommand定义被封装起来了。不能在像Spring Cloud Hystrix那样,通过@HystrixCommand注解的fallback参数那样来定义服务的降级。 Spring Cloud Fegin提供了一种更方便的做法具体如下:
服务降级逻辑的实现只需要为Feign客户端的定义接口编写一个具体的接口实现类
package com.niugang.fallback; import org.springframework.stereotype.Component; import com.niugang.entity.User; import com.niugang.service.ServiceProvide; /** * * 服务降级 * @author niugang * */ @Component public class ServiceProvideFallback implements ServiceProvide { @Override public String hello1() { return "服务器内部异常1"; } @Override public String hello2(String name) { return "服务器内部异常2"; } @Override public String hello3(User user) { return "服务器内部异常3"; } }
测试
启动注册中心,启动服务提供者,启动服务调用者
输入:http://localhost:9001/feign-consumer
输入:http://localhost:9001/feign-consumer1/zhangsan
输入:http://localhost:9001/feign-consumer2/zhangsan/15094031789
微信公众号