说明
在 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")@Componentpublic 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 {@Autowiredprivate 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容器中*/@Componentpublic class UserServiceFeignClientHystrix implements UserServiceFeignClient {@Overridepublic 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)@Componentpublic 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;@Configurationpublic class FeignConfig {@BeanLogger.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)@Componentpublic interface UserServiceFeignClient {@RequestMapping("/user/{id}")User findUserById(@PathVariable("id") Long id);}
重启 consumer-service,访问,http://localhost:8080/consumer/6,查看效果
