5、Spring-Cloud-声明式调用 Feign(上)
5.1、写一个 Feign 害户端
新建项目:
依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
关于服务中心使用的是8762(之前的案列写过)
地址:https://www.cnblogs.com/Mrchengs/p/10645911.html
配置文件:
spring.application.name=feign server.port=8088 eureka.client.service-url.defaultZone=http://localhost:8762/eureka/
主配置类:
@EnableFeignClients:开启Feign Client功能
@EnableFeignClients(basePackages = "com.cr.eurekafeignclient.feign") @EnableDiscoveryClient @SpringBootApplication public class EurekaFeignClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaFeignClientApplication.class, args); } }
上述的三个步骤已经开启了Feign的功能
实现之前eureka client的/port服务的调用
EurekaClientFeign.java
@FeignClient(value = "CLINET",configuration = feignconfig.class) public interface EurekaClientFeign { @GetMapping("/port") String port(); }
@FeignClient:
value:远程调用其他服务的服务名
feignconfig.class为Feign Client的配置类
port():通过Feign来调用CLIENT服务的“/port”的API接口
feignconfig.java
@Configuration public class feignconfig { @Bean public Retryer feignRetryer(){ return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1L),5); } }
配置类:注入一个feignRetryer的Retryer的Bean
注入Bean之后,Feign在远程调用失败之后会进行重试
FeignService.java
@Service public class FeignService { @Autowired EurekaClientFeign eurekaClientFeign; public String port(){ return eurekaClientFeign.port(); } }
在Service层FeignService注入EurekaClientFeign去调用port()方法
FeignController.java
@RestController public class FeignController { @Autowired FeignService feignService; @GetMapping("/feign") public String sayPort(){ return feignService.port(); } }
自动注入FeignService 去调用其port()方法
FeignService 通过 EurekaClientFeign远程调用CLIENT服务的API接口“/port”
此时的两个提供者:
分别是8089和8090端口
启动服务:
Feign Client 程调用了 eureka-client 务(8089、8090)两个端口的实例“/port”API接口
Feign Client 有负载均衡的能力。
spring-cloud-starter-openfeign 的porn 文件
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-openfeign-core</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-slf4j</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-hystrix</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-java8</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-archaius</artifactId> <optional>true</optional> </dependency>
5.2、FeignClient详解
@FeignClient注解源码
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.openfeign; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeignClient { @AliasFor("name") String value() default ""; /** @deprecated */ @Deprecated String serviceId() default ""; String contextId() default ""; @AliasFor("value") String name() default ""; String qualifier() default ""; String url() default ""; boolean decode404() default false; Class<?>[] configuration() default {}; Class<?> fallback() default void.class; Class<?> fallbackFactory() default void.class; String path() default ""; boolean primary() default true; }
FeignClient 注解被@Target(ElementType TYPE)修饰:
表示 FeignClient 注解的作用目标在接口上
@Retention(RetentionPolicy.RUNTll\伍)注解表明该注解会在 lass 字节码文件中存在,
在运行时可以通过反射获取到
@Documented 表示该注解将被包含在 Javadoc 中。
@Feign Client 注解用于创建声明式 API 接口,该接口是RESTful 风格的。
Feign 被设计成插拔式的,可以注入其他组件和 Feign一起使用
最典型的是如果 Ribbon 可用, Feign 会和Ribbon 结合进行负载均衡。
注解中的属性:
---value()和 name() 样,是被调用的服务的 Serviceld
---url ()直接填写硬编码的Uri 地址
---decode404()即 404 是被解码,还是抛异常。
---configuration ()指明 FeignClient 配置类,
默认的配置类为 FeignClientsConfiguration类,在缺省的情况下 这个类注入
了默认的 DecoderEncoder、 Contract 等配置的 Bean
---fall back()为配置熔断器的处理类。
5.3、FeignClient的配置
Feign Client 默认的配置类为 FeignClientsConfiguration,这个类在在 pring-cloud-netflix-core的jar下
现这个类注入了很多 Feign 相关的配置 Bean(默认不配置的时候使用默认的)
Decoder、Encoder 、Contract个类在没有Bean被注入的情况下,会自动注入默认配置的 Bean
@Configuration public class FeignClientsConfiguration { @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; @Autowired( required = false ) private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList(); @Autowired( required = false ) private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList(); @Autowired( required = false ) private Logger logger; public FeignClientsConfiguration() { } @Bean @ConditionalOnMissingBean public Decoder feignDecoder() { return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); } @Bean @ConditionalOnMissingBean @ConditionalOnMissingClass({"org.springframework.data.domain.Pageable"}) public Encoder feignEncoder() { return new SpringEncoder(this.messageConverters); } @Bean @ConditionalOnClass( name = {"org.springframework.data.domain.Pageable"} ) @ConditionalOnMissingBean public Encoder feignEncoderPageable() { return new PageableSpringEncoder(new SpringEncoder(this.messageConverters)); } @Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { return new SpringMvcContract(this.parameterProcessors, feignConversionService); } @Bean public FormattingConversionService feignConversionService() { FormattingConversionService conversionService = new DefaultFormattingConversionService(); Iterator var2 = this.feignFormatterRegistrars.iterator(); while(var2.hasNext()) { FeignFormatterRegistrar feignFormatterRegistrar = (FeignFormatterRegistrar)var2.next(); feignFormatterRegistrar.registerFormatters(conversionService); } return conversionService; } @Bean @ConditionalOnMissingBean public Retryer feignRetryer() { return Retryer.NEVER_RETRY; } @Bean @Scope("prototype") @ConditionalOnMissingBean public Builder feignBuilder(Retryer retryer) { return Feign.builder().retryer(retryer); } @Bean @ConditionalOnMissingBean({FeignLoggerFactory.class}) public FeignLoggerFactory feignLoggerFactory() { return new DefaultFeignLoggerFactory(this.logger); } @Bean @ConditionalOnClass( name = {"org.springframework.data.domain.Page"} ) public Module pageJacksonModule() { return new PageJacksonModule(); } @Configuration @ConditionalOnClass({HystrixCommand.class, HystrixFeign.class}) protected static class HystrixFeignConfiguration { protected HystrixFeignConfiguration() { } @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty( name = {"feign.hystrix.enabled"} ) public Builder feignHystrixBuilder() { return HystrixFeign.builder(); } } }
重写 FeignClientsConfiguration 类中的 Bean
覆盖掉默认的配置 Bean
从而达到自定义配置的目的
Feign 默认的配置在请求失败后 重试次数为 ,即不重试Retryer.NEVER_RETRY
现在希望在请求失败后能够重试
这时需要写 个配置 FeignConfig 类
在该类中注入Retryer的Bean会覆盖掉默认的 Retryer的Bean
@Configuration public class feignconfig { @Bean public Retryer feignRetryer(){ return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1L),5); } }