Feign é uma biblioteca de cliente HTTP declarativa e baseada em templates, projetada para simplificar a criação de clientes HTTP em Java. Ela permite implementar clientes HTTP utilizando ferramentas como Jersey e CXF para construir serviços REST ou SOAP. Além disso, o Feign suporta a implementação de clientes HTTP personalizados com base em kits de ferramentas HTTP comuns, como OkHTTP e HTTPComponents.
O Feign utiliza a anotação @EnableFeignClients para modularizar as requisições HTTP, simplificando drasticamente o processo de solicitação. A funcionalidade de replay de requisições torna os testes unitários HTTP mais convenientes. Em cenários com baixa concorrência, pode ser utilizado como uma solução de RPC para desacoplamento entre serviços, frequentemente integrado com componentes de descoberta de serviço.
Ele integra Ribbon e Hystrix, eliminando a necessidade de uso explícito desses componentes. Ao fornecer templates de requisição HTTP, basta definir interfaces simples com anotações para configurar parâmetros, formato e endereço das requisições. O Feign atua como um proxy completo para requisições HTTP, permitindo chamadas de serviço através de invocações de métodos convencionais.
Características do Feign:
- Suporte a anotações modulares, incluindo Feign e JAX-RS
- Compatibilidade com encoders e decoders HTTP intercambiáveis
- Integração com Hystrix e seus fallbacks
- Suporte a balanceamento de carga via Ribbon
- Compressão de requisições e respostas HTTP
Anotações Comuns do Open Feign
@FeignClient
A anotação @FeignClient é definida conforme o código abaixo:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {
@AliasFor("name")
String value() default "";
String contextId() default "";
@AliasFor("value")
String name() default "";
String[] qualifiers() default {};
String url() default "";
boolean dismiss404() default false;
Class>[] configuration() default {};
Class> fallback() default void.class;
Class> fallbackFactory() default void.class;
String path() default "";
boolean primary() default true;
}
Explicação dos parâmetros:
- value: Especifica o nome único do serviço, correspondente ao nome registrado em centros de descoberta como Nacos, Consul ou Eureka. Equivalente ao parâmetro
name. - contextId: Define um identificador de contexto alternativo.
- qualifiers: Especifica qualificadores para injeção de dependência.
- url: Indica uma URL codificada diretamente.
- dismiss404: Controla se erros 404 são tratados como exceções ou decodificados.
- configuration: Aceita um array de classes de configuração, com base na classe padrão
FeignClientsConfiguration. - fallback: Especifica uma classee de fallback para Hystrix.
- fallbackFactory: Define uma fábrica de fallbacks.
- path: Configura um caminho base para as requisições.
- primary: Indica se o cliente é o primário em caso de múltiplos beans.
Configuração
A classe FeignClientsConfiguration injeta beans relacionados ao Feign, como FeignRetryer e FeignLoggerFactory. Componentes como Decoder, Encoder e Contract são configurados automaticamente se não houver beans definidos, usando implementações padrão como ResponseEntity Decoder e SpringEncoder.
Logs
O nível de log é definido pela enumeração Logger.Level no código-fonte:
public abstract class Logger {
public static enum Level {
NONE,
BASIC,
HEADERS,
FULL;
}
}
Níveis disponíveis:
- NONE: Nenhum registro (padrão).
- BASIC: Registra método da requisição, URL, código de status e tempo de execução.
- HEADERS: Inclui informações de cabeçalho além do nível BASIC.
- FULL: Registra detalhes completos, incluindo corpo e metadados.
Para alterar o nível de log, crie uma classe de configuração:
@Configuration
public class LogSettings {
@Bean
Logger.Level nivelDeLog() {
return Logger.Level.FULL;
}
}
Referencie essa classe no atributo configuration da anotação @FeignClient.
Princípios de Funcionamento
O OpenFeign utiliza proxies dinâmicos para encapsular chamadas remotas. Durante a inicialização do projeto, o framework escaneia pacotes para carregar interfaces anotadas com @FeignClient, gerando objetos proxy dinâmicos. Esses proxies são adicionados ao contexto Spring e injetados em serviços locais.
Quando um serviço local invoca um método do proxy, a chamada é interceptada e traduzida em uma requisição HTTP remota. O processo envolve:
- Carregamento: A anotação
@EnableFeignClientsinicia o carregamento viaFeignClientsRegistrar. - Varredura:
FeignClientsRegistrarescaneia interfaces FeignClient e criaFeignClientFactoryBeanpara análise. - Análise:
FeignClientFactoryBeaninterpreta anotações e configurações de fallback. - Proxy Dinâmico:
ReflectiveFeignconstrói o proxy, analisando anotações de métodos para criar metadados e handlers (MethodHandler). Esses handlers encapsulam lógica de tradução de chamadas em requisições HTTP, com suporte a contratos comoSpringMvcContractpara interpretação de anotações Spring MVC.
Prática
Upload de MultipartFile via Feign
Considere um serviço de arquivos (FS) com a seguinte interface de controller:
@PostMapping(value = "/envioArquivoPrivado")
public Resposta<arquivovo> envioArquivoPrivado(@RequestPart(value = "arquivo") MultipartFile arquivo) {
}
</arquivovo>
Para consumir essa interface em outro microsserviço, crie um módulo cliente com uma interface Feign:
@FeignClient(value = "provedor-fs", configuration = ConfiguracaoMultipart.class)
public interface ServicoArquivoRemoto {
@PostMapping(value = "/fs/envioArquivo")
Resposta<arquivovo> enviarArquivo(@RequestPart(value = "arquivo") MultipartFile arquivo);
}
</arquivovo>
Ao utilizar essa interface, pode ocorrer um erro de serialização. Para resolver, adicione a dependência:
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
</dependency>
E configure o encoder:
@Configuration
public class ConfiguracaoMultipart {
@Bean
@Primary
@Scope("prototype")
public Encoder encoderMultipart() {
return new SpringFormEncoder();
}
}
Tratamento de Erro 404
Considere um cenário onde um serviço cliente chama uma interface remota:
@FeignClient(name = "provedor-pagamento", configuration = ConfiguracaoFeign.class)
public interface ServicoPagamentoRemoto {
@RequestMapping(value = {"/pagamento/consultar/{canal}/{usuarioId}"}, method = {RequestMethod.GET})
Boolean consultaPagamento(@PathVariable(name = "canal") String canal, @PathVariable(value = "usuarioId") String usuarioId);
}
Se um parâmetro como canal não for fornecido, a requisição pode gerar um erro 404. Ferramentas de rastreamento como SkyWalking podem auxiliar na depuração, identificando parâmetros faltantes na cadeia de chamadas.
Para evitar isso, recomenda-se o uso de @RequestParam em vez de @PathVariable em definições de interfaces, pois @PathVariable pode levar a URLs malformadas e dificultar a aálise de métricas de desempenho em microsserviços.
Questões de Deserialização
Inconsistências entre tipos de retorno em controllers e interfaces Feign podem causar erros de deserialização. Por exemplo, se um controller retorna Resposta<Boolean> eqnuanto a interface Feign espera Boolean, isso pode gerar exceções. É crucial garantir alinhamento nos tipos de dados entre definições.