Feign

声明式服务调用

Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。

而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix,可以让我们不再需要显式地使用这两个组件。

总起来说,Feign具有如下特性:

  • 可插拔的注解支持,包括Feign注解和JAX-RS注解;

  • 支持可插拔的HTTP编码器和解码器;

  • 支持Hystrix和它的Fallback;

  • 支持Ribbon的负载均衡;

  • 支持HTTP请求和响应的压缩。

这看起来有点像我们springmvc模式的Controller层的RequestMapping映射。这种模式是我们非常喜欢的。Feign是用@FeignClient来映射服务的。

Feign与Hystrix的关系:

feign默认是集成Hystrix,如果想要解除Hystrix,feign提供了两种方式 :

  1. 通过application.yml中增加这个:

feign.hystrix.enabled: false 索性禁用feign的hystrix支持;这样就全部禁止掉Hystrix。

  1. feign提供了对单个Client的禁止,通过配置类来实现,看下面代码:
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.itmuch.config.Configuration2;
 
@FeignClient(name = "xxxx", url = "http://localhost:8761/", configuration = Configuration2.class, fallback = FeignClient2Fallback.class)
public interface FeignClient2 {
  @RequestMapping(value = "/eureka/apps/{serviceName}")
  public String findServiceInfoFromEurekaByServiceName(@PathVariable("serviceName") String serviceName);
} 

对应的Configuration2类如下:

package com.itmuch.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
 
import feign.Feign;
import feign.auth.BasicAuthRequestInterceptor;
 
@Configuration
public class Configuration2 {
  @Bean
  public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
    return new BasicAuthRequestInterceptor("user", "password123");
  }
 
  @Bean
  @Scope("prototype")
  public Feign.Builder feignBuilder() {
    return Feign.builder();
  }
}
说明:Feign.builder默认的是HystrixFeignBuilder,需要改成Builder,就禁止掉Hystrix

Feign相关配置

spring cloud feign的各种配置的使用

##################FeignClientProperties###############
#全局默认配置名称:defalut
feign.client.config.defalut.error-decoder=com.example.feign.MyErrorDecoder
feign.client.config.defalut.connectTimeout=5000
#修改全局默认配置名称为:my-config
feign.client.default-config=my-config
feign.client.config.my-config.error-decoder=com.example.feign.MyErrorDecoder
feign.client.config.my-config.connectTimeout=5000
#局部配置,@FeignClient#name=user
feign.client.config.user.error-decoder=com.example.feign.MyErrorDecoder
feign.client.config.user.connectTimeout=5000
##################FeignHttpClientProperties##################
feign.httpclient.enabled=true
feign.httpclient.connectionTimeout=5000
##################FeignClientEncodingProperties##################
# 配置请求GZIP压缩
feign.compression.request.enabled=true
# 配置响应GZIP压缩 FeignContentGzipEncodingAutoConfiguration
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

说明:当同时配置了全局(默认)配置和局部配置时,局部配置最终会生效。

相关源码

