Feign adalah pengikat klien Java ke HTTP yang terinspirasi oleh Retrofit, JAXRS-2.0, dan WebSocket. Tujuan pertama Feign adalah mengurangi kerumitan pengikatan Denominator secara seragam ke API HTTP terlepas dari ReSTfulness.
Feign menggunakan alat seperti Jersey dan CXF untuk menulis klien Java untuk layanan ReST atau SOAP. Selain itu, Feign memungkinkan Anda menulis kode Anda sendiri di atas pustaka http seperti Apache HC. Feign menghubungkan kode Anda ke API http dengan overhead dan kode minimal melalui dekoder yang dapat disesuaikan dan penanganan kesalahan, yang dapat ditulis ke API http berbasis teks apa pun.
Feign bekerja dengan memproses anotasi menjadi permintaan yang ditemplat. Argumen diterapkan pada templat ini secara langsung sebelum dikeluarkan. Meskipun Feign terbatas pada mendukung API berbasis teks, Feign secara dramatis menyederhanakan aspek sistem seperti memutar ulang permintaan. Selain itu, Feign memudahkan pengujian unit konversi Anda dengan mengetahui hal ini.
Feign 10.x ke atas dibuat di Java 8 dan dapat berfungsi di Java 9, 10, dan 11. Bagi yang membutuhkan kompatibilitas JDK 6, silakan gunakan Feign 9.x
Ini adalah peta dengan fitur-fitur utama terkini yang disediakan oleh feign:
Membuat klien API lebih mudah
Logger
Logger
API agar lebih sesuai dengan kerangka kerja seperti SLF4J yang menyediakan model mental umum untuk masuk ke dalam Feign. Model ini akan digunakan oleh Feign sendiri dan memberikan arahan yang lebih jelas tentang bagaimana Logger
akan digunakan.Retry
pemfaktoran ulang APIRetry
API untuk mendukung kondisi yang disediakan pengguna dan kontrol yang lebih baik terhadap kebijakan back-off. Hal ini dapat mengakibatkan perubahan yang tidak kompatibel dengan versi sebelumnya CompletableFuture
Future
untuk siklus hidup permintaan/respons. Implementasinya memerlukan perubahan-perubahan yang tidak kompatibel dengan versi sebelumnya . Namun fitur ini diperlukan sebelum eksekusi Reaktif dapat dipertimbangkan.java.util.concurrent.Flow
.Perpustakaan berpura-pura tersedia dari Maven Central.
< dependency >
< groupId >io.github.openfeign</ groupId >
< artifactId >feign-core</ artifactId >
< version >??feign.version??</ version >
</ dependency >
Penggunaan biasanya terlihat seperti ini, sebuah adaptasi dari sampel Retrofit kanonik.
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 + ")" );
}
}
}
Anotasi berpura-pura menentukan Contract
antara antarmuka dan cara kerja klien yang mendasarinya. Kontrak default Feign mendefinisikan anotasi berikut:
Anotasi | Sasaran Antarmuka | Penggunaan |
---|---|---|
@RequestLine | Metode | Mendefinisikan HttpMethod dan UriTemplate untuk permintaan. Expressions , nilai yang dibungkus dengan kurung kurawal {expression} diselesaikan menggunakan parameter beranotasi @Param yang sesuai. |
@Param | Parameter | Mendefinisikan variabel templat, yang nilainya akan digunakan untuk menyelesaikan templat Expression yang sesuai, dengan nama yang diberikan sebagai nilai anotasi. Jika nilainya hilang, ia akan mencoba mendapatkan nama dari nama parameter metode bytecode (jika kode dikompilasi dengan flag -parameters ). |
@Headers | Metode, Ketik | Mendefinisikan HeaderTemplate ; variasi pada UriTemplate . yang menggunakan nilai beranotasi @Param untuk menyelesaikan Expressions yang sesuai. Saat digunakan pada Type , templat akan diterapkan ke setiap permintaan. Saat digunakan pada suatu Method , templat hanya akan berlaku untuk metode yang diberi anotasi. |
@QueryMap | Parameter | Mendefinisikan Map pasangan nama-nilai, atau POJO, untuk diperluas menjadi string kueri. |
@HeaderMap | Parameter | Mendefinisikan Map pasangan nama-nilai, untuk diperluas ke Http Headers |
@Body | Metode | Mendefinisikan Template , mirip dengan UriTemplate dan HeaderTemplate , yang menggunakan nilai beranotasi @Param untuk menyelesaikan Expressions yang sesuai. |
Mengganti Jalur Permintaan
Jika ada kebutuhan untuk menargetkan permintaan ke host yang berbeda maka yang diberikan saat klien Feign dibuat, atau Anda ingin menyediakan host target untuk setiap permintaan, sertakan parameter
java.net.URI
dan Feign akan menggunakan nilai itu sebagai target permintaan.@ RequestLine ( "POST /repos/{owner}/{repo}/issues" ) void createIssue ( URI host , Issue issue , @ Param ( "owner" ) String owner , @ Param ( "repo" ) String repo );
Expressions
Pura-pura mewakili Ekspresi String Sederhana (Level 1) sebagaimana ditentukan oleh Templat URI - RFC 6570. Expressions
diperluas menggunakan parameter metode beranotasi Param
yang sesuai.
Contoh
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" );
}
}
Ekspresi harus diapit kurung kurawal {}
dan boleh berisi pola ekspresi reguler, dipisahkan dengan titik dua :
untuk membatasi nilai yang diselesaikan. owner
contoh harus berdasarkan abjad. {owner:[a-zA-Z]*}
Templat RequestLine
dan QueryMap
mengikuti Templat URI - spesifikasi RFC 6570 untuk templat Tingkat 1, yang menetapkan hal berikut:
encoded
melalui anotasi @Param
.Kami juga memiliki dukungan terbatas untuk Level 3, Ekspresi Gaya Jalur, dengan batasan berikut:
Contoh:
{;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 ;
}
}
Jika owners
dalam contoh di atas didefinisikan sebagai Matt, Jeff, Susan
, uri akan diperluas menjadi /repos;owners=Matt;owners=Jeff;owners=Susan
Untuk informasi lebih lanjut lihat RFC 6570, Bagian 3.2.7
Ekspresi tak terdefinisi adalah ekspresi yang nilai ekspresinya adalah null
eksplisit atau tidak ada nilai yang diberikan. Per Templat URI - RFC 6570, dimungkinkan untuk memberikan nilai kosong untuk sebuah ekspresi. Saat Feign menyelesaikan sebuah ekspresi, pertama-tama ia menentukan apakah nilainya ditentukan, jika ya, maka parameter kueri akan tetap ada. Jika ekspresi tidak ditentukan, parameter kueri akan dihapus. Lihat di bawah untuk rincian lengkapnya.
Tali Kosong
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
parameters . put ( "param" , "" );
this . demoClient . test ( parameters );
}
Hasil
http://localhost:8080/test?param=
Hilang
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
this . demoClient . test ( parameters );
}
Hasil
http://localhost:8080/test
Belum diartikan
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
parameters . put ( "param" , null );
this . demoClient . test ( parameters );
}
Hasil
http://localhost:8080/test
Lihat Penggunaan Lanjutan untuk contoh lainnya.
Bagaimana dengan garis miring?
/
Templat @RequestLine tidak menyandikan garis miring
/
karakter secara default. Untuk mengubah perilaku ini, setel propertidecodeSlash
di@RequestLine
kefalse
.
Bagaimana dengan plusnya?
+
Sesuai spesifikasi URI, tanda
+
diperbolehkan di segmen jalur dan kueri URI, namun penanganan simbol pada kueri bisa jadi tidak konsisten. Di beberapa sistem lama, tanda+
setara dengan spasi. Feign mengambil pendekatan sistem modern, di mana simbol+
tidak boleh mewakili spasi dan secara eksplisit dikodekan sebagai%2B
ketika ditemukan pada string kueri.Jika Anda ingin menggunakan
+
sebagai spasi, gunakan literalkarakter atau menyandikan nilai secara langsung sebagai
%20
Anotasi @Param
memiliki expander
properti opsional yang memungkinkan kontrol penuh atas perluasan parameter individual. Properti expander
harus mereferensikan kelas yang mengimplementasikan antarmuka Expander
:
public interface Expander {
String expand ( Object value );
}
Hasil dari metode ini mengikuti aturan yang sama yang disebutkan di atas. Jika hasilnya null
atau string kosong, nilainya dihilangkan. Jika nilainya tidak dikodekan persen, maka nilainya akan dikodekan. Lihat Ekspansi @Param Khusus untuk contoh lainnya.
Templat Headers
dan HeaderMap
mengikuti aturan yang sama seperti Perluasan Parameter Permintaan dengan perubahan berikut:
Lihat Header untuk contohnya.
Catatan tentang parameter
@Param
dan namanya :Semua ekspresi dengan nama yang sama, terlepas dari posisinya di
@RequestLine
,@QueryMap
,@BodyTemplate
, atau@Headers
akan menghasilkan nilai yang sama. Dalam contoh berikut, nilaicontentType
, akan digunakan untuk menyelesaikan ekspresi header dan jalur:public interface ContentService { @ RequestLine ( "GET /api/documents/{contentType}" ) @ Headers ( "Accept: {contentType}" ) String getDocumentByType ( @ Param ( "contentType" ) String type ); }Ingatlah hal ini saat mendesain antarmuka Anda.
Templat Body
mengikuti aturan yang sama seperti Perluasan Parameter Permintaan dengan perubahan berikut:
Encoder
sebelum ditempatkan pada isi permintaan.Content-Type
harus ditentukan. Lihat Templat Tubuh untuk contohnya. Feign memiliki beberapa aspek yang dapat disesuaikan.
Untuk kasus sederhana, Anda dapat menggunakan Feign.builder()
untuk membuat antarmuka API dengan komponen khusus Anda.
Untuk pengaturan permintaan, Anda dapat menggunakan options(Request.Options options)
pada target()
untuk mengatur connectTimeout, connectTimeoutUnit, readTimeout, readTimeoutUnit, followRedirects.
Misalnya:
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 dapat menghasilkan banyak antarmuka api. Ini didefinisikan sebagai Target<T>
( HardCodedTarget<T>
default), yang memungkinkan penemuan dinamis dan dekorasi permintaan sebelum eksekusi.
Misalnya, pola berikut mungkin menghiasi setiap permintaan dengan url saat ini dan token autentikasi dari layanan identitas.
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 menyertakan contoh klien GitHub dan Wikipedia. Proyek penyebut juga dapat diterapkan pada Feign dalam praktiknya. Khususnya, lihat contoh daemonnya.
Feign bermaksud untuk bekerja dengan baik dengan alat Open Source lainnya. Modul dipersilakan untuk diintegrasikan dengan proyek favorit Anda!
Gson menyertakan encoder dan decoder yang dapat Anda gunakan dengan API JSON.
Tambahkan GsonEncoder
dan/atau GsonDecoder
ke Feign.Builder
Anda seperti:
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 menyertakan encoder dan decoder yang dapat Anda gunakan dengan API JSON.
Tambahkan JacksonEncoder
dan/atau JacksonDecoder
ke Feign.Builder
Anda seperti:
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" );
}
}
Untuk Jackson Jr yang berbobot lebih ringan, gunakan JacksonJrEncoder
dan JacksonJrDecoder
dari Modul Jackson Jr.
Moshi menyertakan encoder dan decoder yang dapat Anda gunakan dengan API JSON. Tambahkan MoshiEncoder
dan/atau MoshiDecoder
ke Feign.Builder
Anda seperti:
GitHub github = Feign . builder ()
. encoder ( new MoshiEncoder ())
. decoder ( new MoshiDecoder ())
. target ( GitHub . class , "https://api.github.com" );
SaxDecoder memungkinkan Anda memecahkan kode XML dengan cara yang kompatibel dengan JVM normal dan juga lingkungan Android.
Berikut ini contoh cara mengonfigurasi penguraian respons Sax:
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 menyertakan encoder dan decoder yang dapat Anda gunakan dengan XML API.
Tambahkan JAXBEncoder
dan/atau JAXBDecoder
ke Feign.Builder
Anda seperti:
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 menyertakan encoder dan decoder yang dapat Anda gunakan dengan XML API.
Modul ini menambahkan dukungan untuk pengkodean dan decoding objek SOAP Body melalui JAXB dan SOAPMessage. Ini juga menyediakan kemampuan decoding SOAPFault dengan membungkusnya ke dalam javax.xml.ws.soap.SOAPFaultException
asli, sehingga Anda hanya perlu menangkap SOAPFaultException
untuk menangani SOAPFault.
Tambahkan SOAPEncoder
dan/atau SOAPDecoder
ke Feign.Builder
Anda seperti:
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" );
}
}
NB: Anda mungkin juga perlu menambahkan SOAPErrorDecoder
jika Kesalahan SOAP dikembalikan sebagai respons dengan kode http kesalahan (4xx, 5xx, ...)
fastjson2 menyertakan encoder dan decoder yang dapat Anda gunakan dengan API JSON.
Tambahkan Fastjson2Encoder
dan/atau Fastjson2Decoder
ke Feign.Builder
Anda seperti:
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 mengesampingkan pemrosesan anotasi untuk menggunakan pemrosesan anotasi standar yang disediakan oleh spesifikasi JAX-RS. Saat ini ditargetkan pada spesifikasi 1.1.
Berikut contoh di atas yang ditulis ulang untuk menggunakan JAX-RS:
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 mengarahkan permintaan http Feign ke OkHttp, yang memungkinkan SPDY dan kontrol jaringan yang lebih baik.
Untuk menggunakan OkHttp dengan Feign, tambahkan modul OkHttp ke classpath Anda. Kemudian, konfigurasikan Feign untuk menggunakan 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 mengesampingkan resolusi URL klien Feign, menambahkan perutean cerdas dan kemampuan ketahanan yang disediakan oleh Ribbon.
Integrasi mengharuskan Anda meneruskan nama klien pita Anda sebagai bagian host url, misalnya myAppProd
.
public class Example {
public static void main ( String [] args ) {
MyService api = Feign . builder ()
. client ( RibbonClient . create ())
. target ( MyService . class , "https://myAppProd" );
}
}
Http2Client mengarahkan permintaan http Feign ke Klien HTTP/2 Baru Java11 yang mengimplementasikan HTTP/2.
Untuk menggunakan Klien HTTP/2 Baru dengan Feign, gunakan Java SDK 11. Kemudian, konfigurasikan Feign untuk menggunakan Http2Client:
GitHub github = Feign . builder ()
. client ( new Http2Client ())
. target ( GitHub . class , "https://api.github.com" );
HystrixFeign mengonfigurasi dukungan pemutus sirkuit yang disediakan oleh Hystrix.
Untuk menggunakan Hystrix dengan Feign, tambahkan modul Hystrix ke classpath Anda. Kemudian gunakan pembuat HystrixFeign
:
public class Example {
public static void main ( String [] args ) {
MyService api = HystrixFeign . builder (). target ( MyService . class , "https://myAppProd" );
}
}
SLF4JModule memungkinkan mengarahkan logging Feign ke SLF4J, memungkinkan Anda dengan mudah menggunakan backend logging pilihan Anda (Logback, Log4J, dll.)
Untuk menggunakan SLF4J dengan Feign, tambahkan modul SLF4J dan pengikatan SLF4J pilihan Anda ke classpath Anda. Kemudian, konfigurasikan Feign untuk menggunakan 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()
memungkinkan Anda menentukan konfigurasi tambahan seperti cara mendekode respons.
Jika ada metode di antarmuka Anda yang mengembalikan tipe selain Response
, String
, byte[]
atau void
, Anda harus mengonfigurasi Decoder
non-default.
Berikut cara mengonfigurasi decoding JSON (menggunakan ekstensi 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" );
}
}
Jika Anda perlu memproses respons terlebih dahulu sebelum memberikannya ke Decoder, Anda dapat menggunakan metode pembuat mapAndDecode
. Contoh kasus penggunaan berurusan dengan API yang hanya melayani jsonp, Anda mungkin perlu membuka jsonp sebelum mengirimkannya ke decoder Json pilihan Anda:
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" );
}
}
Jika ada metode di antarmuka Anda yang mengembalikan tipe Stream
, Anda harus mengonfigurasi StreamDecoder
.
Berikut cara mengonfigurasi dekoder aliran tanpa dekoder delegasi:
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" );
}
}
Berikut cara mengonfigurasi dekoder aliran dengan dekoder delegasi:
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" );
}
}
Cara paling sederhana untuk mengirim isi permintaan ke server adalah dengan mendefinisikan metode POST
yang memiliki parameter String
atau byte[]
tanpa anotasi apa pun di dalamnya. Anda mungkin perlu menambahkan header Content-Type
.
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 " }" );
}
}
Dengan mengonfigurasi Encoder
, Anda dapat mengirim isi permintaan yang aman untuk tipe. Berikut ini contoh penggunaan ekstensi 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" ));
}
}
Anotasi @Body
menunjukkan templat yang akan diperluas menggunakan parameter yang dianotasi dengan @Param
. Anda mungkin perlu menambahkan header Content-Type
.
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 mendukung pengaturan header pada permintaan baik sebagai bagian dari api atau sebagai bagian dari klien tergantung pada kasus penggunaan.
Dalam kasus di mana antarmuka atau panggilan tertentu harus selalu menetapkan nilai header tertentu, masuk akal untuk mendefinisikan header sebagai bagian dari api.
Header statis dapat disetel pada antarmuka atau metode api menggunakan anotasi @Headers
.
@ Headers ( "Accept: application/json" )
interface BaseApi < V > {
@ Headers ( "Content-Type: application/json" )
@ RequestLine ( "PUT /api/{key}" )
void put ( @ Param ( "key" ) String key , V value );
}
Metode dapat menentukan konten dinamis untuk header statis menggunakan ekspansi variabel di @Headers
.
public interface Api {
@ RequestLine ( "POST /" )
@ Headers ( "X-Ping: {token}" )
void post ( @ Param ( "token" ) String token );
}
Dalam kasus di mana kunci dan nilai bidang header bersifat dinamis dan kisaran kemungkinan kunci tidak dapat diketahui sebelumnya dan dapat bervariasi antara pemanggilan metode yang berbeda dalam api/klien yang sama (misalnya bidang header metadata khusus seperti "x-amz- meta-*" atau "x-goog-meta-*"), parameter Peta dapat dianotasi dengan HeaderMap
untuk membuat kueri yang menggunakan konten peta sebagai parameter headernya.
public interface Api {
@ RequestLine ( "POST /" )
void post ( @ HeaderMap Map < String , Object > headerMap );
}
Pendekatan ini menentukan entri header sebagai bagian dari api dan tidak memerlukan penyesuaian apa pun saat membangun klien Feign.
Untuk menyesuaikan header untuk setiap metode permintaan pada Target, RequestInterceptor dapat digunakan. RequestInterceptors dapat dibagikan ke seluruh instance Target dan diharapkan aman untuk thread. RequestInterceptors diterapkan ke semua metode permintaan pada Target.
Jika Anda memerlukan penyesuaian per metode, Target khusus diperlukan, karena RequestInterceptor tidak memiliki akses ke metadata metode saat ini.
Untuk contoh pengaturan header menggunakan RequestInterceptor
, lihat bagian Request Interceptors
.
Header dapat ditetapkan sebagai bagian dari Target
khusus.
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 ));
}
}
Pendekatan ini bergantung pada RequestInterceptor
atau Target
khusus yang disetel pada klien Feign saat dibuat dan dapat digunakan sebagai cara untuk menyetel header pada semua panggilan api berdasarkan per klien. Hal ini dapat berguna untuk melakukan hal-hal seperti mengatur token autentikasi di header semua permintaan api pada basis per klien. Metode ini dijalankan ketika panggilan api dilakukan pada thread yang memanggil panggilan api, yang memungkinkan header disetel secara dinamis pada waktu panggilan dan dengan cara yang sesuai konteks -- misalnya, penyimpanan lokal thread dapat digunakan untuk tetapkan nilai header yang berbeda bergantung pada thread yang dipanggil, yang dapat berguna untuk hal-hal seperti menyetel pengidentifikasi jejak spesifik thread untuk permintaan.
Untuk menentukan Content-Length: 0
header saat membuat permintaan dengan isi kosong, properti sistem sun.net.http.allowRestrictedHeaders
harus disetel ke true
Jika tidak, header Content-Length
tidak akan ditambahkan.
Dalam banyak kasus, api untuk suatu layanan mengikuti konvensi yang sama. Feign mendukung pola ini melalui antarmuka warisan tunggal.
Perhatikan contohnya:
interface BaseAPI {
@ RequestLine ( "GET /health" )
String health ();
@ RequestLine ( "GET /all" )
List < Entity > all ();
}
Anda dapat menentukan dan menargetkan api tertentu, dengan mewarisi metode dasar.
interface CustomAPI extends BaseAPI {
@ RequestLine ( "GET /custom" )
String custom ();
}
Dalam banyak kasus, representasi sumber daya juga konsisten. Karena alasan ini, parameter tipe didukung pada antarmuka api dasar.
@ 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 > { }
Anda dapat mencatat pesan http yang menuju dan dari target dengan menyiapkan Logger
. Inilah cara termudah untuk melakukannya:
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" );
}
}
Catatan tentang JavaLogger : Hindari penggunaan konstruktor
JavaLogger()
default - ini ditandai sebagai tidak digunakan lagi dan akan segera dihapus.
SLF4JLogger (lihat di atas) mungkin juga menarik.
Untuk memfilter informasi sensitif seperti otorisasi atau metode penggantian token shouldLogRequestHeader
atau shouldLogResponseHeader
.
Saat Anda perlu mengubah semua permintaan, apa pun targetnya, Anda dapat mengonfigurasi RequestInterceptor
. Misalnya, jika Anda bertindak sebagai perantara, Anda mungkin ingin menyebarkan header X-Forwarded-For
.
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" );
}
}
Contoh umum lainnya dari pencegat adalah autentikasi, seperti menggunakan BasicAuthRequestInterceptor
bawaan.
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" );
}
}
Parameter yang dianotasi dengan Param
diperluas berdasarkan toString
. Dengan menentukan Param.Expander
khusus, pengguna dapat mengontrol perilaku ini, misalnya memformat tanggal.
public interface Api {
@ RequestLine ( "GET /?since={date}" ) Result list ( @ Param ( value = "date" , expander = DateToMillis . class ) Date date );
}
Parameter Peta dapat dianotasi dengan QueryMap
untuk membuat kueri yang menggunakan konten peta sebagai parameter kuerinya.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap Map < String , Object > queryMap );
}
Ini juga dapat digunakan untuk menghasilkan parameter kueri dari objek POJO menggunakan QueryMapEncoder
.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap CustomPojo customPojo );
}
Ketika digunakan dengan cara ini, tanpa menentukan QueryMapEncoder
khusus, peta kueri akan dibuat menggunakan nama variabel anggota sebagai nama parameter kueri. Anda dapat memberi anotasi pada bidang CustomPojo
tertentu dengan anotasi @Param
untuk menentukan nama berbeda pada parameter kueri. POJO berikut akan menghasilkan parameter kueri "/find?name={name}&number={number}®ion_id={regionId}" (urutan parameter kueri yang disertakan tidak dijamin, dan seperti biasa, jika ada nilai yang nol, maka akan menjadi ditinggalkan).
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 ;
}
}
Untuk menyiapkan QueryMapEncoder
khusus :
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. queryMapEncoder ( new MyCustomQueryMapEncoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
Saat menganotasi objek dengan @QueryMap, pembuat enkode default menggunakan refleksi untuk memeriksa Bidang objek yang disediakan untuk memperluas nilai objek ke dalam string kueri. Jika Anda lebih suka string kueri dibuat menggunakan metode pengambil dan penyetel, seperti yang didefinisikan dalam Java Beans API, silakan gunakan BeanQueryMapEncoder
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. queryMapEncoder ( new BeanQueryMapEncoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
Jika Anda memerlukan kontrol lebih besar dalam menangani respons yang tidak terduga, instance Feign dapat mendaftarkan ErrorDecoder
khusus melalui pembuatnya.
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. errorDecoder ( new MyErrorDecoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
Semua respons yang menghasilkan status HTTP bukan dalam rentang 2xx akan memicu metode decode
ErrorDecoder
, sehingga Anda dapat menangani respons, memasukkan kegagalan ke dalam pengecualian khusus, atau melakukan pemrosesan tambahan apa pun. Jika Anda ingin mencoba kembali permintaan tersebut, berikan RetryableException
. Ini akan memanggil Retryer
yang terdaftar.
Feign, secara default, akan secara otomatis mencoba kembali IOException
s, apa pun metode HTTPnya, memperlakukannya sebagai pengecualian terkait jaringan sementara, dan RetryableException
apa pun yang dilemparkan dari ErrorDecoder
. Untuk menyesuaikan perilaku ini, daftarkan instans Retryer
kustom melalui pembuatnya.
Contoh berikut menunjukkan cara menyegarkan token dan mencoba lagi dengan ErrorDecoder
dan Retryer
ketika menerima respons 401.
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
s bertanggung jawab untuk menentukan apakah percobaan ulang harus dilakukan dengan mengembalikan nilai true
atau false
dari metode continueOrPropagate(RetryableException e);
Mesin virtual Retryer
akan dibuat untuk setiap eksekusi Client
, memungkinkan Anda mempertahankan status antara setiap permintaan jika diinginkan.
Jika percobaan ulang ditentukan tidak berhasil, RetryException
terakhir akan dilempar. Untuk menghilangkan penyebab awal yang menyebabkan percobaan ulang gagal, bangun klien Feign Anda dengan opsi exceptionPropagationPolicy()
.
Jika Anda perlu memperlakukan apa yang seharusnya menjadi kesalahan sebagai keberhasilan dan mengembalikan hasil daripada memberikan pengecualian maka Anda dapat menggunakan ResponseInterceptor
.
Sebagai contoh, Feign menyertakan RedirectionInterceptor
sederhana yang dapat digunakan untuk mengekstrak header lokasi dari respons pengalihan.
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" );
}
}
Secara default, berpura-pura tidak akan mengumpulkan metrik apa pun.
Namun, kemampuan pengumpulan metrik dapat ditambahkan ke klien palsu mana pun.
Kemampuan Metrik menyediakan API Metrik kelas satu yang dapat dimanfaatkan pengguna untuk mendapatkan wawasan tentang siklus hidup permintaan/respons.
Catatan tentang modul Metrik :
Semua integrasi metrik dibangun dalam modul terpisah dan tidak tersedia di modul
feign-core
. Anda perlu menambahkannya ke dependensi Anda.
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
}
}
Antarmuka yang ditargetkan oleh Feign mungkin memiliki metode statis atau default (jika menggunakan Java 8+). Hal ini memungkinkan klien Feign memuat logika yang tidak ditentukan secara tegas oleh API yang mendasarinya. Misalnya, metode statis memudahkan untuk menentukan konfigurasi umum pembangunan klien; metode default dapat digunakan untuk membuat kueri atau menentukan parameter default.
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 memperkenalkan pembuat baru AsyncFeign
yang memungkinkan metode mengembalikan instance CompletableFuture
.
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 + ")" );
}
}
}
Implementasi awal mencakup 2 klien async:
AsyncClient.Default
AsyncApacheHttp5Client
Menjaga semua perpustakaan palsu pada versi yang sama sangat penting untuk menghindari biner yang tidak kompatibel. Saat menggunakan dependensi eksternal, mungkin sulit untuk memastikan hanya ada satu versi.
Dengan mengingat hal tersebut, feign build menghasilkan modul bernama feign-bom
yang mengunci versi untuk semua modul feign-*
.
Bill Of Material adalah file POM khusus yang mengelompokkan versi ketergantungan yang diketahui valid dan diuji untuk bekerja sama. Hal ini akan mengurangi kesulitan pengembang karena harus menguji kompatibilitas versi yang berbeda dan mengurangi kemungkinan terjadinya ketidakcocokan versi.
Berikut adalah salah satu contoh tampilan file BOM palsu.
< 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 >
Modul ini menambahkan dukungan untuk pengkodean formulir aplikasi/x-www-form-urlencoded dan multipart/form-data .
Sertakan ketergantungan pada aplikasi Anda:
Pakar :
< dependencies >
...
< dependency >
< groupId >io.github.openfeign.form</ groupId >
< artifactId >feign-form</ artifactId >
< version >4.0.0</ version >
</ dependency >
...
</ dependencies >
kelas :
compile ' io.github.openfeign.form:feign-form:4.0.0 '
Ekstensi feign-form
bergantung pada OpenFeign
dan versi konkritnya :
feign-form
sebelum 3.5.0 berfungsi dengan versi OpenFeign
9.* ;feign-form
3.5.0 , modul ini berfungsi dengan versi OpenFeign
10.1.0 dan lebih tinggi.PENTING: tidak ada kompatibilitas ke belakang dan tidak ada jaminan bahwa versi
feign-form
setelah 3.5.0 berfungsi denganOpenFeign
sebelum 10.* .OpenFeign
telah difaktorkan ulang pada rilis ke-10, jadi pendekatan terbaik - gunakanOpenFeign
terbaru dan versifeign-form
.
Catatan:
spring-cloud-openfeign menggunakan OpenFeign
9.* hingga v2.0.3.RELEASE dan menggunakan 10.* setelahnya. Bagaimanapun, ketergantungan sudah memiliki versi feign-form
yang sesuai, lihat pom ketergantungan, jadi Anda tidak perlu menentukannya secara terpisah;
spring-cloud-starter-feign
adalah ketergantungan yang tidak digunakan lagi dan selalu menggunakan versi OpenFeign
9.* .
Tambahkan FormEncoder
ke Feign.Builder
Anda seperti ini:
SomeApi github = Feign . builder ()
. encoder ( new FormEncoder ())
. target ( SomeApi . class , "http://api.some.org" );
Selain itu, Anda juga dapat mendekorasi encoder yang sudah ada, misalnya JSONEncoder seperti ini:
SomeApi github = Feign . builder ()
. encoder ( new FormEncoder ( new JacksonEncoder ()))
. target ( SomeApi . class , "http://api.some.org" );
Dan gunakan keduanya bersama-sama:
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 );
}
Anda dapat menentukan dua jenis formulir pengkodean berdasarkan header Content-Type
.
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 ;
}
}
Pada contoh di atas, metode sendPhoto
menggunakan parameter photo
menggunakan tiga tipe berbeda yang didukung.
File
akan menggunakan ekstensi File untuk mendeteksi Content-Type
;byte[]
akan menggunakan application/octet-stream
sebagai Content-Type
;FormData
akan menggunakan Content-Type
dan fileName
FormData
; FormData
adalah objek khusus yang membungkus byte[]
dan mendefinisikan Content-Type
dan fileName
seperti ini:
FormData formData = new FormData ( "image/png" , "filename.png" , myDataAsByteArray );
someApi . sendPhoto ( true , formData );
Anda juga dapat menggunakan Form Encoder dengan Spring MultipartFile
dan @FeignClient
.
Sertakan dependensi pada file pom.xml proyek Anda:
< 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 ));
}
}
}
Atau, jika Anda tidak memerlukan pembuat enkode standar Spring:
@ FeignClient (
name = "file-upload-service" ,
configuration = FileUploadServiceClient . MultipartSupportConfig . class
)
public interface FileUploadServiceClient extends IFileUploadServiceClient {
public class MultipartSupportConfig {
@ Bean
public Encoder feignFormEncoder () {
return new SpringFormEncoder ();
}
}
}
Terima kasih kepada tf-haotri-pham untuk fiturnya, yang memanfaatkan perpustakaan Apache commons-fileupload, yang menangani penguraian respons multipart. Bagian data tubuh disimpan sebagai array byte di memori.
Untuk menggunakan fitur ini, sertakan SpringManyMultipartFilesReader dalam daftar konverter pesan untuk Decoder dan minta klien Feign mengembalikan array MultipartFile:
@ 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 ;
}
});
}
}
}