OpenFeign @FeignClient 注解详解

@FeignClient是 Spring Cloud 提供的一个注解,用于在 Java 应用程序中创建声明式的 REST 客户端。它可以帮助开发者轻松地调用远程 REST 服务,就好像在调用本地方法一样简单。通过这个注解,能够有效地简化微服务之间的通信代码编写。

例如,在一个包含用户服务和订单服务的微服务架构中,订单服务可能需要获取用户信息来完成订单处理。使用 @FeignClient,可以在订单服务中方便地定义一个接口来调用用户服务的 REST 端点,而无需编写大量的底层 HTTP 请求代码。

使用方式

定义接口

使用 @FeignClient 注解来定义一个接口,这个接口中的方法签名定义了要调用的远程服务的端点和请求方式。方法上可以使用 Spring MVC 注解(如 @GetMapping、@PostMapping 等)来指定请求路径和方法类型。例如:

@FeignClient(name = "user-service")
public interface UserClient {
    
    @GetMapping("/users/{id}")
    User getUserById(@PathVariable("id") Long id);
    
}

使用接口

将定义好的 Feign 客户端接口注入到需要调用远程服务的服务类中,然后就可以像调用本地方法一样使用这个接口方法来调用远程服务。

例如,在订单服务中调用用户服务获取用户信息:

@Service
public class OrderService {
    @Autowired
    private UserClient userClient;
    
    public Order createOrder(Long userId, List<Product> products) {
        User user = userClient.getUserById(userId);
        // 其他订单创建逻辑
        return new Order(user, products);
    }
}

当应用启动时,Spring Cloud 会扫描带有 @FeignClient 注解的接口,并为这些接口创建动态代理对象。这些代理对象会在调用接口方法时,根据注解中的信息(如服务名称、请求路径等)构建 HTTP 请求,然后通过底层的 HTTP 客户端(如 Ribbon 和 HttpClient)发送请求到目标服务。

Ribbon 会根据服务发现获取的服务实例列表进行负载均衡,选择一个合适的服务实例来发送请求。如果请求失败并且配置了回退机制,就会调用相应的回退逻辑。

注解源码

下面是 @FeignClient 的源码,支持很多属性:

package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

