Feign 의 실현 원 리 를 상세히 해석 하 다.
Feign 은 HTTP 가 요청 한 경량급 클 라 이언 트 프레임 워 크 입 니 다.인터페이스+주석 을 통 해 HTTP 요청 호출 을 시작 합 니 다.자바 에서 HTTP 요청 메 시 지 를 봉인 하 는 방식 으로 직접 호출 하 는 것 이 아 닙 니 다.서비스 소비 자 는 서비스 제공 자의 연결 을 받 은 다음 에 스토리 보드 로 컬 연결 을 옮 기 고 법 적 으로 스토리 보드 를 옮 기 며 실제 적 으로 원 격 요 구 를 보 냈 다.보다 편리 하고 우아 하 게 HTTP 기반 API 를 옮 겨 서 Spring Cloud 에서 해결 할 수 있 습 니 다.오픈 소스 프로젝트 주소:Feign공식 설명 은 다음 과 같 습 니 다.
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Feign
Feign 의 최 우선 목 표 는 HTTP 호출 의 복잡성 을 줄 이 는 것 이다.마이크로 서비스 가 호출 되 는 장면 에서 우 리 는 HTTP 프로 토 콜 을 기반 으로 하 는 서 비 스 를 많이 호출 합 니 다.만약 에 서비스 호출 이 HTTP 호출 서 비 스 를 제공 하 는 HTTP Client 프레임 워 크(예 를 들 어 Apache HttpComponnets,HttpURLConnection OkHttp 등)만 사용한다 면 우 리 는 어떤 문제 에 관심 을 가 져 야 합 니까?
이러한 HTTP 요청 프레임 워 크 에 비해 Feign 은 HTTP 요청 호출 프로 세 스 를 패키지 하고 사용자 에 게 인터페이스 프로 그래 밍 을 위 한 습관 을 강요 합 니 다(Feign 자체 가 인 터 페 이 스 를 위 한 것 이기 때 문 입 니 다).
실례
3.1 원생 사용 방식
Feign 의 GitHub 오픈 소스 프로젝트 를 가 져 오 는 Contributors 를 예 로 들 면,네 이 티 브 방식 으로 Feign 을 사용 하 는 절 차 는 다음 과 같다.(Gradle 을 사용 하여 의존 관 리 를 하 는 프로젝트 를 예 로 들 면)
첫 번 째 단계:관련 의존 도입:implementation'io.github.openfeign:feign-core:11.0'
프로젝트 의 build.gradle 파일 의존 성명 에 dependencies 에서 이 의존 성명 을 추가 하면 됩 니 다.
두 번 째 단계:HTTP 요청 인터페이스 설명
자바 의 인터페이스 와 Feign 의 네 이 티 브 주석@RequestLine 을 사용 하여 HTTP 요청 인 터 페 이 스 를 설명 합 니 다.여기 서 Feign 이 사용자 에 게 HTTP 호출 디 테 일 을 봉 하여 HTTP 호출 의 복잡성 을 크게 줄 이 고 인 터 페 이 스 를 정의 하면 됩 니 다.
세 번 째 단계:Feign 클 라 이언 트 초기 화 설정
마지막 설정 은 클 라 이언 트 를 초기 화 합 니 다.이 단 계 는 요청 주소,인 코딩(Encoder),디 코딩(Decoder)등 을 설정 합 니 다.
인 터 페 이 스 를 정의 하고 주해 방식 으로 인터페이스의 정 보 를 설명 하면 인터페이스 호출 을 시작 할 수 있다.마지막 요청 결 과 는 다음 과 같 습 니 다.
3.2 스프링 클 라 우 드 사용 방식 결합
마찬가지 로 Feign 의 GitHub 오픈 소스 프로젝트 를 가 져 온 Contributors 를 예 로 들 면 Spring Cloud 와 결합 한 사용 방식 은 다음 과 같다.
첫 번 째 단계:관련 starter 의존 도입:org.springframework.cloud:spring-cloud-starter-openfeign
프로젝트 의 build.gradle 파일 의존 성명 에 dependencies 에서 이 의존 성명 을 추가 하면 됩 니 다.
두 번 째 단계:프로젝트 의 시작 클래스 XXXApplication 에@EnableFeignClient 주 해 를 추가 하여 Feign 클 라 이언 트 기능 을 사용 합 니 다.
세 번 째 단계:HTTP 호출 인 터 페 이 스 를 만 들 고 성명@FeignClient 주 해 를 추가 합 니 다.
마지막 설정 은 클 라 이언 트 를 초기 화 합 니 다.이 단 계 는 요청 주소(url),인 코딩(Encoder),디 코딩(Decoder)등 을 설정 합 니 다.원래 사용 방식 과 달리 현재 저 희 는@FeignClient 를 통 해 설정 한 Feign 클 라 이언 트 속성 을 설명 하고 요청 한 URL 도 Spring MVC 에서 제공 하 는 설명 입 니 다.
테스트 클래스 는 다음 과 같다.
실행 결 과 는 다음 과 같 습 니 다.
@Autowired 를 통 해 방금 정 의 된 인 터 페 이 스 를 주입 한 다음 에 직접 사용 하여 HTTP 요청 을 할 수 있 습 니 다.사용 이 편리 하고 간결 하지 않 습 니까?
4.탐색 Feign
위의 첫 번 째 원생 이 사용 한 예 를 보면 인 터 페 이 스 는 구체 적 인 실현 유형 이 없 지만 테스트 유형 에서 인 터 페 이 스 를 직접 호출 하 는 방법 으로 인터페이스의 호출 을 완성 할 수 있다.우 리 는 자바 에서 인 터 페 이 스 를 직접 사용 할 수 없다 는 것 을 알 고 있다.이 는 Feign 이 뒤에서 인 터 페 이 스 를 묵묵히 생 성 한 대리 실현 유형 이 라 고 대담 하 게 추측 할 수 있 기 때문이다.방금 테스트 클래스 debug 에서 인터페이스 가 실제 사용 하 는 실현 클래스 가 무엇 인지 확인 할 수 있 습 니 다.
debug 결과 에서 알 수 있 듯 이 프레임 워 크 는 인터페이스의 프 록 시 구현 클래스 HardCodedTarget 의 대상$Proxy 14 를 생 성하 여 인터페이스 요청 호출 을 완 료 했 습 니 다.방금 추측 과 일치 합 니 다.Feign 은 주로 HTTP 요청 호출 을 봉 인 했 습 니 다.전체 구 조 는 다음 과 같 습 니 다.
테스트 클래스 코드 는 GitHub github=Feign.builder().target(GitHub.class,"https://api.github.com")에 만 있 습 니 다.Feign 프레임 워 크 의 기능 을 사 용 했 기 때문에 우 리 는 여기 서 소스 코드 를 깊이 들 어가 서 클릭 하면 Feign 추상 류 가 제공 하 는 방법 임 을 알 수 있 습 니 다.마찬가지 로 우 리 는 추상 류 도 초기 화 할 수 없다 는 것 을 알 기 때문에 서브 클래스 가 있 을 것 입 니 다.만약 에 위의 debug 코드 를 자세히 살 펴 보면 Reflective Feign 류 가 있 습 니 다.이 종 류 는 추상 류 Feign 의 하위 클래스 입 니 다.추상 적 인 feign.Feign 의 일부 소스 코드 는 다음 과 같 습 니 다.
public abstract class Feign {
...
public static Builder builder() {
return new Builder();
}
public abstract <T> T newInstance(Target<T> target);
public static class Builder {
...
private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
private Contract contract = new Contract.Default();
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
private boolean forceDecoding = false;
private List<Capability> capabilities = new ArrayList<>();
//
public Builder logLevel(Logger.Level logLevel) {
this.logLevel = logLevel;
return this;
}
// ( )
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
// Client( JDK HttpURLConnection)
public Builder client(Client client) {
this.client = client;
return this;
}
//
public Builder retryer(Retryer retryer) {
this.retryer = retryer;
return this;
}
//
public Builder encoder(Encoder encoder) {
this.encoder = encoder;
return this;
}
//
public Builder decoder(Decoder decoder) {
this.decoder = decoder;
return this;
}
// 404
public Builder decode404() {
this.decode404 = true;
return this;
}
//
public Builder errorDecoder(ErrorDecoder errorDecoder) {
this.errorDecoder = errorDecoder;
return this;
}
//
public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
this.requestInterceptors.clear();
for (RequestInterceptor requestInterceptor : requestInterceptors) {
this.requestInterceptors.add(requestInterceptor);
}
return this;
}
public <T> T target(Class<T> apiType, String url) {
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
}
...
}
방법 public T target(Class api Type,String url)에서 HardCodedTarget 대상 을 직접 만 든 것 을 볼 수 있 습 니 다.이 대상 도 위 debug 에서 보 이 는 대상 입 니 다.더 깊이 들 어가 면 feign.Feign 의 new Instance(Target target)방법 에 왔 습 니 다.추상 적 인 방법 입 니 다.사실은 현재 하위 클래스 Reflective Feign 에서 이 방법 은 인터페이스 에이전트 가 생 성 된 곳 입 니 다.다음은 소스 코드 를 통 해 논 리 를 실현 하 는 것 이 어떤 지 보 겠 습 니 다.
public class ReflectiveFeign extends Feign {
...
private final ParseHandlersByName targetToHandlersByName;
private final InvocationHandlerFactory factory;
private final QueryMapEncoder queryMapEncoder;
ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
QueryMapEncoder queryMapEncoder) {
this.targetToHandlersByName = targetToHandlersByName;
this.factory = factory;
this.queryMapEncoder = queryMapEncoder;
}
@SuppressWarnings("unchecked")
@Override
public <T> T newInstance(Target<T> target) {
// < # , MethodHandler>,key feign.Feign.configKey(Class targetType, Method method)
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
// Map<String, MethodHandler> Map<Method, MethodHandler>
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
//
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
// Object
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
// ( )
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// (e.g. GitHub.listContributors(String, String))
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// Feign InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// JDK (e.g. Github )
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
...
}
전체 절 차 는 방법 T new Instance(Target target)에서 FeignInvocation Handler 를 포함 하 는 대리 대상 을 생 성 하 는 것 입 니 다.FeignInvocation Handler 대상 은 맵다음은 MethodHandler 에 깊이 들 어가 방법 HTTP 요청 처 리 를 어떻게 완성 하 는 지 살 펴 보 겠 습 니 다.MethodHandler 는 하나의 인터페이스 가 feign.Invocation Handler Factory 인터페이스 에 정의 되 어 있 습 니 다(P.S.기초 지식 점,인 터 페 이 스 는 내부 인터페이스 에서 정의 할 수 있 습 니 다).두 가지 실현 유형 은 Default MethodHandler 와 Synchronous MethodHandler 입 니 다.첫 번 째 Default MethodHandler 는 인 터 페 이 스 를 처리 하 는 기본 적 인 방법 이 고,두 번 째 는 정상 적 인 인터페이스 방법 을 처리 하 는 것 이 며,일반적인 상황 에 서 는 이러한 종류 로 처리 된다.
final class SynchronousMethodHandler implements MethodHandler {
...
@Override
public Object invoke(Object[] argv) throws Throwable {
// RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
//
Retryer retryer = this.retryer.clone();
while (true) {
try {
//
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
//
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// RequestTemplate Request
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// client(Apache HttpComponnets、HttpURLConnection OkHttp ) HTTP , HttpURLConnection
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
//
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
...
}
이로써 Feign 의 핵심 실현 프로 세 스 소개 가 완료 되 었 습 니 다.코드 를 보면 feign.synchronousMethodHandler 의 조작 은 상대 적 으로 간단 합 니 다.주로 client 를 통 해 요청 을 완성 하고 응답 에 대한 디 코딩 과 이상 처리 작업 을 합 니 다.전체 절 차 는 다음 과 같 습 니 다.총화
Feign 은 우리 에 게 정 의 된 대상 인터페이스(예 를 들 어 GitHub)를 통 해 HardCodedTarget 형식의 프 록 시 대상 을 만 들 고 JDK 동적 프 록 시 로 이 루어 집 니 다.프 록 시 를 생 성 할 때 주석 에 따라 해당 하 는 맵
그 다음 에 호출 방법 에 따라 맵
이상 은 Feign 의 실현 원리 에 대한 상세 한 내용 입 니 다.더 많은 Feign 원리 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 해 주 십시오!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
웹을 지원하는 기술업무로 PUT 메소드에 대해 조사하고 있어, 구그한 것만으로는 납득할 수 있는 정보가 없었기 때문에 이 책을 구입하고 읽었습니다. 그래서 알고 싶었던 정보를 중심으로 기재하려고 합니다. 처음에 좋은 것을 쓰고 있습니...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.