说明
在 hystix-demo
(SpringCloud_Hystix.md
中有步骤)工程中,
我们使用了 Ribbon
的负载均衡功能,大大简化了远程调用时的代码
String url = "http://user-service/user/" + id;
// 查询
User user = restTemplate.getForObject(url, User.class);
return user;
如果就学到这里,你可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?
这就是我们接下来要学的 Feign
的功能了。
demo
在 hystix-demo
的基础上进行 demo
练习,
在 consumer-service
引入依赖
<!-- Feign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
开启 Feign
功能
在启动类上,添加注解,
你会发现 RestTemplate
的注册被我删除了。Feign
中已经自动集成了 Ribbon
负载均衡,因此我们不需要自己定义 RestTemplate
了
package com.it.learn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableHystrix
@EnableFeignClients // 开启 Feign 功能支持
public class UserConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerApplication .class);
}
}
Feign
的客户端
客户端接口要与服务提供者的入口方法保持一致(与服务提供者Controller中的方法名一致)
@FeignClient("user-service") // 设置服务提供者的名称
@Component // 创建接口实现类对象,并将对象存放到IOC容器中
package com.it.learn.feign;
import com.it.learn.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("user-service")
@Component
public interface UserServiceFeignClient {
@RequestMapping("/user/{id}")
User findUserById(@PathVariable("id") Long id);
}
- 首先这是一个接口,
Feign
会通过动态代理,帮我们生成实现类。这点跟mybatis
的mapper
很像 @FeignClient
,声明这是一个Feign
客户端,同时通过value
属性指定服务名称- 接口中的定义方法,完全采用
SpringMVC
的注解,Feign
会根据注解帮我们生成URL
,并访问获取结果
改造原来的调用逻辑,使用 UserServiceFeignClient
访问:
package com.it.learn.controller;
import com.it.learn.feign.UserServiceFeignClient;
import com.it.learn.pojo.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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.RestController;
@RestController
@RequestMapping("consumer")
public class UserController {
@Autowired
private UserServiceFeignClient userServiceFeignClient;
@RequestMapping("/{id}")
@HystrixCommand(fallbackMethod = "findUserByIdForFail")
public User findUserById(@PathVariable("id") Long id){
User user = userServiceFeignClient.findUserById(id);
return user;
}
/**
* 降级处理的方法,与原方法返回值,参数列表保持一致
* @param id
* @return
*/
public User findUserByIdForFail(@PathVariable("id") Long id){
User user = new User();
user.setId(id);
user.setName("网页丢失了");
return user;
}
}
启动,测试,访问接口 http://localhost:8080/consumer/6
Hystix支持
Feign
默认也有对 Hystix
的集成:
只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
但是,Feign
中的 Fallback
配置不像 Ribbon
中那样简单了。
首先,我们要定义一个类,实现刚才编写的 UserServiceFeignClient
,作为 fallback
的处理类
package com.it.learn.feign.fallback;
import com.it.learn.feign.UserServiceFeignClient;
import com.it.learn.pojo.User;
import org.springframework.stereotype.Component;
/**
* 创建降级类对象,将降级类对象存放到ioc容器中
*/
@Component
public class UserServiceFeignClientHystrix implements UserServiceFeignClient {
@Override
public User findUserById(Long id) {
User user = new User();
user.setId(id);
user.setName("网页丢失了555");
return user;
}
}
在 Feign
客户端类上指定降级类
package com.it.learn.feign;
import com.it.learn.feign.fallback.UserServiceFeignClientHystrix;
import com.it.learn.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* value: 服务提供者名称
* fallback: 降级类的字节码对象
*/
@FeignClient(value = "user-service", fallback = UserServiceFeignClientHystrix.class)
@Component
public interface UserServiceFeignClient {
@RequestMapping("/user/{id}")
User findUserById(@PathVariable("id") Long id);
}
重启 consumer-service
测试,故意在 user-service
中打断点,超时,远程调用导致降级
日志级别
前面讲过,通过logging.level.xx=debug
来设置日志级别。然而这个对 Fegin
客户端而言不会产生效果。因为@FeignClient
注解修改的客户端在被代理时,都会创建一个新的 Fegin.Logger
实例。我们需要额外指定这个日志的级别才可以。
设置 com.it.learn
包下的日志级别都为 debug
编写配置类,定义日志级别
package com.it.learn.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
Feign
客户端开启日志记录
package com.it.learn.feign;
import com.it.learn.config.FeignConfig;
import com.it.learn.feign.fallback.UserServiceFeignClientHystrix;
import com.it.learn.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* value: 服务提供者名称
* fallback: 降级类的字节码对象
*/
@FeignClient(value = "user-service", fallback = UserServiceFeignClientHystrix.class, configuration = FeignConfig.class)
@Component
public interface UserServiceFeignClient {
@RequestMapping("/user/{id}")
User findUserById(@PathVariable("id") Long id);
}
重启 consumer-service
,访问,http://localhost:8080/consumer/6,查看效果