/**
 * Annotation for interfaces declaring that a REST client with that interface should be
 * created (e.g. for autowiring into another component). If SC LoadBalancer is available
 * it will be used to load balance the backend requests, and the load balancer can be
 * configured using the same name (i.e. value) as the feign client.
 *
 * @author Spencer Gibb
 * @author Venil Noronha
 * @author Olga Maciaszek-Sharma
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {

    /**
     * 服务名称,带可选协议前缀。name 的同义词。无论是否提供 url,都必须为所有客户端
     * 指定一个名称。可指定为属性键,例如:${propertyKey}。
	 * The name of the service with optional protocol prefix. Synonym for {@link #name()
	 * name}. A name must be specified for all clients, whether or not a url is provided.
	 * Can be specified as property key, eg: ${propertyKey}.
	 * @return the name of the service with optional protocol prefix
	 */
    @AliasFor("name")
    String value() default "";

    /**
     * 如果存在,它将用作 bean 名称,而不是 name,但不会用作服务 ID。
	 * This will be used as the bean name instead of name if present, but will not be used
	 * as a service id.
	 * @return bean name instead of name if present
	 */
    String contextId() default "";

    /**
     * 带有可选协议前缀的服务ID。与 value() 同义。
	 * @return The service id with optional protocol prefix. Synonym for {@link #value()
	 * value}.
	 */
    @AliasFor("value")
    String name() default "";

    /**
     * Feign 客户端的 @Qualifiers 值
 	 * @return the <code>@Qualifiers</code> value for the feign client.
	 */
    String[] qualifiers() default {};

    /**
     * 一个绝对 URL 或可解析的主机名(协议是可选的)
	 * @return an absolute URL or resolvable hostname (the protocol is optional).
	 */
    String url() default "";

    /**
     * 404 状态码是否应该被解码而不是抛出 FeignExceptions。
	 * @return whether 404s should be decoded instead of throwing FeignExceptions
	 */
    boolean dismiss404() default false;

    /**
     * feign 客户端的自定义配置类。可以包含对构成客户端的各个部分的 @Bean 定义的覆盖,
     * 例如 feign.codec.Decoder、feign.codec.Encoder、feign.Contract。
	 * A custom configuration class for the feign client. Can contain override
	 * <code>@Bean</code> definition for the pieces that make up the client, for instance
	 * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
	 *
	 * @see FeignClientsConfiguration for the defaults
	 * @return list of configurations for feign client
	 */
    Class<?>[] configuration() default {};

    /**
     * 指定 Feign 客户端接口的后备类。回退类必须实现此注解所注解的接口,并且是一个
     * 有效的 Spring Bean。
	 * Fallback class for the specified Feign client interface. The fallback class must
	 * implement the interface annotated by this annotation and be a valid spring bean.
	 * @return fallback class for the specified Feign client interface
	 */
    Class<?> fallback() default void.class;

    /**
     * 为指定的 Feign 客户端接口定义回退工厂。回退工厂必须生成实现 FeignClient 所注
     * 解接口的回退类实例。回退工厂必须是有效的 Spring Bean。
	 * Define a fallback factory for the specified Feign client interface. The fallback
	 * factory must produce instances of fallback classes that implement the interface
	 * annotated by {@link FeignClient}. The fallback factory must be a valid spring bean.
	 *
	 * @see FallbackFactory for details.
	 * @return fallback factory for the specified Feign client interface
	 */
	Class<?> fallbackFactory() default void.class;

	/**
     * 所有方法级别映射要使用的路径前缀。
	 * @return path prefix to be used by all method-level mappings.
	 */
	String path() default "";

	/**
     * 是否将伪装代理标记为主要 bean。默认为 true。
	 * @return whether to mark the feign proxy as a primary bean. Defaults to true.
	 */
	boolean primary() default true;

}

@FeignClient 注解属性说明:

contextId 属性

contextId属性用于为 Feign 客户端提供一个唯一的标识符。这个标识符在 Spring 容器的上下文中具有重要作用,特别是当存在多个相同名称(name属性相同)的@FeignClient或者需要对特定的 Feign 客户端进行更精细的配置和管理时。

当有多个@FeignClient具有相同的name属性时,contextId可以用来区分它们。例如,在一个复杂的微服务架构中,可能有多个不同版本或者不同用途的 Feign 客户端指向同一个服务(相同的name),通过设置不同的contextId,Spring 可以准确地识别和管理每个客户端。

dismiss404 属性

dismiss404属性是一个不太常见但很有用的属性。它主要用于控制 Feign 客户端对 HTTP 404(未找到)状态码的处理方式。

默认情况下,当 Feign 客户端收到一个 404 状态码的响应时,它会抛出一个FeignException.NotFound异常。这是因为在大多数情况下,404 被认为是一种错误情况,表示请求的资源不存在。

例如,当你使用 Feign 客户端调用一个远程服务的端点,而这个端点对应的资源已经被删除或者路径有误时,就会收到 404 状态码,进而触发异常抛出机制。

当dismiss404属性设置为true时,Feign 客户端不会对 404 状态码抛出异常。这在一些特定的场景下非常有用,比如你在查询一个可能不存在的资源,并且希望在代码层面自己处理资源不存在的情况,而不是让 Feign 抛出异常来中断业务流程。

configuration 属性

configuration属性用于自定义 Feign 客户端的配置。它允许你指定一个或多个配置类,这些配置类可以修改 Feign 客户端的各种行为,如日志级别、编码器和解码器、请求拦截器等。

fallback 属性

