继承特性
1.首先新建个工程,名为hello-service-api。因为要用到Spring MVC的注解所以要要加锁spring-boot-web的依赖,具体如下:
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>hello-service-api</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>hello-service-api</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.5.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
2.因为在服务提供者和服务消费者中需要用到User这个类,所以将其提取出来放置到新建的工程中。
3.创建HelloService接口:
@RequestMapping("/refactor")
public interface HelloService {@RequestMapping("/hello")public String hello();@RequestMapping(value = "/hello1", method = RequestMethod.GET)String hello(@RequestParam("name") String name);@RequestMapping(value = "/hello2", method = RequestMethod.GET)Map<String,Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price);@RequestMapping(value = "/hello3", method = RequestMethod.POST)String hello(@RequestBody Map<String, Object> book);@RequestMapping(value = "/hello4", method = RequestMethod.POST)String hello(@RequestBody User user);
}
最后的目录结构:
然后需要对hello-service(服务提供者)和feign-consumer(服务调用)进行重构:
4.首先需要在各自的pom.xml文件中引入对hello-service-api的依赖:
<dependency><groupId>com.example</groupId><artifactId>hello-service-api</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
5.重写服务提供者的控制器,改为:
@RestController
@RequestMapping("/refactor")
public class HelloController {private final Logger logger=Logger.getLogger(getClass());@RequestMapping("/hello")public String index() throws InterruptedException {
// int sleepTime=new Random().nextInt(3000);
// logger.info("sleep:"+sleepTime);
// Thread.sleep(sleepTime);logger.info(new Date());return "Hello"+new Date()+"---"+new Random().nextInt();}@RequestMapping(value = "/hello1", method = RequestMethod.GET)public String hello1(@RequestParam String name) {return "hello " + name + "!";}@RequestMapping(value = "/hello2", method = RequestMethod.GET)public Map<String,Object> hello2(@RequestHeader String name, @RequestHeader String author, @RequestHeader Integer price) throws UnsupportedEncodingException {Map<String,Object> map = new HashMap<String,Object>();map.put("name",URLDecoder.decode(name));map.put("author",URLDecoder.decode(author));return map;}@RequestMapping(value = "/hello3", method = RequestMethod.POST)public String hello3(@RequestBody Map<String,Object> book) {return "书名为:" + book.get("name") + ";作者为:" + book.get("author");}@RequestMapping(value = "/hello4", method = RequestMethod.POST)public String hello4(@RequestBody User user) {return "用户名:" + user.getName() + ";年龄为:" + user.getAge();}}
需要注意的是在原先的控制器上新增了@RequestMapping("/refactor"),要跟hello-service-api上的RequestMapping相同,这里也可以用实现(implements)的方式实现。
6.创建HelloServiceImpl实现HelloService接口并添加@FeignClient注解绑定服务。
@FeignClient("hello-service")//用于绑定名为hello-service服务
public interface HelloServiceImpl extends HelloService {
}
7.修改其控制器如:
@RestController
public class ConsumerController {// @Autowired
// HelloService helloService;@AutowiredHelloServiceImpl helloService;@GetMapping("/feign-consumer")public String helloConsumer() {return helloService.hello();}@RequestMapping("/hello1")public String hello1() {return helloService.hello("张三");}@RequestMapping(value = "/hello2")public Map<String, Object> hello2() throws UnsupportedEncodingException {Map<String, Object> book = helloService.hello(URLEncoder.encode("三国演义", "UTF-8"), URLEncoder.encode("罗贯中", "UTF-8"), 33);System.out.println(book);return book;}@RequestMapping("/hello3")public String hello3() {Map<String, Object> book = new HashMap<String, Object>();book.put("name", "三国演义");book.put("author", "罗贯中");return helloService.hello(book);}@RequestMapping("/hello4")public String hello4() {User user = new User("刘德华", 99);return helloService.hello(user);}}
OK这就是Feign的继承特性
Ribbon配置
由于Spring Cloud Feign的客户端负载均衡是通过Spring Cloud Ribbon实现,所以我们可以直接通过配置Ribbon客户端方式来自定义各个服务客户端调用的参数。
全局配置
全局配置的方式十分容易,如同Ribbon的配置一样可以直接使用ribbon.<key>=<value>的方式来设置ribbon的各项默认参数。如修改客户端调用超时时间:
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000
指定服务设置
在使用Spring Cloud Feign的时候,针对各个服务客户端进行个性化配置的方式与使用Spring Cloud Ribbon时的配置方式是一样的,都采用<client>.ribbon.key=value的格式进行设置。
在定义Feign客户端时,我们使用了@FeignClient注解。在初始化过程中,Spring Cloud Feign会根据该注解的name属性或value属性指定的服务名,自动创建一个同名的Ribbon客户端。在上面的代码中使用的@FeignClient("hello-service")这时的application.properties的配置如:
# 设置针对hello-service服务的连接超时时间
hello-service.ribbon.ConnectTimeout=600
# 设置针对hello-service服务的读取超时时间
hello-service.ribbon.ReadTimeout=6000
# 设置针对hello-service服务所有操作请求都进行重试
hello-service.ribbon.OkToRetryOnAllOperations=true
# 设置针对hello-service服务切换实例的重试次数
hello-service.ribbon.MaxAutoRetriesNextServer=2
# 设置针对hello-service服务的当前实例的重试次数
hello-service.ribbon.MaxAutoRetries=1
Hystrix配置
在Spring Cloud Feign中除了引入了客户端负载均衡的Spring Cloud Ribbon之外,还引入了Hystrix。默认情况下Spring Cloud Feign会为所有Feign客户端方法都封装到Hystrix命令中进行服务保护。
全局配置
对于Hystrix的全局配置同Spring Cloud Ribbon的全局配置一样,直接使用它的默认配置前缀hystrix.command.default进行设置,如设置全局超时时间:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
另外对Hystrix进行配置之前,我们需要确认feign.hystrix.enabled参数没有被设置为false,否则该参数设置会关闭Feign客户端的Hystrix支持。feign.hystrix.enabled=false可以关闭Hystrix功能,hystrix.command.default.execution.timeout.enabled=false关闭熔断功能。
禁用Hystrix
除了feign.hystrix.enabled=false可以关闭Hystrix功能,如果想针对某个服务客户端关闭Hystrix支持可以使用@Scope("prototype")注解为指定的客户端配置Feign.Builder实例。
@Configuration
public class DisableHystrixConfiguration{@Bean@Scope("prototype")public Builder feignBuilder(){return Feign.builder();}
}
在HelloService中的@FeignClient注解中,通过configuration参数引入上面实现的配置。
@FeignClient(name="hello-service",configuration=DisableHystrixConfiguration.class)
指定的命令配置
指定命令配置方法也跟传统的Hystrix 命令的参数配置相似,采用hystrix.command.<commandkey>作为前缀,而默认情况下会采用Feign客户端中的方法名作为标识,比如, 针对hello接口的熔断超时时间的配置可以通过其方法名作为来进行配置, 具体如下:
hystrix.command.hello.execution.isolation.thread.timeoutinMilliseconds=5OOO
在使用指定命令配置的时候, 需要注意, 由于方法名很有可能重复, 这个时候相同方法名的Hystrix配置会共用,所以在进行方法定义与配置的时候需要做好一定的规划。当然,也可以重写Feign.Builder的实现,并在应用主类中创建它的实例来覆盖自动化配置的HystrixFeign.Builder实现
服务降级配置
由于Spring Cloud Feign在定义服务客户端的时候与Spring Cloud Ribbon有很大差别,HystrixCommand定义被封装了起来, 我们无法像之前介绍Spring Cloud Hystrix时, 通过@HystrixCommand注解的fallback参数那样来指定具体的服务降级处理方法。但是, Spring Cloud Feign提供了另外一种简单的定义方式,如下:
1.继承HelloService类,实现服务降级逻辑:
@Component
@RequestMapping("fallback/refactor")//必须要加,不然会报bean创建异常
public class HelloServiceFallbackImpl implements HelloService {@Overridepublic String hello() {return "error";}@Overridepublic String hello(@RequestParam("name") String name) {return "error1";}@Overridepublic Map<String, Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price) {Map<String, Object> map = new HashMap<String, Object>();map.put("error", "未知");return map;}@Overridepublic String hello(@RequestBody Map<String, Object> book) {return "error3";}@Overridepublic String hello(@RequestBody User user) {return "error4";}
}
2.通过FeignClient注解的fallback属性来指定对应的服务降级实现类
@RequestMapping("/refactor")
@FeignClient(name = "hello-service",fallback = HelloServiceFallbackImpl.class)//用于绑定名为hello-service服务 ,fallback = HelloServiceFallbackImpl.class
public interface HelloService {@RequestMapping("/hello")public String hello();@RequestMapping(value = "/hello1", method = RequestMethod.GET)String hello(@RequestParam("name") String name);@RequestMapping(value = "/hello2", method = RequestMethod.GET,produces = {"application/json;charset-UTF-8"})Map<String,Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price);@RequestMapping(value = "/hello3", method = RequestMethod.POST)String hello(@RequestBody Map<String,Object> book);@RequestMapping(value = "/hello4", method = RequestMethod.POST)String hello(@RequestBody User user);
}
3.修改application.properties开启服务降级功能
spring.application.name=feign-consumer
server.port=9001eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/feign.hystrix.enabled=true
并且修改Hello-service使其超时
启动服务后访问:
其他配置
请求压缩
Spring Cloud Feign支持对请求与响应的GZIP压缩,以减少通信过程中的性能损耗,只需要设置下面两个参数即可:
# 配置请求GZIP压缩
feign.compression.request.enabled=true
# 配置响应GZIP压缩
feign.compression.response.enabled=true
并且能对请求压缩做更细致的设置,如:
# 配置压缩支持的MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置压缩数据大小的下限
feign.compression.request.min-request-size=2048
日志配置
Spring Cloud Feign在构建被@FeignClient注解修饰的客户端服务时,会为每一个客户端都创建一个feign.Logger实例。开启方式如下:
logging.level.com.example.feign.consumer.service.HelloService=DEBUG
但是由于Feign客户端默认的Logger.Level对象定义为NONE级别,该级别不会记录任何Feign调用过程中的信息。所以要在应用主类中加入Logger.Level的Bean创建:
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class FeignConsumerApplication {@BeanLogger.Level feignLoggerLevel(){return Logger.Level.FULL;}public static void main(String[] args) {SpringApplication.run(FeignConsumerApplication.class, args);}
}
Logger级别主要有4类:
NONE:不记录任何信息
BASIC:仅记录请求方法,URL以及响应吗和执行时间。
HEADERS:除了记录BASIC级别的信息外,还会记录请求和响应的头信息。
FULL:记录所有请求与响应的明细,包括头信息,请求体,元数据等。
参考:《Spring Cloud 微服务实战》