不明觉厉-OpenFeign是REST客户端

这是我参与更文挑战的第8天

Spring Cloud OpenFeign是Spring Cloud团队将原生的Feign结合到Spring Cloud中的产物。从上面原生Feign的使用示列来看,用的注解都是Feign中自带的(参考GitHub地址:github.com/OpenFeign/f… MVC的注解,不是很方便调用。所以Spring Cloud OpenFeign扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。

GitHub地址:github.com/spring-clou…

@FeignClient("stores")
public interface StoreClient {    
    @RequestMapping(method = RequestMethod.GET, value = "/stores")    
    List getStores();    
    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")    
    Store update(@PathVariable("storeId") Long storeId, Store store);
}
复制代码
  • value, name:value和name的作用一样,如果没有配置url那么配置的值将作为服务名称,用于服务发现,反之只是一个名称。

  • serviceId:serviceId已经废弃了,直接使用name即可。

  • contextId:比如我们有个user服务,但user服务中有很多个接口,我们不想将所有的调用接口都定义在一个类中,比如:

    Client 1:

    @FeignClient(name = "optimization-user")
    public interface UserRemoteClient1 {    
        @GetMapping("/user/get")    
        User getUser(@RequestParam("id") int id);
    }
    复制代码

    Client 2:

    @FeignClient(name = "optimization-user")
    public interface UserRemoteClient2 {    
        @GetMapping("/user2/get")    
        public User getUser(@RequestParam("id") int id);
    }
    复制代码

    在这种情况下启动项目就会报错了,因为Bean的名称冲突了,具体错误如下:(下面提供的解决方案,个人理解云里雾里)

    Description:The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. 
    A bean with that name has already been defined in null and overriding is disabled.
    Action:Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
    复制代码
  • url:url用于配置指定服务的地址,相当于直接请求这个服务,不经过Ribbon的服务选择,像**调试(开发)**等场景可以使用。

    @FeignClient(name = "optimization-user", url = "http://localhost:8085")
    public interface UserRemoteClient {        
        @GetMapping("/user/get")    
        public User getUser(@RequestParam("id") int id);
    }
    复制代码
  • decode404:当调用请求发生404错误时,decode404的值为true,那么会执行decoder解码,否则抛出异常,抛异常的话就是异常信息了,如果配置了fallback那么就会执行回退逻辑,解码也就是会返回固定的数据格式:

    {"timestamp":"2020-01-05T09:18:13.154+0000","status":404,"error":"Not Found","message":"No message available","path":"/user/get"}
    复制代码

    抛异常的话就是异常信息了,如果配置了fallback那么就会执行回退逻辑:

  • configuration:是配置Feign配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等,configuration定义:

    public class FeignConfiguration {    
        @Bean    
        public Logger.Level getLoggerLevel() {        
            return Logger.Level.FULL;    
        }    
        @Bean    
        public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {        
            return new BasicAuthRequestInterceptor("user", "password");    
        }        
        @Bean    
        public CustomRequestInterceptor customRequestInterceptor() {        
            return new CustomRequestInterceptor();    
        }    // Contract,feignDecoder,feignEncoder.....}
    复制代码

    使用示列

    @FeignClient(value = "optimization-user", configuration = FeignConfiguration.class)
    public interface UserRemoteClient {        
        @GetMapping("/user/get")    
        public User getUser(@RequestParam("id")int id);    
    }
    复制代码
  • fallback:定义容错的处理类,也就是回退逻辑,fallback的类必须实现Feign Client的接口,无法知道熔断的异常信息,fallback定义:

    @Componentpublic 
    class UserRemoteClientFallback implements UserRemoteClient {    
        @Override    
        public User getUser(int id) {        
            return new User(0, "默认fallback");    
        }    
    }
    复制代码
    @FeignClient(value = "optimization-user", fallback = UserRemoteClientFallback.class)
    public interface UserRemoteClient {        
        @GetMapping("/user/get")    
        public User getUser(@RequestParam("id")int id);    
    }
    复制代码
  • fallbackFactory:也是容错的处理,可以知道熔断的异常信息,fallbackFactory定义

    @Componentpublic 
    class UserRemoteClientFallbackFactory implements FallbackFactory {    
        private Logger logger = LoggerFactory.getLogger(UserRemoteClientFallbackFactory.class);        
        @Override    
        public UserRemoteClient create(Throwable cause) {        
            return new UserRemoteClient() {            
                @Override            
                public User getUser(int id) {                
                    logger.error("UserRemoteClient.getUser异常", cause);                
                    return new User(0, "默认");            
                }        
            };    
        }
    }
    复制代码
    @FeignClient(value = "optimization-user", fallbackFactory = UserRemoteClientFallbackFactory.class)
    public interface UserRemoteClient {        
        @GetMapping("/user/get")    
        public User getUser(@RequestParam("id")int id);    
    }
    复制代码
  • path:path定义当前FeignClient访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是user, 那么具体方法上的路径就只需要写/get 即可。

    @FeignClient(name = "optimization-user", path="user")
    public interface UserRemoteClient {        
        @GetMapping("/get")    
        public User getUser(@RequestParam("id") int id);
    }
    复制代码
  • primary:primary对应的是@Primary注解,默认为true,官方这样设置也是有原因的。当我们的Feign实现了fallback后,也就意味着Feign Client有多个相同的Bean在Spring容器中,当我们在使用@Autowired进行注入的时候,不知道注入哪个,所以我们需要设置一个优先级高的,@Primary注解就是干这件事情的。

  • qualifier:qualifier对应的是@Qualifier注解,使用场景跟上面的primary关系很淡,一般场景直接@Autowired直接注入就可以了。

    如果我们的Feign Client有fallback实现,默认@FeignClient注解的primary=true, 意味着我们使用@Autowired注入是没有问题的,会优先注入你的Feign Client。

    如果你鬼斧神差的把primary设置成false了,直接用@Autowired注入的地方就会报错,不知道要注入哪个对象。

    解决方案很明显,你可以将primary设置成true即可,如果由于某些特殊原因,你必须得去掉primary=true的设置,这种情况下我们怎么进行注入,我们可以配置一个qualifier,然后使用@Qualifier注解进行注入,示列如下:

    Feign Client定义:

    @FeignClient(name = "optimization-user", path="user", qualifier="userRemoteClient")
    public interface UserRemoteClient {        
        @GetMapping("/get")    
        public User getUser(@RequestParam("id") int id);
    }
    复制代码

    Feign Client注入

    @Autowired@Qualifier("userRemoteClient")
    private UserRemoteClient userRemoteClient;
    复制代码

解决方案A:可以增加下面的配置,作用是允许出现beanName一样的BeanDefinition

spring.main.allow-bean-definition-overriding=true
复制代码

解决方案B:就是为每个Client手动指定不同的contextId,这样就不会冲突了(个人看的是云里雾里)

上面给出了Bean名称冲突后的解决方案,下面来分析下contextId在Feign Client的作用,在注册Feign Client Configuration的时候需要一个名称,名称是通过getClientName方法获取的:

String name = getClientName(attributes);registerClientConfiguration(registry, name,attributes.get("configuration"));
private String getClientName(Map client) {    
    if (client == null) {      
        return null;    
    }    
    String value = (String) client.get("contextId");    
    if (!StringUtils.hasText(value)) {      
        value = (String) client.get("value");    
    }    
    if (!StringUtils.hasText(value)) {      
        value = (String) client.get("name");    
    }    
    if (!StringUtils.hasText(value)) {      
        value = (String) client.get("serviceId");    
    }    
    if (StringUtils.hasText(value)) {      
        return value;    
    }    
    throw new IllegalStateException("Either 'name' or 'value' must be provided in @"+ FeignClient.class.getSimpleName());  
}
复制代码

可以看到如果配置了contextId就会用contextId,如果没有配置就会去value然后是name最后是serviceId。默认都没有配置,当出现一个服务有多个Feign Client的时候就会报错了。

其次的作用是在注册FeignClient中,contextId会作为Client 别名的一部分,如果配置了qualifier优先用qualifier作为别名。

private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map attributes) {    
    String className = annotationMetadata.getClassName();    
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);    
    validate(attributes);    
    definition.addPropertyValue("url", getUrl(attributes));    
    definition.addPropertyValue("path", getPath(attributes));    
    String name = getName(attributes);    
    definition.addPropertyValue("name", name);    
    String contextId = getContextId(attributes);    
    definition.addPropertyValue("contextId", contextId);    
    definition.addPropertyValue("type", className);    
    definition.addPropertyValue("decode404", attributes.get("decode404"));    
    definition.addPropertyValue("fallback", attributes.get("fallback"));    
    definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));    
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);    
    // 拼接别名    
    String alias = contextId + "FeignClient";    
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();    
    boolean primary = (Boolean) attributes.get("primary"); 
    // has a default, won't be                                
    // null    beanDefinition.setPrimary(primary);    
    // 配置了qualifier优先用qualifier    
    String qualifier = getQualifier(attributes);    
    if (StringUtils.hasText(qualifier)) {      
        alias = qualifier;    
    }    
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });    
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);  
}
复制代码

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享