Feign عبارة عن أداة ربط عميل Java إلى HTTP مستوحاة من Retrofit وJAXRS-2.0 وWebSocket. كان الهدف الأول لـ Feign هو تقليل تعقيد ربط المقام بشكل موحد بواجهات برمجة تطبيقات HTTP بغض النظر عن ReSTness.
يستخدم Feign أدوات مثل Jersey وCXF لكتابة عملاء Java لخدمات ReST أو SOAP. علاوة على ذلك، يسمح لك Feign بكتابة التعليمات البرمجية الخاصة بك أعلى مكتبات http مثل Apache HC. يقوم Feign بتوصيل التعليمات البرمجية الخاصة بك إلى واجهات برمجة تطبيقات http مع الحد الأدنى من الحمل والتعليمات البرمجية عبر وحدات فك التشفير القابلة للتخصيص ومعالجة الأخطاء، والتي يمكن كتابتها على أي واجهة برمجة تطبيقات http قائمة على النص.
يعمل Feign عن طريق معالجة التعليقات التوضيحية في طلب مقولب. يتم تطبيق الوسيطات على هذه القوالب بطريقة مباشرة قبل الإخراج. على الرغم من أن Feign يقتصر على دعم واجهات برمجة التطبيقات المستندة إلى النص، إلا أنه يبسط بشكل كبير جوانب النظام مثل إعادة تشغيل الطلبات. علاوة على ذلك، فإن Feign يجعل من السهل وحدة اختبار تحويلاتك مع العلم بذلك.
تم تصميم Feign 10.x والإصدارات الأحدث على Java 8 ويجب أن تعمل على Java 9 و10 و11. بالنسبة لأولئك الذين يحتاجون إلى التوافق مع JDK 6، يرجى استخدام Feign 9.x
هذه خريطة تحتوي على الميزات الرئيسية الحالية المقدمة من feign:
جعل عملاء API أسهل
Logger
Logger
لتلتزم بشكل أقرب بأطر العمل مثل SLF4J التي توفر نموذجًا عقليًا مشتركًا للتسجيل داخل Feign. سيتم استخدام هذا النموذج بواسطة Feign نفسها طوال الوقت وسيوفر توجيهًا أكثر وضوحًا حول كيفية استخدام Logger
.Retry
بناء واجهة برمجة التطبيقاتRetry
API لدعم الشروط التي يقدمها المستخدم والتحكم بشكل أفضل في سياسات التراجع. قد يؤدي هذا إلى تغييرات انقطاع غير متوافقة مع الإصدارات السابقة CompletableFuture
Future
وإدارة المنفذ لدورة حياة الطلب/الاستجابة. سيتطلب التنفيذ إجراء تغييرات انقطاعية غير متوافقة مع الإصدارات السابقة . ومع ذلك، فإن هذه الميزة مطلوبة قبل النظر في التنفيذ التفاعلي.java.util.concurrent.Flow
.مكتبة التظاهر متاحة من Maven Central.
< dependency >
< groupId >io.github.openfeign</ groupId >
< artifactId >feign-core</ artifactId >
< version >??feign.version??</ version >
</ dependency >
عادةً ما يبدو الاستخدام على هذا النحو، وهو تعديل لنموذج التحديث الأساسي.
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 + ")" );
}
}
}
تحدد التعليقات التوضيحية المخادعة Contract
بين الواجهة وكيفية عمل العميل الأساسي. يحدد عقد Feign الافتراضي التعليقات التوضيحية التالية:
تعليق توضيحي | هدف الواجهة | الاستخدام |
---|---|---|
@RequestLine | طريقة | يحدد HttpMethod و UriTemplate للطلب. يتم حل Expressions والقيم الملتفة بين الأقواس المتعرجة {expression} باستخدام المعلمات المشروحة @Param المقابلة لها. |
@Param | المعلمة | يحدد متغير القالب، الذي سيتم استخدام قيمته لحل القالب المقابل Expression ، بالاسم المقدم كقيمة تعليق توضيحي. إذا كانت القيمة مفقودة، فستحاول الحصول على الاسم من اسم معلمة أسلوب bytecode (إذا تم تجميع الكود باستخدام علامة -parameters ). |
@Headers | الطريقة، النوع | يحدد HeaderTemplate ؛ الاختلاف على UriTemplate . يستخدم القيم المشروحة @Param لحل Expressions المقابلة. عند استخدامه على Type ، سيتم تطبيق القالب على كل طلب. عند استخدامه على Method ، سيتم تطبيق القالب فقط على الطريقة المشروحة. |
@QueryMap | المعلمة | يحدد Map لأزواج قيمة الاسم، أو POJO، للتوسيع إلى سلسلة استعلام. |
@HeaderMap | المعلمة | يحدد Map لأزواج قيمة الاسم، للتوسع في Http Headers |
@Body | طريقة | تعريف Template ، مشابه لـ UriTemplate و HeaderTemplate ، يستخدم القيم المشروحة @Param لحل Expressions المقابلة. |
تجاوز خط الطلب
إذا كانت هناك حاجة لتوجيه طلب إلى مضيف مختلف عن ذلك الذي تم توفيره عند إنشاء عميل Feign، أو كنت ترغب في توفير مضيف مستهدف لكل طلب، فقم بتضمين معلمة
java.net.URI
وسيستخدم Feign تلك القيمة كهدف الطلب.@ RequestLine ( "POST /repos/{owner}/{repo}/issues" ) void createIssue ( URI host , Issue issue , @ Param ( "owner" ) String owner , @ Param ( "repo" ) String repo );
تمثل Expressions
الزائفة تعبيرات سلسلة بسيطة (المستوى 1) كما هو محدد بواسطة قالب URI - RFC 6570. يتم توسيع Expressions
باستخدام معلمات طريقة Param
المشروحة المقابلة لها.
مثال
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" );
}
}
يجب أن تكون التعبيرات محاطة بأقواس متعرجة {}
ويمكن أن تحتوي على أنماط تعبير عادية، مفصولة بنقطتين :
لتقييد القيم التي تم حلها. يجب أن يكون owner
المثال أبجديًا. {owner:[a-zA-Z]*}
تتبع قوالب RequestLine
و QueryMap
قالب URI - مواصفات RFC 6570 لقوالب المستوى 1، والتي تحدد ما يلي:
encoded
عبر تعليق توضيحي @Param
.لدينا أيضًا دعم محدود للمستوى 3، تعبيرات نمط المسار، مع القيود التالية:
أمثلة:
{;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 ;
}
}
إذا تم تعريف owners
في المثال أعلاه على أنهم Matt, Jeff, Susan
، فسيتم توسيع معرف الموارد المنتظم (URI) إلى /repos;owners=Matt;owners=Jeff;owners=Susan
لمزيد من المعلومات، راجع RFC 6570، القسم 3.2.7
التعبيرات غير المحددة هي تعبيرات حيث تكون قيمة التعبير null
بشكل صريح أو لا يتم توفير أي قيمة. لكل قالب URI - RFC 6570، من الممكن توفير قيمة فارغة للتعبير. عندما يحل Feign تعبيرًا، فإنه يحدد أولاً ما إذا كانت القيمة محددة أم لا، وإذا كانت كذلك فستبقى معلمة الاستعلام. إذا كان التعبير غير محدد، تتم إزالة معلمة الاستعلام. انظر أدناه للحصول على تفاصيل كاملة.
سلسلة فارغة
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
parameters . put ( "param" , "" );
this . demoClient . test ( parameters );
}
نتيجة
http://localhost:8080/test?param=
مفتقد
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
this . demoClient . test ( parameters );
}
نتيجة
http://localhost:8080/test
غير محدد
public void test () {
Map < String , Object > parameters = new LinkedHashMap <>();
parameters . put ( "param" , null );
this . demoClient . test ( parameters );
}
نتيجة
http://localhost:8080/test
راجع الاستخدام المتقدم لمزيد من الأمثلة.
ماذا عن القطع المائلة؟
/
لا تقوم قوالب @RequestLine بتشفير الشرطة المائلة
/
الأحرف بشكل افتراضي. لتغيير هذا السلوك، قم بتعيين خاصيةdecodeSlash
في@RequestLine
إلىfalse
.
ماذا عن زائد؟
+
وفقًا لمواصفات URI، يُسمح بعلامة
+
في كل من المسار وقطاعات الاستعلام الخاصة بـ URI، ومع ذلك، قد تكون معالجة الرمز في الاستعلام غير متسقة. في بعض الأنظمة القديمة، تكون علامة+
مكافئة للمسافة. يتبع Feign نهج الأنظمة الحديثة، حيث لا ينبغي أن يمثل الرمز+
مسافة ويتم ترميزه بشكل صريح كـ%2B
عند العثور عليه في سلسلة استعلام.إذا كنت ترغب في استخدام
+
كمسافة، فاستخدم الحرفيحرف أو تشفير القيمة مباشرة كـ
%20
يحتوي التعليق التوضيحي @Param
على expander
خاصية اختياري يسمح بالتحكم الكامل في توسيع المعلمة الفردية. يجب أن تشير خاصية expander
إلى فئة تقوم بتنفيذ واجهة Expander
:
public interface Expander {
String expand ( Object value );
}
تلتزم نتيجة هذه الطريقة بنفس القواعد المذكورة أعلاه. إذا كانت النتيجة null
أو سلسلة فارغة، فسيتم حذف القيمة. إذا لم تكن القيمة مشفرة بـ PCT، فستكون كذلك. راجع توسيع @Param المخصص لمزيد من الأمثلة.
تتبع قوالب Headers
و HeaderMap
نفس القواعد المتبعة في طلب توسيع معلمة الطلب مع التعديلات التالية:
راجع الرؤوس للحصول على أمثلة.
ملاحظة حول معلمات
@Param
وأسمائها :سيتم حل كافة التعبيرات التي تحمل الاسم نفسه، بغض النظر عن موضعها في
@RequestLine
أو@QueryMap
أو@BodyTemplate
أو@Headers
بنفس القيمة. في المثال التالي، سيتم استخدام قيمةcontentType
لحل تعبير الرأس والمسار:public interface ContentService { @ RequestLine ( "GET /api/documents/{contentType}" ) @ Headers ( "Accept: {contentType}" ) String getDocumentByType ( @ Param ( "contentType" ) String type ); }ضع ذلك في الاعتبار عند تصميم واجهاتك.
تتبع قوالب Body
نفس القواعد المتبعة في طلب توسيع معلمة الطلب مع التعديلات التالية:
Encoder
قبل وضعها في نص الطلب.Content-Type
. انظر قوالب الجسم للحصول على أمثلة. لدى Feign العديد من الجوانب التي يمكن تخصيصها.
في الحالات البسيطة، يمكنك استخدام Feign.builder()
لإنشاء واجهة API باستخدام مكوناتك المخصصة.
لإعداد الطلب، يمكنك استخدام options(Request.Options options)
على target()
لتعيين ConnectTimeout، وconnectTimeoutUnit، وreadTimeout، وreadTimeoutUnit، وfollowRedirects.
على سبيل المثال:
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 إنتاج واجهات API متعددة. يتم تعريفها على أنها Target<T>
(افتراضي HardCodedTarget<T>
)، والتي تسمح بالاكتشاف الديناميكي للطلبات وتزيينها قبل التنفيذ.
على سبيل المثال، قد يقوم النمط التالي بتزيين كل طلب بعنوان URL الحالي ورمز المصادقة المميز من خدمة الهوية.
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 أمثلة لعملاء GitHub وWikipedia. يمكن أيضًا إلغاء مشروع القاسم من أجل Feign عمليًا. على وجه الخصوص، انظر إلى مثاله الخفي.
تعتزم Feign العمل بشكل جيد مع أدوات أخرى مفتوحة المصدر. الوحدات مرحب بها للتكامل مع مشاريعك المفضلة!
يشتمل Gson على برنامج تشفير ووحدة فك تشفير يمكنك استخدامه مع JSON API.
أضف GsonEncoder
و/أو GsonDecoder
إلى Feign.Builder
الخاص بك كما يلي:
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 جهاز تشفير ووحدة فك ترميز يمكنك استخدامه مع JSON API.
أضف JacksonEncoder
و/أو JacksonDecoder
إلى Feign.Builder
الخاص بك كما يلي:
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" );
}
}
للحصول على وزن جاكسون جونيور الأخف، استخدم JacksonJrEncoder
و JacksonJrDecoder
من وحدة Jackson Jr.
يتضمن Moshi برنامج تشفير ووحدة فك تشفير يمكنك استخدامه مع JSON API. أضف MoshiEncoder
و/أو MoshiDecoder
إلى Feign.Builder
الخاص بك كما يلي:
GitHub github = Feign . builder ()
. encoder ( new MoshiEncoder ())
. decoder ( new MoshiDecoder ())
. target ( GitHub . class , "https://api.github.com" );
يتيح لك SaxDecoder فك تشفير XML بطريقة متوافقة مع بيئات JVM العادية وأيضًا Android.
فيما يلي مثال لكيفية تكوين تحليل استجابة 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 برنامج تشفير وفك تشفير يمكنك استخدامه مع XML API.
قم بإضافة JAXBEncoder
و/أو JAXBDecoder
إلى Feign.Builder
الخاص بك كما يلي:
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 أداة تشفير ووحدة فك ترميز يمكنك استخدامها مع XML API.
تضيف هذه الوحدة دعمًا لتشفير وفك تشفير كائنات SOAP Body عبر JAXB وSOAPMessage. كما أنه يوفر إمكانات فك تشفير SOAPFault عن طريق تغليفها في javax.xml.ws.soap.SOAPFaultException
الأصلي، بحيث ستحتاج فقط إلى التقاط SOAPFaultException
للتعامل مع SOAPFault.
أضف SOAPEncoder
و/أو SOAPDecoder
إلى Feign.Builder
الخاص بك كما يلي:
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" );
}
}
ملاحظة: قد تحتاج أيضًا إلى إضافة SOAPErrorDecoder
إذا تم إرجاع أخطاء SOAP استجابةً لرموز http الخاصة بالخطأ (4xx، 5xx، ...)
يتضمن fastjson2 أداة تشفير ووحدة فك ترميز يمكنك استخدامها مع واجهة برمجة تطبيقات JSON.
أضف Fastjson2Encoder
و/أو Fastjson2Decoder
إلى Feign.Builder
الخاص بك كما يلي:
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 معالجة التعليقات التوضيحية ليستخدم بدلاً من ذلك تلك القياسية التي توفرها مواصفات JAX-RS. ويستهدف هذا حاليًا المواصفات 1.1.
إليك المثال أعلاه الذي تمت إعادة كتابته لاستخدام 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 بتوجيه طلبات Feign http إلى OkHttp، مما يتيح SPDY والتحكم بشكل أفضل في الشبكة.
لاستخدام OkHttp مع Feign، أضف وحدة OkHttp إلى مسار الفصل الدراسي الخاص بك. ثم قم بتكوين Feign لاستخدام 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 دقة عنوان URL لعميل Feign، مما يضيف إمكانيات التوجيه والمرونة الذكية التي يوفرها Ribbon.
يتطلب التكامل منك تمرير اسم عميل الشريط الخاص بك باعتباره الجزء المضيف لعنوان url، على سبيل المثال myAppProd
.
public class Example {
public static void main ( String [] args ) {
MyService api = Feign . builder ()
. client ( RibbonClient . create ())
. target ( MyService . class , "https://myAppProd" );
}
}
يقوم Http2Client بتوجيه طلبات http الخاصة بـ Feign إلى Java11 New HTTP/2 Client الذي ينفذ HTTP/2.
لاستخدام عميل HTTP/2 الجديد مع Feign، استخدم Java SDK 11. ثم قم بتكوين Feign لاستخدام Http2Client:
GitHub github = Feign . builder ()
. client ( new Http2Client ())
. target ( GitHub . class , "https://api.github.com" );
يقوم HystrixFeign بتكوين دعم قاطع الدائرة الذي توفره Hystrix.
لاستخدام Hystrix مع Feign، أضف وحدة Hystrix إلى مسار الفصل الدراسي الخاص بك. ثم استخدم منشئ HystrixFeign
:
public class Example {
public static void main ( String [] args ) {
MyService api = HystrixFeign . builder (). target ( MyService . class , "https://myAppProd" );
}
}
يسمح SLF4JModule بتوجيه تسجيل Feign إلى SLF4J، مما يسمح لك بسهولة استخدام واجهة التسجيل الخلفية من اختيارك (Logback، Log4J، إلخ.)
لاستخدام SLF4J مع Feign، أضف كلاً من وحدة SLF4J وربط SLF4J من اختيارك إلى مسار الفصل الخاص بك. ثم قم بتكوين Feign لاستخدام 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()
بتحديد تكوين إضافي مثل كيفية فك تشفير الاستجابة.
إذا قامت أي طرق في واجهتك بإرجاع أنواع إلى جانب Response
أو String
أو byte[]
أو void
، فستحتاج إلى تكوين وحدة Decoder
غير افتراضية.
فيما يلي كيفية تكوين فك تشفير JSON (باستخدام ملحق 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" );
}
}
إذا كنت بحاجة إلى معالجة الاستجابة مسبقًا قبل تسليمها إلى وحدة فك الترميز، فيمكنك استخدام طريقة منشئ mapAndDecode
. مثال على حالة الاستخدام هو التعامل مع واجهة برمجة التطبيقات (API) التي تخدم jsonp فقط، وقد تحتاج إلى إلغاء تغليف jsonp قبل إرساله إلى وحدة فك ترميز Json التي تختارها:
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" );
}
}
إذا أعادت أي طرق في واجهتك النوع Stream
، فستحتاج إلى تكوين StreamDecoder
.
فيما يلي كيفية تكوين وحدة فك ترميز الدفق بدون وحدة فك ترميز المفوض:
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" );
}
}
فيما يلي كيفية تكوين وحدة فك ترميز الدفق مع وحدة فك ترميز المفوض:
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" );
}
}
إن أبسط طريقة لإرسال نص الطلب إلى الخادم هي تحديد طريقة POST
التي تحتوي على معلمة String
أو byte[]
بدون أي تعليقات توضيحية عليها. ستحتاج على الأرجح إلى إضافة رأس 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 " }" );
}
}
من خلال تكوين Encoder
، يمكنك إرسال نص طلب آمن النوع. فيما يلي مثال باستخدام ملحق 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" ));
}
}
يشير التعليق التوضيحي @Body
إلى قالب للتوسيع باستخدام المعلمات الموضحة بـ @Param
. ستحتاج على الأرجح إلى إضافة رأس 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 رؤوس الإعدادات عند الطلب إما كجزء من واجهة برمجة التطبيقات أو كجزء من العميل اعتمادًا على حالة الاستخدام.
في الحالات التي يجب أن تحتوي فيها الواجهات أو الاستدعاءات المحددة دائمًا على قيم رأس معينة، فمن المنطقي تحديد الرؤوس كجزء من واجهة برمجة التطبيقات.
يمكن تعيين الرؤوس الثابتة على واجهة API أو طريقة باستخدام التعليق التوضيحي @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 );
}
يمكن للطرق تحديد المحتوى الديناميكي للرؤوس الثابتة باستخدام التوسع المتغير في @Headers
.
public interface Api {
@ RequestLine ( "POST /" )
@ Headers ( "X-Ping: {token}" )
void post ( @ Param ( "token" ) String token );
}
في الحالات التي تكون فيها مفاتيح حقل الرأس وقيمه ديناميكية ولا يمكن معرفة نطاق المفاتيح المحتملة مسبقًا وقد يختلف بين استدعاءات الطرق المختلفة في نفس واجهة برمجة التطبيقات/العميل (على سبيل المثال، حقول رأس بيانات التعريف المخصصة مثل "x-amz-" meta-*" أو "x-goog-meta-*")، يمكن إضافة تعليقات توضيحية لمعلمة الخريطة باستخدام HeaderMap
لإنشاء استعلام يستخدم محتويات الخريطة كمعلمات رأسية.
public interface Api {
@ RequestLine ( "POST /" )
void post ( @ HeaderMap Map < String , Object > headerMap );
}
تحدد هذه الأساليب إدخالات الرأس كجزء من واجهة برمجة التطبيقات ولا تتطلب أي تخصيصات عند إنشاء عميل Feign.
لتخصيص الرؤوس لكل طريقة طلب على الهدف، يمكن استخدام RequestInterceptor. يمكن مشاركة RequestInterceptors عبر المثيلات المستهدفة ومن المتوقع أن تكون آمنة لسلسلة الرسائل. يتم تطبيق RequestInterceptors على جميع طرق الطلب على الهدف.
إذا كنت بحاجة إلى تخصيص لكل طريقة، فستكون هناك حاجة إلى هدف مخصص، نظرًا لأن RequestInterceptor ليس لديه حق الوصول إلى البيانات التعريفية للطريقة الحالية.
للحصول على مثال لإعداد الرؤوس باستخدام RequestInterceptor
، راجع قسم Request Interceptors
.
يمكن تعيين الرؤوس كجزء من Target
مخصص.
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 ));
}
}
تعتمد هذه الأساليب على RequestInterceptor
أو Target
المخصص الذي يتم تعيينه على عميل Feign عند إنشائه ويمكن استخدامه كطريقة لتعيين الرؤوس على جميع استدعاءات واجهة برمجة التطبيقات على أساس كل عميل. يمكن أن يكون هذا مفيدًا للقيام بأشياء مثل تعيين رمز المصادقة في رأس جميع طلبات واجهة برمجة التطبيقات على أساس كل عميل. يتم تشغيل الأساليب عند إجراء استدعاء API على مؤشر الترابط الذي يستدعي استدعاء API، والذي يسمح بتعيين الرؤوس ديناميكيًا في وقت الاتصال وبطريقة خاصة بالسياق - على سبيل المثال، يمكن استخدام التخزين المحلي لمؤشر الترابط قم بتعيين قيم رأس مختلفة اعتمادًا على مؤشر الترابط الذي يتم استدعاؤه، والذي يمكن أن يكون مفيدًا لأشياء مثل تعيين معرفات التتبع الخاصة بمؤشر الترابط للطلبات.
لتحديد Content-Length: 0
رأس عند تقديم طلب بنص فارغ، يجب تعيين خاصية النظام sun.net.http.allowRestrictedHeaders
على true
إذا لم يكن الأمر كذلك، فلن تتم إضافة رأس Content-Length
.
في كثير من الحالات، تتبع واجهات برمجة التطبيقات الخاصة بالخدمة نفس الاتفاقيات. يدعم Feign هذا النمط عبر واجهات الوراثة الفردية.
خذ بعين الاعتبار المثال:
interface BaseAPI {
@ RequestLine ( "GET /health" )
String health ();
@ RequestLine ( "GET /all" )
List < Entity > all ();
}
يمكنك تحديد واستهداف واجهة برمجة تطبيقات معينة، من خلال وراثة الأساليب الأساسية.
interface CustomAPI extends BaseAPI {
@ RequestLine ( "GET /custom" )
String custom ();
}
وفي كثير من الحالات، تكون تمثيلات الموارد متسقة أيضًا. لهذا السبب، يتم دعم معلمات النوع على واجهة API الأساسية.
@ 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 > { }
يمكنك تسجيل رسائل http المتجهة من وإلى الهدف عن طريق إعداد Logger
. إليك أسهل طريقة للقيام بذلك:
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" );
}
}
ملاحظة حول JavaLogger : تجنب استخدام مُنشئ
JavaLogger()
الافتراضي - تم وضع علامة عليه كمهمل وستتم إزالته قريبًا.
قد يكون SLF4JLogger (انظر أعلاه) مفيدًا أيضًا.
لتصفية المعلومات الحساسة مثل أساليب تجاوز التفويض أو الرموز المميزة shouldLogRequestHeader
أو shouldLogResponseHeader
.
عندما تحتاج إلى تغيير جميع الطلبات، بغض النظر عن هدفها، فستحتاج إلى تكوين RequestInterceptor
. على سبيل المثال، إذا كنت تعمل كوسيط، فقد ترغب في نشر رأس 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" );
}
}
مثال شائع آخر للمعترض هو المصادقة، مثل استخدام 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" );
}
}
يتم توسيع المعلمات المشروحة باستخدام Param
بناءً على toString
الخاصة بها. من خلال تحديد Param.Expander
مخصص، يمكن للمستخدمين التحكم في هذا السلوك، على سبيل المثال تنسيق التواريخ.
public interface Api {
@ RequestLine ( "GET /?since={date}" ) Result list ( @ Param ( value = "date" , expander = DateToMillis . class ) Date date );
}
يمكن إضافة تعليقات توضيحية لمعلمة الخريطة باستخدام QueryMap
لإنشاء استعلام يستخدم محتويات الخريطة كمعلمات الاستعلام الخاصة بها.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap Map < String , Object > queryMap );
}
يمكن استخدام هذا أيضًا لإنشاء معلمات الاستعلام من كائن POJO باستخدام QueryMapEncoder
.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap CustomPojo customPojo );
}
عند استخدامها بهذه الطريقة، دون تحديد QueryMapEncoder
مخصص، سيتم إنشاء خريطة الاستعلام باستخدام أسماء متغيرات الأعضاء كأسماء معلمات الاستعلام. يمكنك إضافة تعليق توضيحي إلى حقل معين في CustomPojo
باستخدام التعليق التوضيحي @Param
لتحديد اسم مختلف لمعلمة الاستعلام. سيقوم POJO التالي بإنشاء معلمات استعلام لـ "/find?name={name}&number={number}®ion_id={regionId}" (ترتيب معلمات الاستعلام المضمنة غير مضمون، وكالمعتاد، إذا كانت أي قيمة فارغة، فسيتم تركت).
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 ;
}
}
لإعداد QueryMapEncoder
مخصص:
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. queryMapEncoder ( new MyCustomQueryMapEncoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
عند إضافة تعليقات توضيحية إلى الكائنات باستخدام @QueryMap، يستخدم برنامج التشفير الافتراضي الانعكاس لفحص حقول الكائنات المتوفرة لتوسيع قيم الكائنات إلى سلسلة استعلام. إذا كنت تفضل إنشاء سلسلة الاستعلام باستخدام أساليب getter وsetter، كما هو محدد في Java Beans API، فيرجى استخدام BeanQueryMapEncoder
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. queryMapEncoder ( new BeanQueryMapEncoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
إذا كنت بحاجة إلى مزيد من التحكم في التعامل مع الاستجابات غير المتوقعة، فيمكن لمثيلات Feign تسجيل ErrorDecoder
مخصص عبر المُنشئ.
public class Example {
public static void main ( String [] args ) {
MyApi myApi = Feign . builder ()
. errorDecoder ( new MyErrorDecoder ())
. target ( MyApi . class , "https://api.hostname.com" );
}
}
جميع الاستجابات التي تؤدي إلى حالة HTTP ليست في النطاق 2xx ستؤدي إلى تشغيل طريقة decode
ErrorDecoder
، مما يسمح لك بمعالجة الاستجابة، أو تغليف الفشل في استثناء مخصص أو إجراء أي معالجة إضافية. إذا كنت تريد إعادة محاولة الطلب مرة أخرى، فاطرح RetryableException
. سيؤدي هذا إلى استدعاء Retryer
المسجلة.
افتراضيًا، سيعيد Feign محاولة IOException
تلقائيًا، بغض النظر عن طريقة HTTP، ويعاملها كاستثناءات عابرة مرتبطة بالشبكة، وأي RetryableException
يتم طرحه من ErrorDecoder
. لتخصيص هذا السلوك، قم بتسجيل مثيل Retryer
مخصص عبر المنشئ.
يوضح المثال التالي كيفية تحديث الرمز المميز وإعادة المحاولة باستخدام ErrorDecoder
و Retryer
عند تلقي استجابة 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
مسؤولة عن تحديد ما إذا كان يجب إجراء إعادة المحاولة عن طريق إرجاع إما true
أو false
من الطريقة continueOrPropagate(RetryableException e);
سيتم إنشاء مثيل Retryer
لكل عملية تنفيذ Client
، مما يسمح لك بالحفاظ على الحالة بين كل طلب إذا رغبت في ذلك.
إذا تم تحديد أن إعادة المحاولة غير ناجحة، فسيتم طرح RetryException
الأخير. لطرح السبب الأصلي الذي أدى إلى إعادة المحاولة غير الناجحة، أنشئ عميل Feign الخاص بك باستخدام خيار exceptionPropagationPolicy()
.
إذا كنت بحاجة إلى التعامل مع ما يمكن أن يكون خطأً على أنه نجاح وإرجاع نتيجة بدلاً من طرح استثناء، فيمكنك استخدام ResponseInterceptor
.
كمثال، يشتمل Feign على RedirectionInterceptor
بسيط يمكن استخدامه لاستخراج رأس الموقع من استجابات إعادة التوجيه.
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" );
}
}
افتراضيًا، لن يقوم التظاهر بجمع أي مقاييس.
ولكن من الممكن إضافة إمكانيات جمع المقاييس إلى أي عميل مزيف.
توفر Metric Capabilities واجهة برمجة تطبيقات Metrics من الدرجة الأولى يمكن للمستخدمين الاستفادة منها للحصول على نظرة ثاقبة لدورة حياة الطلب/الاستجابة.
ملاحظة حول وحدات المقاييس :
تم إنشاء جميع عمليات التكامل المترية في وحدات منفصلة وغير متوفرة في الوحدة
feign-core
. سوف تحتاج إلى إضافتها إلى التبعيات الخاصة بك.
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
}
}
قد تحتوي الواجهات التي يستهدفها Feign على أساليب ثابتة أو افتراضية (في حالة استخدام Java 8+). يسمح ذلك لعملاء Feign باحتواء منطق لم يتم تعريفه صراحةً بواسطة واجهة برمجة التطبيقات الأساسية. على سبيل المثال، تسهل الطرق الثابتة تحديد تكوينات بناء العميل الشائعة؛ يمكن استخدام الطرق الافتراضية لإنشاء استعلامات أو تحديد المعلمات الافتراضية.
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 منشئًا جديدًا AsyncFeign
يسمح للطرق بإرجاع مثيلات 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 + ")" );
}
}
}
يتضمن التنفيذ الأولي عميلين غير متزامنين:
AsyncClient.Default
AsyncApacheHttp5Client
يعد الاحتفاظ بجميع المكتبات الزائفة على نفس الإصدار أمرًا ضروريًا لتجنب الثنائيات غير المتوافقة. عند استخدام التبعيات الخارجية، قد يكون من الصعب التأكد من وجود إصدار واحد فقط.
مع أخذ ذلك في الاعتبار، يُنشئ feign build وحدة تسمى feign-bom
تقوم بتأمين الإصدارات لجميع وحدات feign-*
.
إن قائمة المواد عبارة عن ملف POM خاص يجمع إصدارات التبعية المعروفة بأنها صالحة وتم اختبارها للعمل معًا. سيؤدي هذا إلى تقليل معاناة المطورين من الاضطرار إلى اختبار توافق الإصدارات المختلفة وتقليل فرص عدم تطابق الإصدارات.
فيما يلي أحد الأمثلة على الشكل الذي يبدو عليه ملف BOM المزيف.
< 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 >
تضيف هذه الوحدة دعمًا لتشفير نماذج البيانات /التطبيقات/x-www-form-urlencoded والأجزاء المتعددة/النماذج .
قم بتضمين التبعية لتطبيقك:
مخضرم :
< dependencies >
...
< dependency >
< groupId >io.github.openfeign.form</ groupId >
< artifactId >feign-form</ artifactId >
< version >4.0.0</ version >
</ dependency >
...
</ dependencies >
غرادل :
compile ' io.github.openfeign.form:feign-form:4.0.0 '
يعتمد امتداد feign-form
على OpenFeign
وإصداراته الملموسة :
feign-form
قبل الإصدار 3.5.0 مع إصدارات OpenFeign
9.* ؛feign-form
، تعمل الوحدة مع إصدارات OpenFeign
10.1.0 والإصدارات الأحدث.هام: لا يوجد توافق مع الإصدارات السابقة ولا يوجد أي ضمان بأن إصدارات
feign-form
بعد 3.5.0 تعمل معOpenFeign
قبل 10.* . تمت إعادة هيكلةOpenFeign
في الإصدار العاشر، لذا فإن أفضل طريقة هي استخدام أحدث إصداراتOpenFeign
والإصداراتfeign-form
.
ملحوظات:
يستخدم Spring-cloud-openfeign OpenFeign
9.* حتى الإصدار 2.0.3.RELEASE ويستخدم 10.* بعد ذلك. على أي حال، تحتوي التبعية بالفعل على نسخة feign-form
مناسبة، راجع بوم التبعية، لذلك لا تحتاج إلى تحديدها بشكل منفصل؛
spring-cloud-starter-feign
هي تبعية مهملة وتستخدم دائمًا إصدارات OpenFeign
9.* .
أضف FormEncoder
إلى Feign.Builder
الخاص بك كما يلي:
SomeApi github = Feign . builder ()
. encoder ( new FormEncoder ())
. target ( SomeApi . class , "http://api.some.org" );
علاوة على ذلك، يمكنك تزيين برنامج التشفير الموجود، على سبيل المثال JsonEncoder، مثل هذا:
SomeApi github = Feign . builder ()
. encoder ( new FormEncoder ( new JacksonEncoder ()))
. target ( SomeApi . class , "http://api.some.org" );
واستخدمهما معًا:
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 );
}
يمكنك تحديد نوعين من نماذج الترميز بواسطة رأس 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 ;
}
}
في المثال أعلاه، يستخدم الأسلوب sendPhoto
المعلمة photo
باستخدام ثلاثة أنواع مختلفة مدعومة.
File
امتداد الملف للكشف عن Content-Type
؛byte[]
سيستخدم application/octet-stream
Content-Type
؛FormData
Content-Type
FormData
واسم fileName
؛ FormData
هو كائن مخصص يلتف byte[]
ويحدد Content-Type
واسم fileName
كما يلي:
FormData formData = new FormData ( "image/png" , "filename.png" , myDataAsByteArray );
someApi . sendPhoto ( true , formData );
يمكنك أيضًا استخدام Form Encoder مع Spring MultipartFile
و @FeignClient
.
قم بتضمين التبعيات في ملف pom.xml الخاص بمشروعك:
< 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 ));
}
}
}
أو، إذا لم تكن بحاجة إلى برنامج التشفير القياسي الخاص بـ 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 ();
}
}
}
شكرًا لـ tf-haotri-pham على ميزته، التي تستفيد من مكتبة Apache commons-fileupload، التي تتولى تحليل الاستجابة متعددة الأجزاء. يتم الاحتفاظ بأجزاء بيانات الجسم كمصفوفات بايت في الذاكرة.
لاستخدام هذه الميزة، قم بتضمين SpringManyMultipartFilesReader في قائمة محولات الرسائل لوحدة فك التشفير واطلب من عميل Feign إرجاع مصفوفة من ملفات 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 ;
}
});
}
}
}