@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    String[] value() default {};
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<?>[] defaultConfiguration() default {};
    Class<?>[] clients() default {};
}
//用于处理 FeignClient 的全局配置和被 @FeignClient 标记的接口
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 处理默认配置类(EnableFeignClients.defaultConfiguration属性)
        this.registerDefaultConfiguration(metadata, registry);
        // 注册被 @FeignClient 标记的接口
        this.registerFeignClients(metadata, registry);
    }
    private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }
            this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
        }
    }
    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // classpath scan工具
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        // 利用FeignClient作为过滤条件
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
        Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
        ...
        // 注册
        this.registerFeignClient(registry, annotationMetadata, attributes);
    }
    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        // 拿到FeignClientFactoryBean的BeanDefinitionBuilder
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);
        definition.addPropertyValue("url", this.getUrl(attributes));
        definition.addPropertyValue("path", this.getPath(attributes));
        String name = this.getName(attributes);
        definition.addPropertyValue("name", name);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.setAutowireMode(2);
        String alias = name + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setPrimary(true);
        String qualifier = this.getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }        
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
}
//自动配置类
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
 
    @Autowired(required = false)
    private List<FeignClientSpecification> configurations = new ArrayList<>();
    @Bean
    public HasFeatures feignFeature() {
        return HasFeatures.namedFeature("Feign", Feign.class);
    }
    @Bean
    public FeignContext feignContext() {
        //加载FeignClientsConfiguration配置类
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }
    @Configuration
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }
    }
    @Configuration
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new DefaultTargeter();
        }
    }
    // the following configuration is for alternate feign clients if
    // ribbon is not on the class path.
    // see corresponding configurations in FeignRibbonClientAutoConfiguration
    // for load balanced ribbon clients.
    @Configuration
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnMissingBean(CloseableHttpClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    protected static class HttpClientFeignConfiguration {
        private final Timer connectionManagerTimer = new Timer(
                "FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
 
        @Autowired(required = false)
        private RegistryBuilder registryBuilder;
        private CloseableHttpClient httpClient;
        @Bean
        @ConditionalOnMissingBean(HttpClientConnectionManager.class)
        public HttpClientConnectionManager connectionManager(
                ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                FeignHttpClientProperties httpClientProperties) {
            final HttpClientConnectionManager connectionManager = connectionManagerFactory
                    .newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
                            httpClientProperties.getMaxConnectionsPerRoute(),
                            httpClientProperties.getTimeToLive(),
                            httpClientProperties.getTimeToLiveUnit(), registryBuilder);
            this.connectionManagerTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    connectionManager.closeExpiredConnections();
                }
            }, 30000, httpClientProperties.getConnectionTimerRepeat());
            return connectionManager;
        }
        @Bean
        public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
                HttpClientConnectionManager httpClientConnectionManager,
                FeignHttpClientProperties httpClientProperties) {
            RequestConfig defaultRequestConfig = RequestConfig.custom()
                    .setConnectTimeout(httpClientProperties.getConnectionTimeout())
                    .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
                    .build();
            this.httpClient = httpClientFactory.createBuilder().
                    setConnectionManager(httpClientConnectionManager).
                    setDefaultRequestConfig(defaultRequestConfig).build();
            return this.httpClient;
        }
        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(HttpClient httpClient) {
            return new ApacheHttpClient(httpClient);
        }
        @PreDestroy
        public void destroy() throws Exception {
            connectionManagerTimer.cancel();
            if(httpClient != null) {
                httpClient.close();
            }
        }
    }
    @Configuration
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
    @ConditionalOnProperty(value = "feign.okhttp.enabled")
    protected static class OkHttpFeignConfiguration {
        private okhttp3.OkHttpClient okHttpClient;
        @Bean
        @ConditionalOnMissingBean(ConnectionPool.class)
        public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
                                                       OkHttpClientConnectionPoolFactory connectionPoolFactory) {
            Integer maxTotalConnections = httpClientProperties.getMaxConnections();
            Long timeToLive = httpClientProperties.getTimeToLive();
            TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
            return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
        }
        @Bean
        public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
                                           ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
            Boolean followRedirects = httpClientProperties.isFollowRedirects();
            Integer connectTimeout = httpClientProperties.getConnectionTimeout();
            Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
            this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).
                    connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
                    followRedirects(followRedirects).
                    connectionPool(connectionPool).build();
            return this.okHttpClient;
        }
        @PreDestroy
        public void destroy() {
            if(okHttpClient != null) {
                okHttpClient.dispatcher().executorService().shutdown();
                okHttpClient.connectionPool().evictAll();
            }
        }
        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient() {
            return new OkHttpClient(this.okHttpClient);
        }
    }
}
@Configuration
public class FeignClientsConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }
    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }
    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }
    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }
    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }
    @Bean
    @ConditionalOnMissingBean(FeignLoggerFactory.class)
    public FeignLoggerFactory feignLoggerFactory() {
        return new DefaultFeignLoggerFactory(logger);
    }
}

Hystrix默认配置

###全集配置
# 设置熔断超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
hystrix.command.default.execution.timeout.enabled=false
 
###局部配置
# 设置熔断超时时间
hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=10000
# 关闭熔断功能
hystrix.command.hello.execution.timeout.enabled=false

ribbon相关配置

###全集配置就是是对所有的请求生效的,CommonClientConfigKey
# 设置连接超时时间
ribbon.ConnectTimeout=600
# 设置读取超时时间
ribbon.ReadTimeout=6000
# 对所有操作请求都进行重试
ribbon.OkToRetryOnAllOperations=true
# 切换实例的重试次数
ribbon.MaxAutoRetriesNextServer=2
# 对当前实例的重试次数
ribbon.MaxAutoRetries=1
 
###局部配置
# Interval to refresh the server list from the source
kxtx-oms.ribbon.ServerListRefreshInterval=2000
# Connect timeout used by Apache HttpClient
kxtx-oms.ribbon.ConnectTimeout=3000
# Read timeout used by Apache HttpClient

spring cloud的Feign Hystrix Ribbon配置详解及原理(源码分析)
https://blog.csdn.net/alex_xfboy/article/details/88840491

Feign 搭建

  1. 创建eureka-server
11.PNG
image