fallback属性是 OpenFeign 中用于实现服务降级的一个重要特性。

当所调用的远程服务出现故障(如网络异常、服务不可用、响应超时等)时,fallback属性指定的类会提供一个备用的响应或者执行替代的逻辑,以避免客户端因为远程服务的问题而出现故障,从而增强整个系统的健壮性和容错性。

当你在@FeignClient注解中设置了fallback属性后,OpenFeign 会为该 Feign 客户端生成一个代理类。在正常情况下,这个代理类会将方法调用转发到实际的远程服务。

然而,当远程服务调用出现问题时(例如,抛出了FeignException或其他运行时异常),代理类会转而调用fallback属性指定的类中的相应方法。这个fallback类需要实现与@FeignClient接口相同的方法,并且在这些方法中提供备用的响应或逻辑。

fallbackFactory 属性

fallbackFactory属性用于提供一种更灵活的服务降级机制。当被调用的远程服务出现故障(如网络问题、服务不可用等)或者响应超时时,通过fallbackFactory可以返回一个备用的响应或者执行一些替代的逻辑,从而增强系统的容错性。

例如,在一个电商系统中,订单服务需要调用用户服务来获取用户信息。如果用户服务出现故障,使用fallbackFactory可以返回一个默认的用户信息(如一个包含默认值的用户对象),或者记录错误日志并返回一个友好的错误提示,而不是让订单服务因为无法获取用户信息而崩溃。

path 属性

@FeignClient注解中的path属性用于为 Feign 客户端接口中的所有方法指定一个公共的路径前缀。它提供了一种简洁的方式来定义一组相关的远程服务端点,使得代码更加清晰和易于维护。

例如,假设你正在构建一个微服务应用,其中有一个用户服务,它包含多个与用户相关的端点,如/users/{id}用于获取用户信息、/users/create用于创建用户等。通过设置path属性,可以将这些端点统一在一个公共的路径下。

以下是一个@FeignClient注解使用path属性的示例:

@FeignClient(name = "user-service", path = "/users")
public interface UserClient {

  @GetMapping("/{id}")
  User getUserById(@PathVariable("id") Long id);
  
  @PostMapping("/create")
  void createUser(@RequestBody User user);

}

primary 属性

默认情况下,如果没有设置primary属性,Spring 会根据其内部的规则来确定一个主要的@FeignClient。通常情况下,最先被扫描和加载的符合条件的@FeignClient可能会被当作主要的。

但是,当你设置了primary属性为true(这是默认值)时,就明确告诉 Spring 这个@FeignClient是主要的候选者,在自动装配时会优先考虑它。如果设置为false,那么这个@FeignClient就不会是首选的,除非没有其他primary为true的同类型@FeignClient。

支持向 Feign 客户端提供URL的方法

你可以通过以下任何一种方式向Feign客户端提供一个URL:

(1)URL 是在 @FeignClient 注解中提供的,例如:

@FeignClient(name="testClient", url="http://localhost:8081")

URL 是从注解的 url 属性中解析出来的,没有负载均衡。

(2)URL 是在 @FeignClient 注解和配置属性中提供的,例如:

@FeignClient(name="testClient", url="http://localhost:8081")

定义在 application.yml 中的属性:

spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081

URL 是从注解的 url 属性中解析出来的,没有负载均衡。在配置属性中提供的 URL 仍未使用。

(3)URL 没有在 @FeignClient 注解中提供,而是在配置属性中提供,例如:

@FeignClient(name="testClient")

定义在 application.yml 中的属性:

spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081

URL 从配置属性中解析,没有负载均衡。如果 spring.cloud.openfeign.client.refresh-enabled=true,那么配置属性中定义的 URL 可以被刷新。

(4)在 @FeignClient 注解中和配置属性中都没有提供这个URL,例如:

@FeignClient(name="testClient")

URL 是从注解的 name 属性中解析出来的,具有负载均衡性。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号