Feign ist ein Java-zu-HTTP-Client-Binder, der von Retrofit, JAXRS-2.0 und WebSocket inspiriert ist. Feigns erstes Ziel bestand darin, die Komplexität der einheitlichen Bindung von Denominator an HTTP-APIs unabhängig von ReSTfulness zu reduzieren.
Feign verwendet Tools wie Jersey und CXF, um Java-Clients für ReST- oder SOAP-Dienste zu schreiben. Darüber hinaus können Sie mit Feign Ihren eigenen Code auf Basis von http-Bibliotheken wie Apache HC schreiben. Feign verbindet Ihren Code mit minimalem Overhead und Code über anpassbare Decoder und Fehlerbehandlung mit http-APIs, die in jede textbasierte http-API geschrieben werden können.
Feign verarbeitet Anmerkungen in einer Vorlagenanfrage. Argumente werden vor der Ausgabe auf einfache Weise auf diese Vorlagen angewendet. Obwohl sich Feign auf die Unterstützung textbasierter APIs beschränkt, vereinfacht es Systemaspekte wie die Wiedergabe von Anfragen erheblich. Darüber hinaus macht es Feign mit diesem Wissen ganz einfach, Ihre Konvertierungen einem Unit-Test zu unterziehen.
Feign 10.x und höher basieren auf Java 8 und sollten auf Java 9, 10 und 11 funktionieren. Für diejenigen, die JDK 6-Kompatibilität benötigen, verwenden Sie bitte Feign 9.x
Dies ist eine Karte mit den aktuellen Hauptfunktionen von feign:
API- Clients einfacher machen
Logger
API-RefaktorLogger
-API, um sie besser an Frameworks wie SLF4J anzupassen und ein gemeinsames mentales Modell für die Protokollierung in Feign bereitzustellen. Dieses Modell wird durchgehend von Feign selbst verwendet und bietet klarere Anweisungen für die Verwendung des Logger
.Retry
den API-RefactorRetry
API, um vom Benutzer bereitgestellte Bedingungen zu unterstützen und eine bessere Kontrolle über Backoff-Richtlinien zu ermöglichen. Dies kann zu nicht abwärtskompatiblen Breaking Changes führen CompletableFuture
Future
Verkettung und Executor-Verwaltung für den Anforderungs-/Antwort-Lebenszyklus. Für die Implementierung sind nicht abwärtskompatible Breaking Changes erforderlich . Diese Funktion ist jedoch erforderlich, bevor eine reaktive Ausführung in Betracht gezogen werden kann.java.util.concurrent.Flow
verwendet.Die Feign-Bibliothek ist bei Maven Central erhältlich.
< dependency >
< groupId >io.github.openfeign</ groupId >
< artifactId >feign-core</ artifactId >
< version >??feign.version??</ version >
</ dependency >
Die Verwendung sieht normalerweise so aus, eine Adaption des kanonischen Retrofit-Beispiels.
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 + ")" );
}
}
}
Feign-Annotationen definieren den Contract
zwischen der Schnittstelle und der Funktionsweise des zugrunde liegenden Clients. Der Standardvertrag von Feign definiert die folgenden Anmerkungen:
Anmerkung | Schnittstellenziel | Verwendung |
---|---|---|
@RequestLine | Verfahren | Definiert HttpMethod und UriTemplate für die Anfrage. Expressions , in geschweifte Klammern eingeschlossene Werte {expression} werden mithilfe ihrer entsprechenden @Param annotierten Parameter aufgelöst. |
@Param | Parameter | Definiert eine Vorlagenvariable, deren Wert zum Auflösen des entsprechenden Expression anhand des als Anmerkungswert bereitgestellten Namens verwendet wird. Wenn der Wert fehlt, wird versucht, den Namen aus dem Parameternamen der Bytecode-Methode abzurufen (wenn der Code mit dem Flag -parameters kompiliert wurde). |
@Headers | Methode, Typ | Definiert ein HeaderTemplate ; eine Variation eines UriTemplate . das @Param -annotierte Werte verwendet, um die entsprechenden Expressions aufzulösen. Bei Verwendung auf einem Type wird die Vorlage auf jede Anfrage angewendet. Bei Verwendung für eine Method gilt die Vorlage nur für die mit Anmerkungen versehene Methode. |
@QueryMap | Parameter | Definiert eine Map von Name-Wert-Paaren (POJO), die zu einer Abfragezeichenfolge erweitert werden sollen. |
@HeaderMap | Parameter | Definiert eine Map von Name-Wert-Paaren zur Erweiterung in Http Headers |
@Body | Verfahren | Definiert ein Template , ähnlich einem UriTemplate und HeaderTemplate , das @Param annotierte Werte verwendet, um die entsprechenden Expressions aufzulösen. |
Überschreiben der Anforderungszeile
Wenn es notwendig ist, eine Anfrage an einen anderen Host als den zu richten, der bei der Erstellung des Feign-Clients angegeben wurde, oder Sie für jede Anfrage einen Zielhost angeben möchten, fügen Sie einen
java.net.URI
-Parameter ein und Feign verwendet diesen Wert als Anforderungsziel.@ RequestLine ( "POST /repos/{owner}/{repo}/issues" ) void createIssue ( URI host , Issue issue , @ Param ( "owner" ) String owner , @ Param ( "repo" ) String repo );
Feign Expressions
stellen einfache Zeichenfolgenausdrücke (Ebene 1) dar, wie in der URI-Vorlage – RFC 6570 definiert. Expressions
werden mit ihren entsprechenden Param
annotierten Methodenparametern erweitert.
Beispiel
public interface GitHub {
@ RequestLine ( "GET /repos/{owner}/{repo}/contributors" )
List < Contributor > contributors ( @ Param ( "owner" ) String owner , @ Param ( "repo" ) String repository );
class Contributor {
String login ;
int contributions ;
}
}
public class MyApp {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. decoder ( new GsonDecoder ())
. target ( GitHub . class , "https://api.github.com" );
/* The owner and repository parameters will be used to expand the owner and repo expressions
* defined in the RequestLine.
*
* the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors
*/
github . contributors ( "OpenFeign" , "feign" );
}
}
Ausdrücke müssen in geschweifte Klammern {}
eingeschlossen werden und können reguläre Ausdrucksmuster enthalten, getrennt durch einen Doppelpunkt :
um aufgelöste Werte einzuschränken. owner
muss alphabetisch sein. {owner:[a-zA-Z]*}
RequestLine
und QueryMap
Vorlagen folgen der URI-Vorlage – RFC 6570-Spezifikation für Level-1-Vorlagen, die Folgendes festlegt:
@Param
Annotation encoded
markiert sind.Wir bieten außerdem eingeschränkte Unterstützung für Level 3, Pfadstilausdrücke, mit den folgenden Einschränkungen:
Beispiele:
{;who} ;who=fred
{;half} ;half=50%25
{;empty} ;empty
{;list} ;list=red;list=green;list=blue
{;map} ;semi=%3B;dot=.;comma=%2C
public interface MatrixService {
@ RequestLine ( "GET /repos{;owners}" )
List < Contributor > contributors ( @ Param ( "owners" ) List < String > owners );
class Contributor {
String login ;
int contributions ;
}
}
Wenn owners
im obigen Beispiel als Matt, Jeff, Susan
definiert sind, wird die URI zu /repos;owners=Matt;owners=Jeff;owners=Susan
erweitert
Weitere Informationen finden Sie in RFC 6570, Abschnitt 3.2.7
Undefinierte Ausdrücke sind Ausdrücke, bei denen der Wert für den Ausdruck eine explizite null
ist oder kein Wert bereitgestellt wird. Gemäß URI-Vorlage – RFC 6570 ist es möglich, einen leeren Wert für einen Ausdruck bereitzustellen. Wenn Feign einen Ausdruck auflöst, stellt es zunächst fest, ob der Wert definiert ist. Wenn dies der Fall ist, bleibt der Abfrageparameter bestehen. Wenn der Ausdruck undefiniert ist, wird der Abfrageparameter entfernt. Eine vollständige Aufschlüsselung finden Sie weiter unten.
Leere Zeichenfolge
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
parameters . put ( "param" , "" );
this . demoClient . test ( parameters );
}
Ergebnis
http://localhost:8080/test?param=
Fehlen
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
this . demoClient . test ( parameters );
}
Ergebnis
http://localhost:8080/test
Undefiniert
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
parameters . put ( "param" , null );
this . demoClient . test ( parameters );
}
Ergebnis
http://localhost:8080/test
Weitere Beispiele finden Sie unter Erweiterte Verwendung.
Was ist mit Schrägstrichen?
/
@RequestLine-Vorlagen kodieren standardmäßig keine Schrägstriche
/
Zeichen. Um dieses Verhalten zu ändern, legen Sie diedecodeSlash
Eigenschaft der@RequestLine
auffalse
fest.
Was ist mit Plus?
+
Gemäß der URI-Spezifikation ist ein
+
-Zeichen sowohl im Pfad- als auch im Abfragesegment eines URI zulässig, die Handhabung des Symbols in der Abfrage kann jedoch inkonsistent sein. In einigen älteren Systemen entspricht das+
dem a-Leerzeichen. Feign folgt dem Ansatz moderner Systeme, bei denen ein+
-Symbol kein Leerzeichen darstellen sollte und explizit als%2B
codiert wird, wenn es in einer Abfragezeichenfolge gefunden wird.Wenn Sie
+
als Leerzeichen verwenden möchten, verwenden Sie das LiteralZeichen oder kodieren Sie den Wert direkt als
%20
Die @Param
-Annotation verfügt über einen optionalen Eigenschaften expander
der eine vollständige Kontrolle über die Erweiterung der einzelnen Parameter ermöglicht. Die expander
-Eigenschaft muss auf eine Klasse verweisen, die die Expander
Schnittstelle implementiert:
public interface Expander {
String expand ( Object value );
}
Das Ergebnis dieser Methode folgt den gleichen oben genannten Regeln. Wenn das Ergebnis null
oder eine leere Zeichenfolge ist, wird der Wert weggelassen. Wenn der Wert nicht PCT-codiert ist, ist dies der Fall. Weitere Beispiele finden Sie unter Benutzerdefinierte @Param-Erweiterung.
Headers
und HeaderMap
-Vorlagen folgen denselben Regeln wie die Anforderungsparametererweiterung mit den folgenden Änderungen:
Beispiele finden Sie unter Kopfzeilen.
Ein Hinweis zu
@Param
-Parametern und ihren Namen :Alle Ausdrücke mit demselben Namen werden unabhängig von ihrer Position in
@RequestLine
,@QueryMap
,@BodyTemplate
oder@Headers
in denselben Wert aufgelöst. Im folgenden Beispiel wird der Wert voncontentType
verwendet, um sowohl den Header- als auch den Pfadausdruck aufzulösen:public interface ContentService { @ RequestLine ( "GET /api/documents/{contentType}" ) @ Headers ( "Accept: {contentType}" ) String getDocumentByType ( @ Param ( "contentType" ) String type ); }Berücksichtigen Sie dies bei der Gestaltung Ihrer Schnittstellen.
Body
-Vorlagen folgen denselben Regeln wie die Anforderungsparametererweiterung mit den folgenden Änderungen:
Encoder
geleitet, bevor er im Anforderungstext platziert wird.Content-Type
Header angegeben werden. Beispiele finden Sie unter Body-Vorlagen. Feign verfügt über mehrere Aspekte, die angepasst werden können.
In einfachen Fällen können Sie Feign.builder()
verwenden, um eine API-Schnittstelle mit Ihren benutzerdefinierten Komponenten zu erstellen.
Für die Anforderungseinstellung können Sie options(Request.Options options)
für target()
verwenden, um connectTimeout, connectTimeoutUnit, readTimeout, readTimeoutUnit und followRedirects festzulegen.
Zum Beispiel:
interface Bank {
@ RequestLine ( "POST /account/{id}" )
Account getAccountInfo ( @ Param ( "id" ) String id );
}
public class BankService {
public static void main ( String [] args ) {
Bank bank = Feign . builder ()
. decoder ( new AccountDecoder ())
. options ( new Request . Options ( 10 , TimeUnit . SECONDS , 60 , TimeUnit . SECONDS , true ))
. target ( Bank . class , "https://api.examplebank.com" );
}
}
Feign kann mehrere API-Schnittstellen erstellen. Diese sind als Target<T>
(Standard: HardCodedTarget<T>
) definiert und ermöglichen eine dynamische Erkennung und Dekoration von Anforderungen vor der Ausführung.
Das folgende Muster könnte beispielsweise jede Anfrage mit der aktuellen URL und dem Authentifizierungstoken vom Identitätsdienst versehen.
public class CloudService {
public static void main ( String [] args ) {
CloudDNS cloudDNS = Feign . builder ()
. target ( new CloudIdentityTarget < CloudDNS >( user , apiKey ));
}
class CloudIdentityTarget extends Target < CloudDNS > {
/* implementation of a Target */
}
}
Feign enthält Beispiel-GitHub- und Wikipedia-Clients. Das Nennerprojekt kann für Feign in der Praxis auch abgekratzt werden. Schauen Sie sich insbesondere den Beispiel-Daemon an.
Feign beabsichtigt, gut mit anderen Open-Source-Tools zusammenzuarbeiten. Module können gerne in Ihre Lieblingsprojekte integriert werden!
Gson enthält einen Encoder und Decoder, den Sie mit einer JSON-API verwenden können.
Fügen Sie GsonEncoder
und/oder GsonDecoder
wie folgt zu Ihrem Feign.Builder
hinzu:
public class Example {
public static void main ( String [] args ) {
GsonCodec codec = new GsonCodec ();
GitHub github = Feign . builder ()
. encoder ( new GsonEncoder ())
. decoder ( new GsonDecoder ())
. target ( GitHub . class , "https://api.github.com" );
}
}
Jackson enthält einen Encoder und Decoder, die Sie mit einer JSON-API verwenden können.
Fügen Sie JacksonEncoder
und/oder JacksonDecoder
wie folgt zu Ihrem Feign.Builder
hinzu:
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. encoder ( new JacksonEncoder ())
. decoder ( new JacksonDecoder ())
. target ( GitHub . class , "https://api.github.com" );
}
}
Für den leichteren Jackson Jr verwenden Sie JacksonJrEncoder
und JacksonJrDecoder
aus dem Jackson Jr-Modul.
Moshi enthält einen Encoder und Decoder, den Sie mit einer JSON-API verwenden können. Fügen Sie MoshiEncoder
und/oder MoshiDecoder
wie folgt zu Ihrem Feign.Builder
hinzu:
GitHub github = Feign . builder ()
. encoder ( new MoshiEncoder ())
. decoder ( new MoshiDecoder ())
. target ( GitHub . class , "https://api.github.com" );
Mit SaxDecoder können Sie XML auf eine Weise dekodieren, die mit normalen JVM- und auch Android-Umgebungen kompatibel ist.
Hier ist ein Beispiel für die Konfiguration der Sax-Antwortanalyse:
public class Example {
public static void main ( String [] args ) {
Api api = Feign . builder ()
. decoder ( SAXDecoder . builder ()
. registerContentHandler ( UserIdHandler . class )
. build ())
. target ( Api . class , "https://apihost" );
}
}
JAXB enthält einen Encoder und Decoder, den Sie mit einer XML-API verwenden können.
Fügen Sie JAXBEncoder
und/oder JAXBDecoder
wie folgt zu Ihrem Feign.Builder
hinzu:
public class Example {
public static void main ( String [] args ) {
Api api = Feign . builder ()
. encoder ( new JAXBEncoder ())
. decoder ( new JAXBDecoder ())
. target ( Api . class , "https://apihost" );
}
}
SOAP enthält einen Encoder und Decoder, den Sie mit einer XML-API verwenden können.
Dieses Modul fügt Unterstützung für die Kodierung und Dekodierung von SOAP-Body-Objekten über JAXB und SOAPMessage hinzu. Es bietet auch SOAPFault-Dekodierungsfunktionen, indem es diese in die ursprüngliche javax.xml.ws.soap.SOAPFaultException
einschließt, sodass Sie nur SOAPFaultException
abfangen müssen, um SOAPFault zu verarbeiten.
Fügen Sie SOAPEncoder
und/oder SOAPDecoder
wie folgt zu Ihrem Feign.Builder
hinzu:
public class Example {
public static void main ( String [] args ) {
Api api = Feign . builder ()
. encoder ( new SOAPEncoder ( jaxbFactory ))
. decoder ( new SOAPDecoder ( jaxbFactory ))
. errorDecoder ( new SOAPErrorDecoder ())
. target ( MyApi . class , "http://api" );
}
}
Hinweis: Möglicherweise müssen Sie auch SOAPErrorDecoder
hinzufügen, wenn SOAP-Fehler als Antwort mit Fehler-HTTP-Codes (4xx, 5xx, ...) zurückgegeben werden.
fastjson2 enthält einen Encoder und Decoder, den Sie mit einer JSON-API verwenden können.
Fügen Sie Fastjson2Encoder
und/oder Fastjson2Decoder
wie folgt zu Ihrem Feign.Builder
hinzu:
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. encoder ( new Fastjson2Encoder ())
. decoder ( new Fastjson2Decoder ())
. target ( GitHub . class , "https://api.github.com" );
}
}
JAXRSContract überschreibt die Annotationsverarbeitung und verwendet stattdessen die von der JAX-RS-Spezifikation bereitgestellten Standardannotationen. Dies ist derzeit auf die 1.1-Spezifikation ausgerichtet.
Hier ist das obige Beispiel, das für die Verwendung von JAX-RS umgeschrieben wurde:
interface GitHub {
@ GET @ Path ( "/repos/{owner}/{repo}/contributors" )
List < Contributor > contributors ( @ PathParam ( "owner" ) String owner , @ PathParam ( "repo" ) String repo );
}
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. contract ( new JAXRSContract ())
. target ( GitHub . class , "https://api.github.com" );
}
}
OkHttpClient leitet Feigns HTTP-Anfragen an OkHttp weiter, was SPDY und eine bessere Netzwerkkontrolle ermöglicht.
Um OkHttp mit Feign zu verwenden, fügen Sie das OkHttp-Modul zu Ihrem Klassenpfad hinzu. Konfigurieren Sie dann Feign für die Verwendung von OkHttpClient:
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. client ( new OkHttpClient ())
. target ( GitHub . class , "https://api.github.com" );
}
}
RibbonClient überschreibt die URL-Auflösung des Feign-Clients und fügt intelligente Routing- und Ausfallsicherheitsfunktionen hinzu, die von Ribbon bereitgestellt werden.
Für die Integration müssen Sie den Namen Ihres Ribbon-Clients als Host-Teil der URL übergeben, zum Beispiel myAppProd
.
public class Example {
public static void main ( String [] args ) {
MyService api = Feign . builder ()
. client ( RibbonClient . create ())
. target ( MyService . class , "https://myAppProd" );
}
}
Http2Client leitet Feigns HTTP-Anfragen an den neuen Java11 HTTP/2-Client weiter, der HTTP/2 implementiert.
Um den neuen HTTP/2-Client mit Feign zu verwenden, verwenden Sie Java SDK 11. Konfigurieren Sie dann Feign für die Verwendung des Http2Client:
GitHub github = Feign . builder ()
. client ( new Http2Client ())
. target ( GitHub . class , "https://api.github.com" );
HystrixFeign konfiguriert die von Hystrix bereitgestellte Leistungsschalterunterstützung.
Um Hystrix mit Feign zu verwenden, fügen Sie das Hystrix-Modul zu Ihrem Klassenpfad hinzu. Dann verwenden Sie den HystrixFeign
-Builder:
public class Example {
public static void main ( String [] args ) {
MyService api = HystrixFeign . builder (). target ( MyService . class , "https://myAppProd" );
}
}
SLF4JModule ermöglicht die Weiterleitung der Protokollierung von Feign an SLF4J, sodass Sie problemlos ein Protokollierungs-Backend Ihrer Wahl (Logback, Log4J usw.) verwenden können.
Um SLF4J mit Feign zu verwenden, fügen Sie Ihrem Klassenpfad sowohl das SLF4J-Modul als auch eine SLF4J-Bindung Ihrer Wahl hinzu. Konfigurieren Sie dann Feign für die Verwendung des Slf4jLogger:
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. logger ( new Slf4jLogger ())
. logLevel ( Level . FULL )
. target ( GitHub . class , "https://api.github.com" );
}
}
Feign.builder()
können Sie zusätzliche Konfigurationen angeben, z. B. wie eine Antwort dekodiert werden soll.
Wenn Methoden in Ihrer Schnittstelle andere Typen als Response
, String
, byte[]
oder void
zurückgeben, müssen Sie einen nicht standardmäßigen Decoder
konfigurieren.
So konfigurieren Sie die JSON-Dekodierung (mit der Erweiterung feign-gson
):
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. decoder ( new GsonDecoder ())
. target ( GitHub . class , "https://api.github.com" );
}
}
Wenn Sie die Antwort vorverarbeiten müssen, bevor Sie sie an den Decoder übergeben, können Sie die Builder-Methode mapAndDecode
verwenden. Ein Beispiel für einen Anwendungsfall ist der Umgang mit einer API, die nur JSONP bereitstellt. Möglicherweise müssen Sie den JSONP entpacken, bevor Sie ihn an den JSON-Decoder Ihrer Wahl senden:
public class Example {
public static void main ( String [] args ) {
JsonpApi jsonpApi = Feign . builder ()
. mapAndDecode (( response , type ) -> jsopUnwrap ( response , type ), new GsonDecoder ())
. target ( JsonpApi . class , "https://some-jsonp-api.com" );
}
}
Wenn Methoden in Ihrer Schnittstelle den Typ Stream
zurückgeben, müssen Sie einen StreamDecoder
konfigurieren.
So konfigurieren Sie den Stream-Decoder ohne Delegierten-Decoder:
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. decoder ( StreamDecoder . create (( r , t ) -> {
BufferedReader bufferedReader = new BufferedReader ( r . body (). asReader ( UTF_8 ));
return bufferedReader . lines (). iterator ();
}))
. target ( GitHub . class , "https://api.github.com" );
}
}
So konfigurieren Sie den Stream-Decoder mit dem Delegierten-Decoder:
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. decoder ( StreamDecoder . create (( r , t ) -> {
BufferedReader bufferedReader = new BufferedReader ( r . body (). asReader ( UTF_8 ));
return bufferedReader . lines (). iterator ();
}, ( r , t ) -> "this is delegate decoder" ))
. target ( GitHub . class , "https://api.github.com" );
}
}
Der einfachste Weg, einen Anforderungstext an einen Server zu senden, besteht darin, eine POST
-Methode zu definieren, die über einen String
oder byte[]
Parameter ohne Anmerkungen verfügt. Sie müssen wahrscheinlich einen Content-Type
Header hinzufügen.
interface LoginClient {
@ RequestLine ( "POST /" )
@ Headers ( "Content-Type: application/json" )
void login ( String content );
}
public class Example {
public static void main ( String [] args ) {
client . login ( "{ " user_name " : " denominator " , " password " : " secret " }" );
}
}
Durch die Konfiguration eines Encoder
können Sie einen typsicheren Anforderungstext senden. Hier ist ein Beispiel mit der Erweiterung feign-gson
:
static class Credentials {
final String user_name ;
final String password ;
Credentials ( String user_name , String password ) {
this . user_name = user_name ;
this . password = password ;
}
}
interface LoginClient {
@ RequestLine ( "POST /" )
void login ( Credentials creds );
}
public class Example {
public static void main ( String [] args ) {
LoginClient client = Feign . builder ()
. encoder ( new GsonEncoder ())
. target ( LoginClient . class , "https://foo.com" );
client . login ( new Credentials ( "denominator" , "secret" ));
}
}
Die @Body
-Annotation gibt eine Vorlage an, die mithilfe von mit @Param
annotierten Parametern erweitert werden kann. Sie müssen wahrscheinlich einen Content-Type
Header hinzufügen.
interface LoginClient {
@ RequestLine ( "POST /" )
@ Headers ( "Content-Type: application/xml" )
@ Body ( "<login " user_name " = " {user_name} " " password " = " {password} " />" )
void xml ( @ Param ( "user_name" ) String user , @ Param ( "password" ) String password );
@ RequestLine ( "POST /" )
@ Headers ( "Content-Type: application/json" )
// json curly braces must be escaped!
@ Body ( "%7B " user_name " : " {user_name} " , " password " : " {password} " %7D" )
void json ( @ Param ( "user_name" ) String user , @ Param ( "password" ) String password );
}
public class Example {
public static void main ( String [] args ) {
client . xml ( "denominator" , "secret" ); // <login "user_name"="denominator" "password"="secret"/>
client . json ( "denominator" , "secret" ); // {"user_name": "denominator", "password": "secret"}
}
}
Feign unterstützt Einstellungsheader für Anfragen je nach Anwendungsfall entweder als Teil der API oder als Teil des Clients.
In Fällen, in denen für bestimmte Schnittstellen oder Aufrufe immer bestimmte Header-Werte festgelegt werden sollen, ist es sinnvoll, Header als Teil der API zu definieren.
Statische Header können mithilfe der Annotation @Headers
auf einer API-Schnittstelle oder -Methode festgelegt werden.
@ Headers ( "Accept: application/json" )
interface BaseApi < V > {
@ Headers ( "Content-Type: application/json" )
@ RequestLine ( "PUT /api/{key}" )
void put ( @ Param ( "key" ) String key , V value );
}
Methoden können mithilfe der Variablenerweiterung in @Headers
dynamische Inhalte für statische Header angeben.
public interface Api {
@ RequestLine ( "POST /" )
@ Headers ( "X-Ping: {token}" )
void post ( @ Param ( "token" ) String token );
}
In Fällen, in denen sowohl die Header-Feldschlüssel als auch die Werte dynamisch sind und der Bereich möglicher Schlüssel nicht im Voraus bekannt ist und zwischen verschiedenen Methodenaufrufen in derselben API/dem gleichen Client variieren kann (z. B. benutzerdefinierte Metadaten-Headerfelder wie „x-amz- meta-*“ oder „x-goog-meta-*“) kann ein Map-Parameter mit HeaderMap
annotiert werden, um eine Abfrage zu erstellen, die den Inhalt der Map als Header-Parameter verwendet.
public interface Api {
@ RequestLine ( "POST /" )
void post ( @ HeaderMap Map < String , Object > headerMap );
}
Diese Ansätze geben Header-Einträge als Teil der API an und erfordern keine Anpassungen beim Erstellen des Feign-Clients.
Um Header für jede Anforderungsmethode auf einem Ziel anzupassen, kann ein RequestInterceptor verwendet werden. RequestInterceptors können von allen Target-Instanzen gemeinsam genutzt werden und sollen Thread-sicher sein. RequestInterceptors werden auf alle Anforderungsmethoden auf einem Ziel angewendet.
Wenn Sie eine individuelle Anpassung pro Methode benötigen, ist ein benutzerdefiniertes Ziel erforderlich, da ein RequestInterceptor keinen Zugriff auf die Metadaten der aktuellen Methode hat.
Ein Beispiel für das Festlegen von Headern mithilfe eines RequestInterceptor
finden Sie im Abschnitt Request Interceptors
.
Header können als Teil eines benutzerdefinierten Target
festgelegt werden.
static class DynamicAuthTokenTarget < T > implements Target < T > {
public DynamicAuthTokenTarget ( Class < T > clazz ,
UrlAndTokenProvider provider ,
ThreadLocal < String > requestIdProvider );
@ Override
public Request apply ( RequestTemplate input ) {
TokenIdAndPublicURL urlAndToken = provider . get ();
if ( input . url (). indexOf ( "http" ) != 0 ) {
input . insert ( 0 , urlAndToken . publicURL );
}
input . header ( "X-Auth-Token" , urlAndToken . tokenId );
input . header ( "X-Request-ID" , requestIdProvider . get ());
return input . request ();
}
}
public class Example {
public static void main ( String [] args ) {
Bank bank = Feign . builder ()
. target ( new DynamicAuthTokenTarget ( Bank . class , provider , requestIdProvider ));
}
}
Diese Ansätze hängen davon ab, dass der benutzerdefinierte RequestInterceptor
oder das benutzerdefinierte Target
beim Erstellen auf dem Feign-Client festgelegt wird, und können als Möglichkeit verwendet werden, Header für alle API-Aufrufe auf Client-Basis festzulegen. Dies kann nützlich sein, um beispielsweise ein Authentifizierungstoken im Header aller API-Anfragen pro Client festzulegen. Die Methoden werden ausgeführt, wenn der API-Aufruf für den Thread erfolgt, der den API-Aufruf aufruft. Dadurch können die Header zum Zeitpunkt des Aufrufs dynamisch und kontextspezifisch festgelegt werden – beispielsweise kann dazu Thread-lokaler Speicher verwendet werden Legen Sie je nach aufrufendem Thread unterschiedliche Header-Werte fest. Dies kann beispielsweise zum Festlegen threadspezifischer Trace-IDs für Anforderungen nützlich sein.
Um den Header Content-Length: 0
anzugeben, wenn eine Anfrage mit leerem Text gestellt wird, sollte die Systemeigenschaft sun.net.http.allowRestrictedHeaders
auf true
gesetzt werden
Andernfalls wird der Content-Length
Header nicht hinzugefügt.
In vielen Fällen folgen APIs für einen Dienst denselben Konventionen. Feign unterstützt dieses Muster über Single-Inheritance-Schnittstellen.
Betrachten Sie das Beispiel:
interface BaseAPI {
@ RequestLine ( "GET /health" )
String health ();
@ RequestLine ( "GET /all" )
List < Entity > all ();
}
Sie können eine bestimmte API definieren und als Ziel festlegen und dabei die Basismethoden erben.
interface CustomAPI extends BaseAPI {
@ RequestLine ( "GET /custom" )
String custom ();
}
In vielen Fällen sind auch Ressourcendarstellungen konsistent. Aus diesem Grund werden Typparameter auf der Basis-API-Schnittstelle unterstützt.
@ Headers ( "Accept: application/json" )
interface BaseApi < V > {
@ RequestLine ( "GET /api/{key}" )
V get ( @ Param ( "key" ) String key );
@ RequestLine ( "GET /api" )
List < V > list ();
@ Headers ( "Content-Type: application/json" )
@ RequestLine ( "PUT /api/{key}" )
void put ( @ Param ( "key" ) String key , V value );
}
interface FooApi extends BaseApi < Foo > { }
interface BarApi extends BaseApi < Bar > { }
Sie können die HTTP-Nachrichten, die zum und vom Ziel gehen, protokollieren, indem Sie einen Logger
einrichten. So geht das am einfachsten:
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. decoder ( new GsonDecoder ())
. logger ( new Logger . JavaLogger ( "GitHub.Logger" ). appendToFile ( "logs/http.log" ))
. logLevel ( Logger . Level . FULL )
. target ( GitHub . class , "https://api.github.com" );
}
}
Ein Hinweis zu JavaLogger : Vermeiden Sie die Verwendung des Standardkonstruktors
JavaLogger()
– er wurde als veraltet markiert und wird bald entfernt.
Der SLF4JLogger (siehe oben) könnte auch von Interesse sein.
Um vertrauliche Informationen wie Autorisierung oder Token herauszufiltern, überschreiben Sie die Methoden shouldLogRequestHeader
oder shouldLogResponseHeader
.
Wenn Sie alle Anfragen unabhängig von ihrem Ziel ändern müssen, sollten Sie einen RequestInterceptor
konfigurieren. Wenn Sie beispielsweise als Vermittler fungieren, möchten Sie möglicherweise den X-Forwarded-For
Header weitergeben.
static class ForwardedForInterceptor implements RequestInterceptor {
@ Override public void apply ( RequestTemplate template ) {
template . header ( "X-Forwarded-For" , "origin.host.com" );
}
}
public class Example {
public static void main ( String [] args ) {
Bank bank = Feign . builder ()
. decoder ( accountDecoder )
. requestInterceptor ( new ForwardedForInterceptor ())
. target ( Bank . class , "https://api.examplebank.com" );
}
}
Ein weiteres häufiges Beispiel für einen Interceptor wäre die Authentifizierung, beispielsweise die Verwendung des integrierten BasicAuthRequestInterceptor
.
public class Example {
public static void main ( String [] args ) {
Bank bank = Feign . builder ()
. decoder ( accountDecoder )
. requestInterceptor ( new BasicAuthRequestInterceptor ( username , password ))
. target ( Bank . class , "https://api.examplebank.com" );
}
}
Mit Param
annotierte Parameter werden basierend auf ihrem toString
erweitert. Durch die Angabe eines benutzerdefinierten Param.Expander
können Benutzer dieses Verhalten steuern, beispielsweise die Formatierung von Datumsangaben.
public interface Api {
@ RequestLine ( "GET /?since={date}" ) Result list ( @ Param ( value = "date" , expander = DateToMillis . class ) Date date );
}
Ein Kartenparameter kann mit QueryMap
mit Anmerkungen versehen werden, um eine Abfrage zu erstellen, die den Inhalt der Karte als Abfrageparameter verwendet.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap Map < String , Object > queryMap );
}
Dies kann auch verwendet werden, um die Abfrageparameter aus einem POJO-Objekt mithilfe eines QueryMapEncoder
zu generieren.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap CustomPojo customPojo );
}
Bei dieser Verwendung ohne Angabe eines benutzerdefinierten QueryMapEncoder
wird die Abfragezuordnung unter Verwendung von Mitgliedsvariablennamen als Abfrageparameternamen generiert. Sie können ein bestimmtes Feld von CustomPojo
mit der Annotation @Param
versehen, um einen anderen Namen für den Abfrageparameter anzugeben. Das folgende POJO generiert Abfrageparameter von „/find?name={name}&number={number}®ion_id={regionId}“ (die Reihenfolge der enthaltenen Abfrageparameter ist nicht garantiert, und wie üblich ist es so, wenn ein Wert null ist weggelassen).
public class CustomPojo {
private final String name ;
private final int number ;
@ Param ( "region_id" )
private final String regionId ;
public CustomPojo ( String name , int number , String regionId ) {
this . name = name ;
this . number = number ;
this . regionId = regionId ;
}
}
So richten Sie einen benutzerdefinierten QueryMapEncoder
ein:
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. queryMapEncoder ( new MyCustomQueryMapEncoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
Beim Annotieren von Objekten mit @QueryMap verwendet der Standard-Encoder Reflektion, um bereitgestellte Objektfelder zu überprüfen und die Objektwerte in eine Abfragezeichenfolge zu erweitern. Wenn Sie es vorziehen, dass die Abfragezeichenfolge mit Getter- und Setter-Methoden erstellt wird, wie in der Java Beans API definiert, verwenden Sie bitte den BeanQueryMapEncoder
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. queryMapEncoder ( new BeanQueryMapEncoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
Wenn Sie mehr Kontrolle über den Umgang mit unerwarteten Antworten benötigen, können Feign-Instanzen über den Builder einen benutzerdefinierten ErrorDecoder
registrieren.
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. errorDecoder ( new MyErrorDecoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
Alle Antworten, die zu einem HTTP-Status führen, der nicht im 2xx-Bereich liegt, lösen die decode
von ErrorDecoder
aus, sodass Sie die Antwort verarbeiten, den Fehler in eine benutzerdefinierte Ausnahme einschließen oder eine zusätzliche Verarbeitung durchführen können. Wenn Sie die Anfrage erneut versuchen möchten, lösen Sie eine RetryableException
aus. Dadurch wird der registrierte Retryer
aufgerufen.
Feign wird IOException
s standardmäßig unabhängig von der HTTP-Methode automatisch wiederholen und sie als vorübergehende netzwerkbezogene Ausnahmen sowie alle von einem ErrorDecoder
ausgelösten RetryableException
behandeln. Um dieses Verhalten anzupassen, registrieren Sie eine benutzerdefinierte Retryer
Instanz über den Builder.
Das folgende Beispiel zeigt, wie das Token aktualisiert und mit ErrorDecoder
und Retryer
erneut versucht wird, wenn eine 401-Antwort empfangen wird.
public class Example {
public static void main ( String [] args ) {
var github = Feign . builder ()
. decoder ( new GsonDecoder ())
. retryer ( new MyRetryer ( 100 , 3 ))
. errorDecoder ( new MyErrorDecoder ())
. target ( Github . class , "https://api.github.com" );
var contributors = github . contributors ( "foo" , "bar" , "invalid_token" );
for ( var contributor : contributors ) {
System . out . println ( contributor . login + " " + contributor . contributions );
}
}
static class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default ();
@ Override
public Exception decode ( String methodKey , Response response ) {
// wrapper 401 to RetryableException in order to retry
if ( response . status () == 401 ) {
return new RetryableException ( response . status (), response . reason (), response . request (). httpMethod (), null , response . request ());
}
return defaultErrorDecoder . decode ( methodKey , response );
}
}
static class MyRetryer implements Retryer {
private final long period ;
private final int maxAttempts ;
private int attempt = 1 ;
public MyRetryer ( long period , int maxAttempts ) {
this . period = period ;
this . maxAttempts = maxAttempts ;
}
@ Override
public void continueOrPropagate ( RetryableException e ) {
if (++ attempt > maxAttempts ) {
throw e ;
}
if ( e . status () == 401 ) {
// remove Authorization first, otherwise Feign will add a new Authorization header
// cause github responses a 400 bad request
e . request (). requestTemplate (). removeHeader ( "Authorization" );
e . request (). requestTemplate (). header ( "Authorization" , "Bearer " + getNewToken ());
try {
Thread . sleep ( period );
} catch ( InterruptedException ex ) {
throw e ;
}
} else {
throw e ;
}
}
// Access an external api to obtain new token
// In this example, we can simply return a fixed token to demonstrate how Retryer works
private String getNewToken () {
return "newToken" ;
}
@ Override
public Retryer clone () {
return new MyRetryer ( period , maxAttempts );
}
}
Retryer
sind dafür verantwortlich, zu bestimmen, ob ein erneuter Versuch erfolgen soll, indem sie entweder „ true
oder false
von der Methode continueOrPropagate(RetryableException e);
Für jede Client
-Ausführung wird eine Retryer
-Instanz erstellt, sodass Sie bei Bedarf den Status zwischen den einzelnen Anforderungen aufrechterhalten können.
Wenn festgestellt wird, dass der Wiederholungsversuch nicht erfolgreich ist, wird die letzte RetryException
ausgelöst. Um die ursprüngliche Ursache auszulösen, die zu dem erfolglosen Wiederholungsversuch geführt hat, erstellen Sie Ihren Feign-Client mit der Option exceptionPropagationPolicy()
.
Wenn Sie einen Fehler als Erfolg behandeln und ein Ergebnis zurückgeben müssen, anstatt eine Ausnahme auszulösen, können Sie einen ResponseInterceptor
verwenden.
Als Beispiel enthält Feign einen einfachen RedirectionInterceptor
, der zum Extrahieren des Standortheaders aus Umleitungsantworten verwendet werden kann.
public interface Api {
// returns a 302 response
@ RequestLine ( "GET /location" )
String location ();
}
public class MyApp {
public static void main ( String [] args ) {
// Configure the HTTP client to ignore redirection
Api api = Feign . builder ()
. options ( new Options ( 10 , TimeUnit . SECONDS , 60 , TimeUnit . SECONDS , false ))
. responseInterceptor ( new RedirectionInterceptor ())
. target ( Api . class , "https://redirect.example.com" );
}
}
Standardmäßig erfasst feign keine Messwerte.
Es ist jedoch möglich, jedem Feign-Client Metrikerfassungsfunktionen hinzuzufügen.
Metric Capabilities bietet eine erstklassige Metrics-API, auf die Benutzer zugreifen können, um Einblicke in den Anforderungs-/Antwortlebenszyklus zu erhalten.
Ein Hinweis zu Metrikmodulen :
Alle Metrikintegrationen sind in separaten Modulen integriert und im
feign-core
-Modul nicht verfügbar. Sie müssen sie zu Ihren Abhängigkeiten hinzufügen.
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new Metrics4Capability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new Metrics5Capability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new MicrometerCapability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
Von Feign anvisierte Schnittstellen können über statische oder Standardmethoden verfügen (bei Verwendung von Java 8+). Dadurch können Feign-Clients Logik enthalten, die nicht ausdrücklich durch die zugrunde liegende API definiert ist. Statische Methoden erleichtern beispielsweise die Angabe allgemeiner Client-Build-Konfigurationen. Standardmethoden können zum Verfassen von Abfragen oder zum Definieren von Standardparametern verwendet werden.
interface GitHub {
@ RequestLine ( "GET /repos/{owner}/{repo}/contributors" )
List < Contributor > contributors ( @ Param ( "owner" ) String owner , @ Param ( "repo" ) String repo );
@ RequestLine ( "GET /users/{username}/repos?sort={sort}" )
List < Repo > repos ( @ Param ( "username" ) String owner , @ Param ( "sort" ) String sort );
default List < Repo > repos ( String owner ) {
return repos ( owner , "full_name" );
}
/**
* Lists all contributors for all repos owned by a user.
*/
default List < Contributor > contributors ( String user ) {
MergingContributorList contributors = new MergingContributorList ();
for ( Repo repo : this . repos ( owner )) {
contributors . addAll ( this . contributors ( user , repo . getName ()));
}
return contributors . mergeResult ();
}
static GitHub connect () {
return Feign . builder ()
. decoder ( new GsonDecoder ())
. target ( GitHub . class , "https://api.github.com" );
}
}
CompletableFuture
Feign 10.8 führt einen neuen Builder AsyncFeign
ein, der es Methoden ermöglicht, CompletableFuture
-Instanzen zurückzugeben.
interface GitHub {
@ RequestLine ( "GET /repos/{owner}/{repo}/contributors" )
CompletableFuture < List < Contributor >> contributors ( @ Param ( "owner" ) String owner , @ Param ( "repo" ) String repo );
}
public class MyApp {
public static void main ( String ... args ) {
GitHub github = AsyncFeign . builder ()
. decoder ( new GsonDecoder ())
. target ( GitHub . class , "https://api.github.com" );
// Fetch and print a list of the contributors to this library.
CompletableFuture < List < Contributor >> contributors = github . contributors ( "OpenFeign" , "feign" );
for ( Contributor contributor : contributors . get ( 1 , TimeUnit . SECONDS )) {
System . out . println ( contributor . login + " (" + contributor . contributions + ")" );
}
}
}
Die anfängliche Implementierung umfasst zwei asynchrone Clients:
AsyncClient.Default
AsyncApacheHttp5Client
Es ist wichtig, alle Feign-Bibliotheken auf derselben Version zu halten, um inkompatible Binärdateien zu vermeiden. Bei der Nutzung externer Abhängigkeiten kann es schwierig sein, sicherzustellen, dass nur eine Version vorhanden ist.
Vor diesem Hintergrund generiert feign build ein Modul namens feign-bom
, das die Versionen für alle feign-*
Module sperrt.
Die Stückliste ist eine spezielle POM-Datei, die Abhängigkeitsversionen gruppiert, die bekanntermaßen gültig sind und auf ihre Zusammenarbeit getestet wurden. Dies verringert den Aufwand für Entwickler, die Kompatibilität verschiedener Versionen testen zu müssen, und verringert die Wahrscheinlichkeit von Versionskonflikten.
Hier ist ein Beispiel dafür, wie eine vorgetäuschte Stücklistendatei aussieht.
< project >
...
< dependencyManagement >
< dependencies >
< dependency >
< groupId >io.github.openfeign</ groupId >
< artifactId >feign-bom</ artifactId >
< version >??feign.version??</ version >
< type >pom</ type >
< scope >import</ scope >
</ dependency >
</ dependencies >
</ dependencyManagement >
</ project >
Dieses Modul fügt Unterstützung für die Codierung von application/x-www-form-urlencoded- und multipart/form-data- Formularen hinzu.
Fügen Sie die Abhängigkeit zu Ihrer App hinzu:
Maven :
< dependencies >
...
< dependency >
< groupId >io.github.openfeign.form</ groupId >
< artifactId >feign-form</ artifactId >
< version >4.0.0</ version >
</ dependency >
...
</ dependencies >
Gradle :
compile ' io.github.openfeign.form:feign-form:4.0.0 '
Die feign-form
Erweiterung hängt von OpenFeign
und seinen konkreten Versionen ab:
feign-form
Versionen vor 3.5.0 funktionieren mit OpenFeign
9.*- Versionen;feign-form
funktioniert das Modul mit OpenFeign
10.1.0- Versionen und höher.WICHTIG: Es gibt keine Abwärtskompatibilität und keine Garantie dafür, dass die Versionen von
feign-form
nach 3.5.0 mitOpenFeign
vor 10.* funktionieren.OpenFeign
wurde in der 10. Version überarbeitet. Der beste Ansatz besteht darin, die aktuellstenOpenFeign
undfeign-form
-Versionen zu verwenden.
Hinweise:
spring-cloud-openfeign verwendet OpenFeign
9.* bis v2.0.3.RELEASE und 10.* danach. Wie auch immer, die Abhängigkeit verfügt bereits über eine geeignete feign-form
, siehe Abhängigkeits-POM, sodass Sie sie nicht separat angeben müssen.
spring-cloud-starter-feign
ist eine veraltete Abhängigkeit und verwendet immer die 9.*- Versionen von OpenFeign
.
Fügen Sie FormEncoder
wie folgt zu Ihrem Feign.Builder
hinzu:
SomeApi github = Feign . builder ()
. encoder ( new FormEncoder ())
. target ( SomeApi . class , "http://api.some.org" );
Darüber hinaus können Sie den vorhandenen Encoder, zum Beispiel JsonEncoder, wie folgt dekorieren:
SomeApi github = Feign . builder ()
. encoder ( new FormEncoder ( new JacksonEncoder ()))
. target ( SomeApi . class , "http://api.some.org" );
Und verwenden Sie sie zusammen:
interface SomeApi {
@ RequestLine ( "POST /json" )
@ Headers ( "Content-Type: application/json" )
void json ( Dto dto );
@ RequestLine ( "POST /form" )
@ Headers ( "Content-Type: application/x-www-form-urlencoded" )
void from ( @ Param ( "field1" ) String field1 , @ Param ( "field2" ) String [] values );
}
Sie können zwei Arten von Codierungsformen durch Content-Type
Header angeben.
interface SomeApi {
@ RequestLine ( "POST /authorization" )
@ Headers ( "Content-Type: application/x-www-form-urlencoded" )
void authorization ( @ Param ( "email" ) String email , @ Param ( "password" ) String password );
// Group all parameters within a POJO
@ RequestLine ( "POST /user" )
@ Headers ( "Content-Type: application/x-www-form-urlencoded" )
void addUser ( User user );
class User {
Integer id ;
String name ;
}
}
interface SomeApi {
// File parameter
@ RequestLine ( "POST /send_photo" )
@ Headers ( "Content-Type: multipart/form-data" )
void sendPhoto ( @ Param ( "is_public" ) Boolean isPublic , @ Param ( "photo" ) File photo );
// byte[] parameter
@ RequestLine ( "POST /send_photo" )
@ Headers ( "Content-Type: multipart/form-data" )
void sendPhoto ( @ Param ( "is_public" ) Boolean isPublic , @ Param ( "photo" ) byte [] photo );
// FormData parameter
@ RequestLine ( "POST /send_photo" )
@ Headers ( "Content-Type: multipart/form-data" )
void sendPhoto ( @ Param ( "is_public" ) Boolean isPublic , @ Param ( "photo" ) FormData photo );
// Group all parameters within a POJO
@ RequestLine ( "POST /send_photo" )
@ Headers ( "Content-Type: multipart/form-data" )
void sendPhoto ( MyPojo pojo );
class MyPojo {
@ FormProperty ( "is_public" )
Boolean isPublic ;
File photo ;
}
}
Im obigen Beispiel verwendet die sendPhoto
-Methode den photo
unter Verwendung von drei verschiedenen unterstützten Typen.
File
verwendet die Dateierweiterung, um den Content-Type
zu erkennen.byte[]
verwendet application/octet-stream
als Content-Type
;FormData
verwendet den Content-Type
und fileName
von FormData
. FormData
ist ein benutzerdefiniertes Objekt, das ein byte[]
umschließt und einen Content-Type
und einen fileName
wie folgt definiert:
FormData formData = new FormData ( "image/png" , "filename.png" , myDataAsByteArray );
someApi . sendPhoto ( true , formData );
Sie können Form Encoder auch mit Spring MultipartFile
und @FeignClient
verwenden.
Fügen Sie die Abhängigkeiten zur pom.xml-Datei Ihres Projekts hinzu:
< dependencies >
< dependency >
< groupId >io.github.openfeign.form</ groupId >
< artifactId >feign-form</ artifactId >
< version >4.0.0</ version >
</ dependency >
< dependency >
< groupId >io.github.openfeign.form</ groupId >
< artifactId >feign-form-spring</ artifactId >
< version >4.0.0</ version >
</ dependency >
</ dependencies >
@ FeignClient (
name = "file-upload-service" ,
configuration = FileUploadServiceClient . MultipartSupportConfig . class
)
public interface FileUploadServiceClient extends IFileUploadServiceClient {
public class MultipartSupportConfig {
@ Autowired
private ObjectFactory < HttpMessageConverters > messageConverters ;
@ Bean
public Encoder feignFormEncoder () {
return new SpringFormEncoder ( new SpringEncoder ( messageConverters ));
}
}
}
Oder wenn Sie den Standard-Encoder von Spring nicht benötigen:
@ FeignClient (
name = "file-upload-service" ,
configuration = FileUploadServiceClient . MultipartSupportConfig . class
)
public interface FileUploadServiceClient extends IFileUploadServiceClient {
public class MultipartSupportConfig {
@ Bean
public Encoder feignFormEncoder () {
return new SpringFormEncoder ();
}
}
}
Vielen Dank an tf-haotri-pham für seine Funktion, die die Apache commons-fileupload-Bibliothek nutzt, die das Parsen der mehrteiligen Antwort übernimmt. Die Körperdatenteile werden als Byte-Arrays im Speicher gehalten.
Um diese Funktion zu verwenden, nehmen Sie SpringManyMultipartFilesReader in die Liste der Nachrichtenkonverter für den Decoder auf und lassen Sie den Feign-Client ein Array von MultipartFile zurückgeben:
@ FeignClient (
name = "${feign.name}" ,
url = "${feign.url}"
configuration = DownloadClient . ClientConfiguration . class
)
public interface DownloadClient {
@ RequestMapping ( "/multipart/download/{fileId}" )
MultipartFile [] download ( @ PathVariable ( "fileId" ) String fileId );
class ClientConfiguration {
@ Autowired
private ObjectFactory < HttpMessageConverters > messageConverters ;
@ Bean
public Decoder feignDecoder () {
List < HttpMessageConverter <?>> springConverters =
messageConverters . getObject (). getConverters ();
List < HttpMessageConverter <?>> decoderConverters =
new ArrayList < HttpMessageConverter <?>>( springConverters . size () + 1 );
decoderConverters . addAll ( springConverters );
decoderConverters . add ( new SpringManyMultipartFilesReader ( 4096 ));
HttpMessageConverters httpMessageConverters = new HttpMessageConverters ( decoderConverters );
return new SpringDecoder ( new ObjectFactory < HttpMessageConverters >() {
@ Override
public HttpMessageConverters getObject () {
return httpMessageConverters ;
}
});
}
}
}