捕获.PNG

build.gradle

plugins {
    id 'org.springframework.boot' version '2.1.6.RELEASE'
    id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
    mavenCentral()
}
ext {
    set('springCloudVersion', "Greenwich.SR2")
}
// 设置sourceSets,只是project的属性
sourceSets {
    // 设置main source, 打包需要用到的资源
    main {
        java{
            srcDirs = ['src/main/java']
        }
        resources {
            srcDirs = ["src/main/resources"] 
        }
    }
    // 设置test source, 单元测试所需的资源
    test {
        java{
            srcDirs = ['src/test/java']
        }
        resources {
            srcDirs = ['src/test/resources']
        }
    }
}
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
    implementation ('org.springframework.boot:spring-boot-starter-security:2.1.2.RELEASE')
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

application.yml

server:
  port: 8888
  servlet:
    context-path: / 
spring:
  application:
    name: eureka-server
  security:
    user:
      name: anxminise
      password: 123456
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@127.0.0.1:${server.port}/eureka/

启动类 EurekaServerApplication.java 添加@EnableEurekaServer注解

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
    
     @EnableWebSecurity 
     public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
     @Override 
         protected void configure(HttpSecurity http) throws Exception {
             http.csrf().disable(); 
             super.configure(http); 
         } 
     }
}
  1. 服务A
    2.1. 创建一个普通的SpringBoot项目
    2.2. build.gradle添加依赖
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.1.1.RELEASE')
    2.3. application.yml 添加eureka配置
eureka:
   client:
      serviceUrl:
         defaultZone: http://anxminise:123456@127.0.0.1:8888/eureka/

2.4. A启动类 添加@EnableEurekaClient注解

@EnableEurekaClient
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
     public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2.5. controller中提供对位调用的API

@RestController
public class TestController {
    @RequestMapping("/hi")
    public String home(@RequestParam String name) {
        return "hi " + name + ",i am from port:8081";
    }
}
  1. 服务B
    3.1. 创建一个普通的SpringBoot项目
    3.2. build.gradle添加依赖
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.1.1.RELEASE') implementation('org.springframework.cloud:spring-cloud-starter-openfeign:2.1.1.RELEASE')
    3.3. application.yml 添加eureka配置
eureka:
   client:
      serviceUrl:
##         defaultZone: http://localhost:8888/eureka/
         defaultZone: http://anxminise:123456@127.0.0.1:8888/eureka/
feign:
   hystrix:
      enabled: true
      
hystrix:  
   command:  
      default:  
         execution:  
            isolation:  
               thread:  
                  timeoutInMilliseconds: 30000 #缺省为1000  

3.4. B启动类 添加 @EnableDiscoveryClient 、@EnableFeignClients

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    public static void main(String[] args) {  
        SpringApplication.run(Application.class, args);
    }
}

3.5. 编写接口 指明需要调用的 server name @FeignClient(value = "xxx")

@FeignClient(value = "xxx")
public interface SchedualServiceHi {
    @RequestMapping(value = "/hi", method = RequestMethod.GET)
    String sayHiFromClientOne(@RequestParam(value = "name") String name);
}

注:

  • 当参数比较复杂时,feign即使声明为get请求也会强行使用post请求
  • 不支持@GetMapping类似的注解声明,需要使用@RequestMapping(value = "url",method = RequestMethod.GET)
  • 使用@RequestParam("paramName")注解时,最好加上参数名
  • 可以传具体的对象
    若Feign 添加 Hystrix:
/**
 * Feign的fallback测试类
 * 使用@FeignClient的fallback属性指定回退类
 */
@FeignClient(value = "ams" ,fallback = SchedualServiceHiFallback.class)
public interface SchedualServiceHi {
    
    @RequestMapping(value = "/hi", method = RequestMethod.GET)
    String sayHiFromClientOne(@RequestParam(value = "name") String name);
    
}
/**
 * 回退类FeignClientFallback需实现Feign Client接口
 */
@Component
public class SchedualServiceHiFallback implements SchedualServiceHi {
    /* (non-Javadoc)
     * 请求发生错误时,执行
     */
    @Override
    public String sayHiFromClientOne(String name) {
        return "error";
    }
}

3.6. controller 注入接口并调用

@RestController
public class HiController {
    @Autowired
    SchedualServiceHi schedualServiceHi;
    
    @RequestMapping(value = "/hi",method = RequestMethod.GET)
    public String sayHi(@RequestParam String name){
        return schedualServiceHi.sayHiFromClientOne(name);
    }
}

4.启动以上server
访问eureka 服务A B已经完成了注册


图片.png

结果:


图片.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容