目录
Netflix Feign特性?Feign能干什么?Feign工作原理?@FeignClient注解、Spring Cloud服务调用使用案例?
Netflix Feign远程调用
Feign是Netflix开发的声明式、模板化的HTTP客户端
Feign可以更快捷、优雅地调用HTTP API。在Spring Cloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求,使得一切Web服务得以简化
Feign是一个声明式的Web Service客户端。它的出现使开发Web Service客户端变得很简单。使用Feign只需要创建一个接口加上对应的注解。
比如:@FeignClient注解。Feign有可插拔的注解,包括Feign注解和AX-RS注解。Feign也支持编码器和解码器
Spring Cloud Open Feign对Netflix Feign进行封装增强。支持Spring MVC标准注解和HttpMessageConverters,可以像Spring Web一样使用 HttpMessageConverters等。Feign整合了Eureka和Ribbon,可以与Eureka和Ribbon组合使用以支持负载均衡
Spring Cloud Feign帮助定义和实现依赖服务接口的定义。在Spring Cloud Feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量
使用:只需要创建一个接口,然后在上面添加注解即可
Feign特性?
(1)可插拔的注解支持,包括Feign注解和AX-RS注解
(2)支持可插拔的HTTP编码器和解码器
(3)支持Hystrix和它的Fallback
(4)支持Ribbon的负载均衡
(5)支持Spring MVC注解
(6)支持HTTP请求和响应的压缩
Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。它整合了Eureka和Ribbon,从而不需要开发者针对 Feign 对其进行整合。Feign还提供了HTTP请求的模板,通过编写简单的接口和注解,就可以定义好HTTP请求的参数、格式、地址等信息。Feign会完全代理HTTP的请求,在使用过程中只需要依赖注入Bean,然后调用对应的方法传递参数即可
Web Service概念
总结:WebService是一种跨编程语言和跨操作系统平台的远程调用技术
跨编程语言和跨操作平台:就是说服务端程序采用Java编写,客户端程序则可以采用其他编程语言编写,反之亦然!跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行
远程调用:就是一台计算机a上 的一个程序可以调用到另外一台计算机b上的一个对象的方法,譬如,银联提供给商场的pos刷卡系统,商场的POS机转账调用的转账方法的代码其实是跑在银行服务器上。再比如,amazon,天气预报系统,淘宝网,校内网,百度等把自己的系统服务以webservice服务的形式暴露出来,让第三方网站和程 序可以调用这些服务功能,这样扩展了自己系统的市场占有率,往大的概念上吹,就是所谓的SOA应用
其实可以从多个角度来理解 WebService,从表面上看,WebService就是一个应用程序向外界暴露出一个能通过Web进行调用的API,也就是说能用编程的方法通过 Web来调用这个应用程序。把调用这个WebService的应用程序叫做客户端,而把提供这个WebService的应用程序叫做服务端。从深层次看,WebService是建立可互操作的分布式应用程序的新平台,是一个平台,是一套标准。它定义了应用程序如何在Web上实现互操作性,可以用任何语言,在任何平台上写Webservice,只要可以通过Webservice标准对这些服务进行查询和访问
Feign工作原理
(1)在开发微服务应用时,会在主程序入口添加@EnableFeignClients注解开启对Feign Client 扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClients注解
(2)当程序启动时,会进行包扫描,扫描所有@FeignClients的注解的类,并将这些信息注入 Spring IOC容器中。当定义的Feign接口中的方法被调用时,通过JDK的代理的方式,来生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,如请求参数名、请求方法等信息都是在这个过程中确定的
(3)然后由RequestTemplate生成Request,然后把Request交给Client去处理,这里指的 Client可以是JDK原生的URLConnection、Apache的Http Client也可以是Okhttp。最后Client 被封装到LoadBalanceclient类,这个类结合Ribbon负载均衡发起服务之间的调用
Feign能干什么?
Feign使编写Java Http客户端变得更容易
Spring Cloud服务调用
Spring Cloud服务间的调用有两种方式:RestTemplate和FeignClient。
不管是什么方式,都是通过REST接口调用服务的http接口,参数和结果默认都是通过Jackson序列化和反序列化。因为Spring MVC的RestController定义的接口,返回的数据都是通过Jackson序列化成JSON数据
(1)RestTemplate(三种)
方式一:直接使用RestTemplate,路径固定不好修改
RestTemplate对于路径的访问已经固定,如果线上部署,不好进行修改;或者是启动了多个实例,不好进行捕获和调用
private static final Logger log = LoggerFactory.getLogger(IndexController.class);
@GetMapping
public String getIndex() {
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://localhost:9001/msg", String.class);
log.info("response{}", response);
return response;
}
方式二:利用LoadBalancerClient通过应用名获取URL,然后在使用RestTemplate
注入LoaderBalancerClient,通过服务名称进行获取服务的IP和端口号
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/getProductMsg")
public String getProductMsg(){
// 方式二:(利用LoadBalancerClient通过应用名获取url,然后在使用RestTemplate)
RestTemplate restTemplate = new RestTemplate();
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String url = String.format("http://%s:%s", serviceInstance.getHost(),serviceInstance.getPort() + "/msg");
String response = restTemplate.getForObject(url, String.class);
log.info("response{}",response);
return response;
}
方式三:只需要定义一个RestTemplate的Bean,设置成LoadBalanced即可(推荐)
1.在RestTemplate上添加@LoadBalanced注解
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
2.Controller或Service中使用
这样就可以在需要用的地方注入这个bean使用
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
public String getUserById(Long userId) {
List<ServiceInstance> instances = discoveryClient.getInstances("EUREKA-CLIENT");
// 获取EUREKA-SERVER的第一个实例
ServiceInstance instance = instances.get(0);
//获得主机名、端口
String url = "http://" + instance.getHost() + ":" + instance.getPort();
String response = restTemplate.getForObject(url, String.class);
log.info("response{}", response);
String results = restTemplate.getForObject("http://users/getUserDetail/" + userId, String.class);
return results;
}
(2)FeignClient
Feign指在使编写Java Http客户端变得更容易。前面在使用Ribbon + RestTemplate时,利用 RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。
所以,Feign在此基础上做了进一步的封装,由它来帮助定义和实现依赖服务接口的定义。使用Feign只需要创建一个接口并使用一个注解来配置它即可
使用Ribbon + RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。
但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来定义和实现依赖服务接口的定义。在Feign的实现下,只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上标注@Mapper注解,现在是一个微服务接口上面标注一个@Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量
Feign集成了Ribbon
利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
1.添加Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--添加feign依赖,启动器可能会下载报错,可使用指定版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
2.创建一个Feign接口,并添@FeignClient注解
UserFeignClient:一个接口,Feign会通过动态代理,生成实现类;@FeignClient,声明这是一个Feign客户端。接口中的定义方法,完全采用Spring MVC的注解,Feign会根据注解生成URL,并访问获取结果改造原来的调用逻辑,调用UserClient接口。所以将Controller中的方法复制过来
@Component
@FeignClient(value = "service-test", path = "/users")
public interface UserFeignClient {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
}
只需要使用@FeignClient定义一个接口,Spring Cloud Feign会自动生成一个它的实现,从相应的users服务获取数据。其中,@FeignClient(value = “service-test”, path = “/users”)里面的value是服务ID,path是这一组接口的path前缀。在下面的方法定义里,就好像设置Spring MVC的接口一样,对于这个方法,它对应的URL是/users/{id}。然后,在使用它的时候,就像注入一个一般的服务一样注入后使用即可
3.修改Controller代码,让其调用Feign接口
@Autowired
private UserFeignClient userFeignClient; // 注入接口
@GetMapping("/user/{id}")
public String findById(@PathVariable Long id) {
return this.userFeignClient.findById(id);
}
4.修改启动类,为其添加@EnableFeignClients注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SpringcloudconsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudconsumerApplication.class, args);
}
}
5.测试
①启动eureka
②启动2个user实例
③启动feign
④多次访问http://localhost:8010/user/1
@FeignClient注解参数
@FeignClient用于创建声明是API接口,该接口是RESTful风格的。Feign被设计成插拔式的,可注入其他组件和Feign一起使用。最典型的是如果Ribbon可用,Feign会和Ribbon相结合进行负载均衡
简单理解就是,分布式架构服务之间,各子模块系统内部通信的核心。一般在一个系统调用另一个系统的接口时使用
FeignClient注解被三个元注解修饰
元注解 | 描述 |
---|---|
@Target(ElementType.TYPE) | 表示FeignClient的作用目标在接口上 |
@Retention(RetentionPolicy.RUNTIME) | 注解表明该注解会在Class字节码中存在,在运行时可以通过反射获取到 |
@Documented | 表明该注解被包含在javadoc中 |
name、value和serviceId
从源码可以得知,name是value的别名,value也是name的别名。两者的作用是一致的,name指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现。
其中,serviceId和value的作用一样,用于指定服务ID,已经废弃。
实例:通过name或者value指定服务名,然后根据服务名类型(GET、DLETE)调用/one服务
@Component
//@FeignClient(value = "service-test")
@FeignClient(name = "service-test")
public interface UserFeignClient {
@RequestMapping(value = "/app/one", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
@RequestMapping(value = "/app/one", method = RequestMethod.DELETE)
public String deleteId(@PathVariable("id") Long id);
}
path
path属性定义当前FeignClient的统一前缀。方便在该FeignClient中的@RequestMapping中书写value值
假如存在一系列的用户管理服务,如下:
/app/service/userManager/get
/app/service/userManager/insert
/app/service/userManager/update
/app/service/userManager/delete
每次都在@RequestMapping注解中编写全服务名称,就有点多余。因此可以设置FeignClient的path路径为“/app/service/userManager”,简化@RequestMapping的编写
@Component
@FeignClient(value = "service-test",path = "/app/service/userManager")
public interface UserFeignClient {
//正确服务地址:/app/service/one
@RequestMapping(value = "/one", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
}
当调用findById()方法时将请求http://service-test/app/service/userManager/one服务
qualifier
用来指定@Qualifier注解的值,该值是该FeignClient的限定词,可以使用改值进行引用
@Component
//@FeignClient(value = "service-test")
@FeignClient(qualifier = "myService",value = "service-test")
public interface UserFeignClient {
@RequestMapping(value = "/app/one", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
@RequestMapping(value = "/app/one", method = RequestMethod.DELETE)
public String deleteId(@PathVariable("id") Long id);
}
在使用“myService”标识注入服务
@RestController
public class IndexController {
@Autowired
@Qualifier("myService")
private UserFeignClient userFeignClient;
}
url
url属性一般用于调试程序,允许手动指定@FeignClient调用的地址
指定服务调用的地址为http://localhost:8762
@Component
//@FeignClient(value = "service-test")
@FeignClient(value = "service-test",path = "/app/service",url = "http://localhost:8080")
public interface UserFeignClient {
@RequestMapping(value = "/one", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
@RequestMapping(value = "/one", method = RequestMethod.DELETE)
public String deleteId(@PathVariable("id") Long id);
}
decode404
当发生http 404错误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException
实例:访问在服务“service-hi”上面没有的服务/one(服务实际地址为:/app/service/one)
@Component
//@FeignClient(value = "service-test")
@FeignClient(value = "service-test")
public interface UserFeignClient {
//正确服务地址:/app/service/one
@RequestMapping(value = "/one", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
}
调用上面的sayHiFormClientOne()方法时,Spring Boot将给出如下错误信息:
为了在调用服务抛出404错误时,返回一些有用的信息。可以将decode404参数设置为true
@Component
//@FeignClient(value = "service-test")
@FeignClient(value = "service-test",decode404 = true)
public interface UserFeignClient {
//正确服务地址:/app/service/one
@RequestMapping(value = "/one", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
}
此时调用sayHiFromClientOne()方法时,返回如下错误信息:
设置decode404=true,需要通过设置configuration去配置decode
configuration
Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract。
实例:自定义configuration配置类,简单自定义一个Decoder,该Decoder配合decod404=true使用;当服务调用抛出404错误时,将自动调用自定义的Decoder,输出一个简单的字符串
(1)定义一个Controller,提供给一个REST服务/one
@RestController
public class IndexController {
@Autowired
private UserFeignClient userFeignClient;
@RequestMapping("/one/data")
public String getIndex(@RequestParam Long id){
return userFeignClient.findById(id);
}
}
(2)编写FeignClient类UserFeginClient
@Component
//@FeignClient(value = "service-test")
@FeignClient(value = "service-test",decode404 = true)
public interface UserFeignClient {
//正确服务地址:/app/service/one
@RequestMapping(value = "/one", method = RequestMethod.GET)
public String findById(@PathVariable("id") Long id);
}
(3)编写MyConfiguration配置类
@Configuration
public class MyDecoderConfiguration {
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new MyDecoder();
}
}
(4)编写自定义的Decoder类MyDecoder
public class MyDecoder implements Decoder {
@Override
public Object decode(Response response, Type type)
throws IOException, DecodeException, FeignException {
return "响应内容response:"+response +",type ="+type;
}
}
当调用服务/one/data发生404时,输出如下信息
fallback和fallbackFactory
feign的注解@FeignClient:fallbackFactory与fallback方法不能同时使用,这个两个方法其实都类似于Hystrix的功能,当网络不通时返回默认的配置数据
参数 | 描述 |
---|---|
fallback | 定义容错的处理类。当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口 |
fallbackFactory | 工厂类,用于生成fallback类示例,通过这个属性可以实现每个接口通用的容错逻辑,减少重复的代码 |
熔断只是作用在服务调用这一端,在Feign中已经依赖了Hystrix所以在maven配置上不用做任何改动
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SpringcloudconsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudconsumerApplication.class, args);
}
}
application.properties添加这一条:
feign.hystrix.enabled=true
使用fallback
(1)访问服务接口
写一个访问“server-test”服务的接口,同时在@FeignClient注解中使用fallback默认返回方法(断容器,fallback = ClientFallback.class)
@FeignClient(name="server-test", fallback = ClientFallback.class)
public interface UserFeignClient {
// 两个坑:1. @GetMapping不支持 2. @PathVariable得设置value
@RequestMapping(value="/simple/{id}", method=RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
}
(2)定义一个类,实现@FeignClient注解的接口
定义HystrixClientFallback类,并实现UserFeignClient类,当网络不通或者访问失败时,返回固定/默认内容
@Component
public class ClientFallback implements UserFeignClient{
@Override
public User findById(Long id) {
User user = new User();
user.setId(0L);
return user;
}
}
(3)Controller中测试服务接口
调用“server-test”服务的接口
@RestController
public class IndexController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/movie/{id}")
public User findById(@PathVariable("id") Long id) {
return this.userFeignClient.findById(id);
}
}
使用fallbackFactory
(1)服务接口
写feignClient客户端,使用feignClient注解的fallbackFactory方法
@FeignClient(name="server-test", fallbackFactory = ClientFallbackFactory.class)
public interface UserFeignClient {
// 两个坑:1. @GetMapping不支持 2. @PathVariable得设置value
@RequestMapping(value="/simple/{id}", method=RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
}
(2)定义一个类继承服务接口
定义一个类继承UserFeignClient接口(可以省略,使用匿名内部类new UserFeignClient())
public interface HystrixClientWithFallbackFactory
extends UserFeignClient {}
(3)定义一个类实现FallbackFactory<服务接口>类,并实现create()方法
HystrixClientFallbackFactory实现FallbackFactory类,并使用内部匿名方法类,继续UserFeignClient
@Component
public class HystrixClientFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable throwable) {
//这步可以换成直接new接口:new UserFeignClient()
return new HystrixClientWithFallbackFactory () {
@Override
public User findById(Long id) {
return null;
}
};
}
}
(4)Controller、Service中调用UserFeignClient接口
@RestController
public class MovieController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/movie/{id}")
public User findById(@PathVariable("id") Long id) {
return this.userFeignClient.findById(id);
}
}
调用结果
当开启“server-test”服务,返回数据
当关闭“server-test”服务,返回数据
primary
是否将伪代理标记为主Bean,默认为true