Feign은 Retrofit, JAXRS-2.0 및 WebSocket에서 영감을 받은 Java-HTTP 클라이언트 바인더입니다. Feign의 첫 번째 목표는 ReSTfulness에 관계없이 Denominator를 HTTP API에 균일하게 바인딩하는 복잡성을 줄이는 것이었습니다.
Feign은 Jersey 및 CXF와 같은 도구를 사용하여 ReST 또는 SOAP 서비스용 Java 클라이언트를 작성합니다. 게다가 Feign을 사용하면 Apache HC와 같은 http 라이브러리 위에 자신만의 코드를 작성할 수 있습니다. Feign은 텍스트 기반 http API에 작성할 수 있는 사용자 정의 가능한 디코더 및 오류 처리를 통해 최소한의 오버헤드와 코드로 코드를 http API에 연결합니다.
Feign은 주석을 템플릿화된 요청으로 처리하여 작동합니다. 인수는 출력 전에 간단한 방식으로 이러한 템플릿에 적용됩니다. Feign은 텍스트 기반 API 지원으로 제한되어 있지만 요청 재생과 같은 시스템 측면을 대폭 단순화합니다. 또한 Feign을 사용하면 이를 알면 변환의 단위 테스트를 쉽게 수행할 수 있습니다.
Feign 10.x 이상은 Java 8을 기반으로 구축되었으며 Java 9, 10, 11에서 작동해야 합니다. JDK 6 호환성이 필요한 경우 Feign 9.x를 사용하세요.
이것은 feign이 제공하는 현재 주요 기능이 포함된 지도입니다.
API 클라이언트를 더 쉽게 만들기
Logger
API 리팩터링Logger
API를 리팩터링합니다. 이 모델은 Feign 자체에서 전체적으로 사용되며 Logger
사용 방법에 대한 보다 명확한 지침을 제공합니다.Retry
Retry
API를 리팩터링합니다. 이로 인해 이전 버전과 호환되지 않는 주요 변경 사항이 발생할 수 있습니다. CompletableFuture
를 통한 비동기 실행 지원Future
연결 및 실행자 관리를 허용합니다. 구현에는 이전 버전과 호환되지 않는 주요 변경 사항이 필요합니다 . 그러나 이 기능은 반응적 실행을 고려하기 전에 필요합니다.java.util.concurrent.Flow
사용하는 기본 구현을 고려하세요.feign 라이브러리는 Maven Central에서 사용할 수 있습니다.
< dependency >
< groupId >io.github.openfeign</ groupId >
< artifactId >feign-core</ artifactId >
< version >??feign.version??</ version >
</ dependency >
사용법은 일반적으로 정식 Retrofit 샘플을 적용한 것과 같습니다.
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 주석은 인터페이스 간의 Contract
과 기본 클라이언트의 작동 방식을 정의합니다. Feign의 기본 계약은 다음 주석을 정의합니다.
주석 | 인터페이스 대상 | 용법 |
---|---|---|
@RequestLine | 방법 | 요청에 대한 HttpMethod 및 UriTemplate 정의합니다. Expressions , 중괄호 {expression} 으로 묶인 값은 해당 @Param 주석이 달린 매개변수를 사용하여 확인됩니다. |
@Param | 매개변수 | 주석 값으로 제공된 이름으로 해당 템플릿 Expression 확인하는 데 사용되는 값을 갖는 템플릿 변수를 정의합니다. 값이 누락된 경우 바이트코드 메서드 매개변수 이름에서 이름을 가져오려고 시도합니다(코드가 -parameters 플래그로 컴파일된 경우). |
@Headers | 방법, 유형 | HeaderTemplate 을 정의합니다. UriTemplate 의 변형입니다. 해당 Expressions 해결하기 위해 @Param 주석이 달린 값을 사용합니다. Type 에서 사용하면 템플릿이 모든 요청에 적용됩니다. Method 에서 사용하면 템플릿은 주석이 달린 메서드에만 적용됩니다. |
@QueryMap | 매개변수 | 쿼리 문자열로 확장하기 위해 이름-값 쌍의 Map (POJO)을 정의합니다. |
@HeaderMap | 매개변수 | Http Headers 로 확장하기 위해 이름-값 쌍의 Map 정의합니다. |
@Body | 방법 | 해당 Expressions 확인하기 위해 @Param 주석이 달린 값을 사용하는 UriTemplate 및 HeaderTemplate 과 유사한 Template 정의합니다. |
요청 라인 재정의
요청의 대상을 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 );
Feign Expressions
URI 템플릿 - RFC 6570에 정의된 단순 문자열 표현식(레벨 1)을 나타냅니다. 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
템플릿은 다음을 지정하는 수준 1 템플릿에 대한 URI 템플릿 - RFC 6570 사양을 따릅니다.
@Param
주석을 통해 encoded
것으로 표시되지 않은 경우 pct로 인코딩됩니다.또한 수준 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 템플릿은 기본적으로 슬래시
/
문자를 인코딩하지 않습니다. 이 동작을 변경하려면@RequestLine
의decodeSlash
속성을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 인터페이스를 구성할 수 있습니다.
요청 설정의 경우 target()
에서 options(Request.Options options)
사용하여 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>
)로 정의되어 실행 전에 요청을 동적으로 검색하고 장식할 수 있습니다.
예를 들어, 다음 패턴은 ID 서비스의 현재 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와 함께 사용할 수 있는 인코더와 디코더가 포함되어 있습니다.
다음과 같이 Feign.Builder
에 GsonEncoder
및/또는 GsonDecoder
추가합니다.
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와 함께 사용할 수 있는 인코더와 디코더가 포함되어 있습니다.
다음과 같이 Feign.Builder
에 JacksonEncoder
및/또는 JacksonDecoder
추가합니다.
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" );
}
}
더 가벼운 Jackson Jr의 경우 Jackson Jr 모듈의 JacksonJrEncoder
및 JacksonJrDecoder
사용하십시오.
Moshi에는 JSON API와 함께 사용할 수 있는 인코더와 디코더가 포함되어 있습니다. 다음과 같이 Feign.Builder
에 MoshiEncoder
및/또는 MoshiDecoder
추가합니다.
GitHub github = Feign . builder ()
. encoder ( new MoshiEncoder ())
. decoder ( new MoshiDecoder ())
. target ( GitHub . class , "https://api.github.com" );
SaxDecoder를 사용하면 일반 JVM 및 Android 환경과 호환되는 방식으로 XML을 디코딩할 수 있습니다.
다음은 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와 함께 사용할 수 있는 인코더와 디코더가 포함되어 있습니다.
다음과 같이 Feign.Builder
에 JAXBEncoder
및/또는 JAXBDecoder
추가합니다.
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와 함께 사용할 수 있는 인코더와 디코더가 포함되어 있습니다.
이 모듈은 JAXB 및 SOAPMessage를 통해 SOAP Body 객체 인코딩 및 디코딩에 대한 지원을 추가합니다. 또한 SOAPFault를 처리하기 위해 SOAPFaultException
포착하면 되도록 원래의 javax.xml.ws.soap.SOAPFaultException
으로 래핑하여 SOAPFault 디코딩 기능을 제공합니다.
다음과 같이 Feign.Builder
에 SOAPEncoder
및/또는 SOAPDecoder
추가합니다.
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" );
}
}
주의: 오류 http 코드(4xx, 5xx, ...)와 함께 SOAP 오류가 응답으로 반환되는 경우 SOAPErrorDecoder
추가해야 할 수도 있습니다.
fastjson2에는 JSON API와 함께 사용할 수 있는 인코더와 디코더가 포함되어 있습니다.
다음과 같이 Feign.Builder
에 Fastjson2Encoder
및/또는 Fastjson2Decoder
추가하세요.
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 및 더 나은 네트워크 제어를 가능하게 합니다.
Feign과 함께 OkHttp를 사용하려면 클래스 경로에 OkHttp 모듈을 추가하세요. 그런 다음 OkHttpClient를 사용하도록 Feign을 구성합니다.
public class Example {
public static void main ( String [] args ) {
GitHub github = Feign . builder ()
. client ( new OkHttpClient ())
. target ( GitHub . class , "https://api.github.com" );
}
}
RibbonClient는 Feign 클라이언트의 URL 확인을 재정의하여 리본에서 제공하는 스마트 라우팅 및 탄력성 기능을 추가합니다.
통합하려면 리본 클라이언트 이름을 URL의 호스트 부분으로 전달해야 합니다(예: myAppProd
).
public class Example {
public static void main ( String [] args ) {
MyService api = Feign . builder ()
. client ( RibbonClient . create ())
. target ( MyService . class , "https://myAppProd" );
}
}
Http2Client는 Feign의 http 요청을 HTTP/2를 구현하는 Java11 New HTTP/2 클라이언트로 보냅니다.
Feign과 함께 새 HTTP/2 클라이언트를 사용하려면 Java SDK 11을 사용하세요. 그런 다음 Http2Client를 사용하도록 Feign을 구성하세요.
GitHub github = Feign . builder ()
. client ( new Http2Client ())
. target ( GitHub . class , "https://api.github.com" );
HystrixFeign은 Hystrix에서 제공하는 회로 차단기 지원을 구성합니다.
Feign과 함께 Hystrix를 사용하려면 클래스 경로에 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 등)를 쉽게 사용할 수 있습니다.
Feign과 함께 SLF4J를 사용하려면 SLF4J 모듈과 선택한 SLF4J 바인딩을 클래스 경로에 추가하세요. 그런 다음 Slf4jLogger를 사용하도록 Feign을 구성합니다.
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
빌더 메소드를 사용할 수 있습니다. 예제 사용 사례는 jsonp만 제공하는 API를 다루는 것입니다. 선택한 Json 디코더로 보내기 전에 jsonp를 풀어야 할 수도 있습니다.
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" );
}
}
요청 본문을 서버에 보내는 가장 간단한 방법은 주석이 없는 String
또는 byte[]
매개변수가 있는 POST
메서드를 정의하는 것입니다. 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의 일부 또는 클라이언트의 일부로 요청에 대한 설정 헤더를 지원합니다.
특정 인터페이스나 호출에 항상 특정 헤더 값이 설정되어야 하는 경우 헤더를 API의 일부로 정의하는 것이 좋습니다.
@Headers
주석을 사용하여 API 인터페이스 또는 메서드에 정적 헤더를 설정할 수 있습니다.
@ 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 );
}
헤더 필드 키와 값이 모두 동적이고 가능한 키의 범위를 미리 알 수 없으며 동일한 API/클라이언트의 다양한 메소드 호출마다 다를 수 있는 경우(예: "x-amz- Meta-*" 또는 "x-goog-meta-*"), 지도 매개변수에 HeaderMap
주석을 달아 지도의 콘텐츠를 헤더 매개변수로 사용하는 쿼리를 생성할 수 있습니다.
public interface Api {
@ RequestLine ( "POST /" )
void post ( @ HeaderMap Map < String , Object > headerMap );
}
이러한 접근 방식은 헤더 항목을 API의 일부로 지정하며 Feign 클라이언트를 구축할 때 사용자 정의가 필요하지 않습니다.
Target의 각 요청 메소드에 대한 헤더를 사용자 정의하기 위해 RequestInterceptor를 사용할 수 있습니다. RequestInterceptors는 Target 인스턴스 전체에서 공유될 수 있으며 스레드로부터 안전할 것으로 예상됩니다. RequestInterceptor는 Target의 모든 요청 메소드에 적용됩니다.
메소드별 사용자 정의가 필요한 경우 RequestInterceptor가 현재 메소드 메타데이터에 액세스할 수 없으므로 사용자 정의 Target이 필요합니다.
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 ));
}
}
이러한 접근 방식은 Feign 클라이언트가 구축될 때 설정되는 사용자 지정 RequestInterceptor
또는 Target
에 따라 달라지며 클라이언트별로 모든 API 호출에 헤더를 설정하는 방법으로 사용할 수 있습니다. 이는 클라이언트별로 모든 API 요청의 헤더에 인증 토큰을 설정하는 등의 작업을 수행하는 데 유용할 수 있습니다. API 호출을 호출하는 스레드에서 API 호출이 이루어질 때 메서드가 실행됩니다. 이를 통해 헤더는 호출 시 컨텍스트별 방식으로 동적으로 설정될 수 있습니다. 예를 들어 스레드 로컬 저장소를 사용하여 다음 작업을 수행할 수 있습니다. 호출하는 스레드에 따라 서로 다른 헤더 값을 설정합니다. 이는 요청에 대한 스레드별 추적 식별자 설정과 같은 작업에 유용할 수 있습니다.
빈 본문으로 요청할 때 Content-Length: 0
헤더를 지정하려면 시스템 속성 sun.net.http.allowRestrictedHeaders
true
로 설정해야 합니다.
그렇지 않은 경우 Content-Length
헤더가 추가되지 않습니다.
대부분의 경우 서비스에 대한 API는 동일한 규칙을 따릅니다. Feign은 단일 상속 인터페이스를 통해 이 패턴을 지원합니다.
다음 예를 고려해보세요:
interface BaseAPI {
@ RequestLine ( "GET /health" )
String health ();
@ RequestLine ( "GET /all" )
List < Entity > all ();
}
기본 메소드를 상속하여 특정 API를 정의하고 대상으로 지정할 수 있습니다.
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 > { }
Logger
설정하여 대상과 주고받는 http 메시지를 기록할 수 있습니다. 이를 수행하는 가장 쉬운 방법은 다음과 같습니다.
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 );
}
Map 매개변수에 QueryMap
으로 주석을 달아 지도의 내용을 쿼리 매개변수로 사용하는 쿼리를 생성할 수 있습니다.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap Map < String , Object > queryMap );
}
이는 QueryMapEncoder
를 사용하여 POJO 객체에서 쿼리 매개변수를 생성하는 데에도 사용될 수 있습니다.
public interface Api {
@ RequestLine ( "GET /find" )
V find ( @ QueryMap CustomPojo customPojo );
}
이러한 방식으로 사용하면 사용자 정의 QueryMapEncoder
를 지정하지 않고 멤버 변수 이름을 쿼리 매개변수 이름으로 사용하여 쿼리 맵이 생성됩니다. @Param
주석을 사용하여 CustomPojo
의 특정 필드에 주석을 달아 쿼리 매개변수에 다른 이름을 지정할 수 있습니다. 다음 POJO는 "/find?name={name}&number={number}®ion_id={regionId}"의 쿼리 매개변수를 생성합니다(포함된 쿼리 매개변수의 순서는 보장되지 않으며 평소와 같이 값이 null인 경우 생략).
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으로 개체에 주석을 추가할 때 기본 인코더는 리플렉션을 사용하여 제공된 개체 필드를 검사하여 개체 값을 쿼리 문자열로 확장합니다. Java Beans API에 정의된 대로 getter 및 setter 메소드를 사용하여 쿼리 문자열을 작성하려면 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" );
}
}
2xx 범위가 아닌 HTTP 상태로 이어지는 모든 응답은 ErrorDecoder
의 decode
메서드를 트리거하여 응답을 처리하고 오류를 사용자 정의 예외로 래핑하거나 추가 처리를 수행할 수 있도록 합니다. 요청을 다시 시도하려면 RetryableException
을 발생시키세요. 그러면 등록된 Retryer
호출됩니다.
기본적으로 Feign은 HTTP 메소드에 관계없이 IOException
을 자동으로 재시도하여 일시적인 네트워크 관련 예외 및 ErrorDecoder
에서 발생한 모든 RetryableException
으로 처리합니다. 이 동작을 맞춤설정하려면 빌더를 통해 맞춤 Retryer
인스턴스를 등록하세요.
다음 예에서는 401 응답을 받았을 때 토큰을 새로 고치고 ErrorDecoder
및 Retryer
사용하여 재시도하는 방법을 보여줍니다.
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
continueOrPropagate(RetryableException e);
메소드에서 true
또는 false
반환하여 재시도가 발생해야 하는지 여부를 결정하는 역할을 담당합니다. 각 Client
실행에 대해 Retryer
인스턴스가 생성되므로 원하는 경우 각 요청 사이의 상태를 유지할 수 있습니다.
재시도가 실패했다고 판단되면 마지막 RetryException
발생합니다. 재시도 실패의 원인이 된 원래 원인을 발생시키려면 exceptionPropagationPolicy()
옵션을 사용하여 Feign 클라이언트를 빌드하세요.
오류가 될 수 있는 것을 성공으로 처리하고 예외를 발생시키는 대신 결과를 반환해야 하는 경우 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" );
}
}
기본적으로 feign은 측정항목을 수집하지 않습니다.
그러나 모든 가짜 클라이언트에 메트릭 수집 기능을 추가하는 것은 가능합니다.
메트릭 기능은 사용자가 요청/응답 수명주기에 대한 통찰력을 얻기 위해 활용할 수 있는 최고 수준의 메트릭 API를 제공합니다.
측정항목 모듈에 대한 참고 사항 :
모든 메트릭 통합은 별도의 모듈에 내장되어 있으며
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 클라이언트는 기본 API에 의해 명시적으로 정의되지 않은 논리를 포함할 수 있습니다. 예를 들어 정적 메서드를 사용하면 공통 클라이언트 빌드 구성을 쉽게 지정할 수 있습니다. 기본 메소드를 사용하여 쿼리를 작성하거나 기본 매개변수를 정의할 수 있습니다.
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에는 메서드가 CompletableFuture
인스턴스를 반환할 수 있도록 하는 새로운 빌더 AsyncFeign
도입되었습니다.
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 + ")" );
}
}
}
초기 구현에는 2개의 비동기 클라이언트가 포함됩니다.
AsyncClient.Default
AsyncApacheHttp5Client
호환되지 않는 바이너리를 방지하려면 모든 가짜 라이브러리를 동일한 버전으로 유지하는 것이 필수적입니다. 외부 종속성을 사용할 때 하나의 버전만 존재하는지 확인하는 것이 까다로울 수 있습니다.
이를 염두에 두고 feign 빌드는 모든 feign-*
모듈의 버전을 잠그는 feign-bom
이라는 모듈을 생성합니다.
BOM은 유효하고 함께 작동하도록 테스트된 종속성 버전을 그룹화하는 특수 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 >
이 모듈은 application/x-www-form-urlencoded 및 multipart/form-data 양식 인코딩에 대한 지원을 추가합니다.
앱에 대한 종속성을 포함합니다.
메이븐 :
< 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
릴리스는 OpenFeign
9.* 버전에서 작동합니다.feign-form
버전 3.5.0 부터 모듈은 OpenFeign
10.1.0 버전 이상에서 작동합니다.중요: 이전 버전과의 호환성이 없으며 3.5.0 이후의
feign-form
버전이 10.* 이전의OpenFeign
에서 작동한다는 보장도 없습니다.OpenFeign
10번째 릴리스에서 리팩터링되었으므로 가장 좋은 접근 방식은 최신OpenFeign
및feign-form
버전을 사용하는 것입니다.
참고:
spring-cloud-openfeign은 v2.0.3.RELEASE 까지는 OpenFeign
9.*를 사용하고 이후에는 10.*을 사용합니다. 어쨌든 종속성은 이미 적합한 feign-form
버전을 가지고 있습니다. 종속성 폼을 참조하세요. 따라서 별도로 지정할 필요가 없습니다.
spring-cloud-starter-feign
더 이상 사용되지 않는 종속성이며 항상 OpenFeign
의 9.* 버전을 사용합니다.
다음과 같이 Feign.Builder
에 FormEncoder
추가합니다.
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
File의 확장자를 사용하여 Content-Type
감지합니다.byte[]
application/octet-stream
Content-Type
으로 사용합니다.FormData
FormData
의 Content-Type
및 fileName
사용합니다. FormData
는 byte[]
래핑하고 다음과 같이 Content-Type
및 fileName
정의하는 사용자 정의 개체입니다.
FormData formData = new FormData ( "image/png" , "filename.png" , myDataAsByteArray );
someApi . sendPhoto ( true , formData );
Spring MultipartFile
및 @FeignClient
와 함께 Form Encoder를 사용할 수도 있습니다.
프로젝트의 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 ();
}
}
}
멀티파트 응답의 구문 분석을 처리하는 Apache commons-fileupload 라이브러리를 사용하는 기능을 제공한 tf-haotri-pham에게 감사드립니다. 본문 데이터 부분은 메모리에 바이트 배열로 보관됩니다.
이 기능을 사용하려면 Decoder의 메시지 변환기 목록에 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 ;
}
});
}
}
}