import org.
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = “cloud-user”)
public interface UserClient {
@GetMapping(value = “userInfo”)
User getUserInfo(@RequestParam(“userId”) final Integer userId);
}
@FeignClient:表示该类是一个远程调用类,交给jdk动态代理处理。
value:需要调用的服务名。
TestController:
package com.ymy.controller;
import com.ymy.entity.vo.Order;
import com.ymy.entity.vo.User;
import com.ymy.feign.UserClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
@Slf4j
@RefreshScope
public class TestController {
@Value("${test.name}")
private String name;
private UserClient userClient;
TestController(UserClient userClient){
this.userClient = userClient;
}
@GetMapping(value = “/test”)
public String test(){
return “hello:”+name+" 人生总是起起落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落落!";
}
@GetMapping(value = “orderInfo”)
public Order getOrderInfo(){
Order order = new Order();
order.setId(1);
order.setUserId(2);
order.setOorderPrice(BigDecimal.valueOf(100l));
order.setNum(2);
User userInfo = userClient.getUserInfo(order.getUserId());
if(null == userInfo){
log.info(“没有查询到用户信息”);
}
order.setUser(userInfo);
return order;
}
}
6.在启动类中开启feign远程调用
package com.ymy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CloudOrderApplication {
public static void main(String[] args) {
SpringApplication.run(CloudOrderApplication.class, args);
}
}
@SpringBootApplication:springboot启动注解
@EnableDiscoveryClient:将服务注册到注册中心
@EnableFeignClients:开启feign,如果不加此注解,服务调用将不会成功。
7.启动cloud-eureka、cloud-config、cloud-user、cloud-order,测试
其中user则是cloud-order通过调用cloud-user服务拿到的,这是理想状态,如果出现意外情况呢?比如cloud-user服务宕机了会发生什么呢?,现在我停掉cloud-user服务,再次请求:
这是你会发现,服务器出现了报错,这种错误是不能出现在用户眼中的,有没有方法解决呢?
fallback 熔断机制
feign自带熔断,所以我们不需要在引入其他的依赖,feign的熔断有两种实现方式,如下:
第一种:实现类
什么是实现类呢?请注意看
UserClient是一个接口,这时候我们需要用一个类来实现这个接口
UserClientHystrix.java
package com.ymy.feign.hystrix;
import com.ymy.entity.vo.User;
import com.ymy.feign.UserClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class UserClientHystrix implements UserClient {
@Override
public User getUserInfo(Integer userId) {
//这里就是当远程调用失败时,feign会自动进入接口的实现类,就是之前所说的熔断,
// 在这里,你可以做对应的处理,比如返回一个空的User对象,或者启用备用方案,调用联外一台服务等等。
//这里我们直接返回空User对象
log.info(“请求cloud-user服务发生错误”);
return null;
}
}
还需要一步那就是在接口指明实现类:
@FeignClient(value = “cloud-user”,fallback = UserClientHystrix.class)
重启cloud-order,cloud-user保持宕机状态:
发现报错了,而且还是和第一次的错误不一致,这是什么问题导致的呢?这是因为我们没有启动feign的熔断机制,默认时关闭状态,找到bootstrap.yml文件:
feign:
hystrix:
enabled: true #开启feign熔断
重启服务再次测试:
控制台:
第二种:指定回调
指定实现类这种方式需要对每个接口做实现,如果所有的请求接口都只需要返回空的话,这样就会重复写很多一样的实现方法,所以这里引入了回调。
1.注释掉实现类,引入hystrix依赖:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.引入注解:@HystrixCommand
@HystrixCommand:这里指明服务调用失败时需要做什么。
fallbackMethod:指明失败之后进入哪个回调方法。
/**
-
失败之后走这里
-
@return
*/
public Order defaultBack(){
log.info(“请求cloud-user服务发生错误”);
Order order = new Order();
order.setId(110);
order.setUserId(120);
order.setOorderPrice(BigDecimal.valueOf(1000l));
order.setNum(20);
return order;
}
3.在启动类中加入:@EnableCircuitBreaker
重启测试:
以上两种方式都可以做熔断处理,具体选择哪种,还要看业务的需求。
源码跟踪
@EnableFeignClients做了什么?
进入FeignClientsRegistrar类
一看,蒙蔽了,这么多方法,这要怎么看,其实我们需要注意的并不多,我们重点来看一下registerFeignClients方法,打上断点,启动服务:
basePackages:指的是扫描的包,下一步对扫描出来的包做遍历
请看candidateComponents的返回结果:
这里是一个集合,找到com.ymy下面有多少个被FeignClient注解标识的类。
在UserClient类中只指定了value属性,所以这里只展示value和name,随后就会将这些被@FeignClient注解标识的类交由jdk动态代理。
这是启动部分,我们再来看看调用过程
feign调用跟踪
spring-cloud-openfeign源码:https://github.com/spring-cloud/spring-cloud-openfeign
Feign源码:https://github.com/OpenFeign/feign
spring-cloud-openfeign是我们项目中使用的依赖,而spring-cloud-openfeign又引入了feign依赖,这里我们只看调用过程,真正的调用过程在Feign源码中,请看:SynchronousMethodHandler.java
调用时,会走这里的代理,断点走起
Object[] argv :参数,这里我只传入了一个userId 值为2。
我们再来看看buildTemplateFromArgs.create(argv):
mutable:
varBuilder:
参数列表,Map集合,回到invoke()方法,
Options options = findOptions(argv) :拿到连接超时等信息
这是默认配置,如果我们在配置文件配置了超时时间,那么这里获取到的就是我们配置的。
进入 executeAndDecode()