Глубокий разговор с аннотациями @FeignClient той ночью

Spring Cloud

бред какой то

Той ночью у меня был подробный обмен аннотациями @FeignClient, круто!

В основном в технической группе я видел, как некоторые студенты задавали связанные вопросы, такие как: Что такое contextId? Будут ли несколько клиентов с одинаковым именем сообщать об ошибке?

Затем я чувствую необходимость написать статью, чтобы рассказать об использовании @FeignClient, выкроить время из своего плотного графика, написать статью непросто, не забудьте поставить лайк.

Формальный

Основное введение в Feign

Прежде всего, базовая популяризация, я боюсь, что некоторые студенты не были ознакомлены со Spring Cloud. Feign — это клиент REST с открытым исходным кодом от Netflix.Определив интерфейс и используя аннотации для описания информации об интерфейсе, вы можете инициировать вызов интерфейса.

Адрес гитхаба:GitHub.com/open feign/…

Ниже приведен базовый пример использования, приведенный на домашней странице GitHub.В этом примере Feign используется для вызова интерфейса GitHub.

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

public static class Contributor {
  String login;
  int contributions;
}

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  
    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Введение в Spring Cloud OpenFeign

Spring Cloud OpenFeign — это продукт команды Spring Cloud, объединяющий родной Feign в Spring Cloud. В приведенном выше примере использования нативного Feign все используемые аннотации встроены в Feign, но мы в основном основаны на аннотациях Spring MVC в разработке, которые не очень удобно вызывать. Таким образом, Spring Cloud OpenFeign расширяет поддержку аннотаций Spring MVC, а также интегрирует Ribbon и Eureka для обеспечения реализации HTTP-клиента с балансировкой нагрузки.

Адрес гитхаба:GitHub.com/spring - уродливо...

Официально предоставленные примеры использования:

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

Введение в использование аннотаций FeignClient

value, name

Роль значения и имени одинакова. Если URL-адрес не настроен, настроенное значение будет использоваться в качестве имени службы для обнаружения служб. Вместо этого это просто имя.

serviceId

serviceId устарел, вы можете использовать name напрямую.

contextId

Например, у нас есть пользовательский сервис, но в пользовательском сервисе много интерфейсов, мы не хотим определять все вызывающие интерфейсы в одном классе, например:

Client 1

@FeignClient(name = "optimization-user")
public interface UserRemoteClient {
	@GetMapping("/user/get")
	public 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);
}

В этом случае запуск сообщит об ошибке, поскольку имя бина конфликтует, конкретная ошибка выглядит следующим образом:

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

Решением может быть добавление следующей конфигурации, роль которой состоит в том, чтобы разрешить BeanDefinition с тем же beanName.

spring.main.allow-bean-definition-overriding=true

Другое решение — вручную указать разные contextId для каждого клиента, чтобы не было конфликтов.

Решение после конфликта имени бина приведено выше.Разберем роль contextId в Feign Client.При регистрации Feign Client Configuration требуется имя.Имя получается через метод getClientName:

String name = getClientName(attributes);

registerClientConfiguration(registry, name,
attributes.get("configuration"));
private String getClientName(Map<String, Object> 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.Если конфигурации нет, за значением будет следовать имя и, наконец, serviceId. По умолчанию конфигурация отсутствует, и при наличии у службы нескольких клиентов Feign будет сообщено об ошибке.

Второй эффект заключается в том, что при регистрации FeignClient contextId будет использоваться как часть псевдонима клиента.Если квалификатор настроен, квалификатор будет использоваться в качестве псевдонима.

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map<String, Object> 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);
  }

url

URL-адрес используется для настройки адреса указанной службы, что эквивалентно запросу этой службы напрямую, без выбора службы на ленте. Можно использовать такие сценарии, как отладка.

использовать список

@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, тогда будет выполняться декодирование декодером, в противном случае будет выдано исключение.

Декодирование вернет вам фиксированный формат данных:

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

Если выдается исключение, это информация об исключении.Если настроен откат, будет выполнена логика отката:

图片

configuration

Конфигурация предназначена для настройки класса конфигурации Feign.В классе конфигурации вы можете настроить Feign Encoder, Decoder, LogLevel, Contract и т. д.

определение конфигурации

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

Определите отказоустойчивый класс обработки, то есть резервную логику.Запасной класс должен реализовывать интерфейс Feign Client и не может знать информацию об исключении предохранителя.

резервное определение

@Component
public 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

Это также отказоустойчивый процесс, и вы можете узнать ненормальную информацию о предохранителе.

резервное определение фабрики

@Component
public class UserRemoteClientFallbackFactory implements FallbackFactory<UserRemoteClient> {
	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, которая по умолчанию имеет значение true.Для этой официальной настройки есть причина. Когда наш Feign реализует резервный вариант, это означает, что Feign Client имеет несколько идентичных bean-компонентов в контейнере Spring.Когда мы используем @Autowired для внедрения, мы не знаем, какой из них внедрить, поэтому нам нужно установить высокий приоритет Да, @ Первичная аннотация делает именно это.

qualifier

Квалификатор соответствует аннотации @Qualifier. Сценарий использования имеет очень слабую связь с основным выше. В общих сценариях вы можете напрямую внедрить @Autowired напрямую.

Если наш Feign Client имеет резервную реализацию, аннотация @FeignClient по умолчанию имеет значение primary=true, что означает, что у нас нет проблем с использованием @Autowired инъекции, и ваш Feign Client будет внедрен первым.

Если вы случайно установите для параметра primary значение false, будет сообщено об ошибке, когда вы вводите напрямую с помощью @Autowired, и вы не знаете, какой объект вводить.

Решение очевидное, вы можете установить для первичного значение true, если по какой-то особой причине вам нужно удалить настройку primary=true, как мы будем вводить в этом случае, мы можем настроить квалификатор, а затем использовать аннотацию @ The Qualifier вводится, как показано ниже:

Определение ложного клиента

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

Внедрение фиктивного клиента

@Autowired
@Qualifier("userRemoteClient")
private UserRemoteClient userRemoteClient;

Если вам интересно, вы можете подписаться на мой публичный аккаунт в WeChat.обезьяний мир, читать больше технических статей в первый раз. У меня также есть открытый исходный код на моем GitHub.github.com/yinjihuan