Feign 원리 해석 (1) Feign 호출 대상의 생성
34125 단어 Feign 원리 해석
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 ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
public Builder logLevel(Logger.Level logLevel) {
this.logLevel = logLevel;
return this;
}
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
public Builder client(Client client) {
this.client = client;
return this;
}
public Builder retryer(Retryer retryer) {
this.retryer = retryer;
return this;
}
public Builder logger(Logger logger) {
this.logger = logger;
return this;
}
public Builder encoder(Encoder encoder) {
this.encoder = encoder;
return this;
}
public Builder decoder(Decoder decoder) {
this.decoder = decoder;
return this;
}
/**
* Allows to map the response before passing it to the decoder.
*/
public Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
this.decoder = new ResponseMappingDecoder(mapper, decoder);
return this;
}
/**
* This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with
* 404 status, specifically returning null or empty instead of throwing {@link FeignException}.
*
* All first-party (ex gson) decoders return well-known empty values defined by {@link
* Util#emptyValueOf}. To customize further, wrap an existing {@link #decoder(Decoder) decoder}
* or make your own.
*
* This flag only works with 404, as opposed to all or arbitrary status codes. This was an
* explicit decision: 404 -> empty is safe, common and doesn't complicate redirection, retry or
* fallback policy. If your server returns a different status for not-found, correct via a
* custom {@link #client(Client) client}.
*
* @since 8.12
*/
public Builder decode404() {
this.decode404 = true;
return this;
}
public Builder errorDecoder(ErrorDecoder errorDecoder) {
this.errorDecoder = errorDecoder;
return this;
}
public Builder options(Options options) {
this.options = options;
return this;
}
/**
* Adds a single request interceptor to the builder.
*/
public Builder requestInterceptor(RequestInterceptor requestInterceptor) {
this.requestInterceptors.add(requestInterceptor);
return this;
}
/**
* Sets the full set of request interceptors for the builder, overwriting any previous
* interceptors.
*/
public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
this.requestInterceptors.clear();
for (RequestInterceptor requestInterceptor : requestInterceptors) {
this.requestInterceptors.add(requestInterceptor);
}
return this;
}
/**
* Allows you to override how reflective dispatch works inside of Feign.
*/
public Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
this.invocationHandlerFactory = invocationHandlerFactory;
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 Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
}
builder를 통해 Feign 호출 대상을 만들 때 로그 단계, 재시도 횟수, 인코딩 클래스 등을 설정하고 target () 방법으로 Feign 인터페이스 대상을 만들 수 있습니다. 다음은 Feign이 제공하는 Target 인터페이스를 볼 수 있습니다.
public interface Target<T> {
/* The type of the interface this target applies to. ex. {@code Route53}. */
Class<T> type();
/* configuration key associated with this target. For example, {@code route53}. */
String name();
/* base HTTP URL of the target. For example, {@code https://api/v2}. */
String url();
/**
* Targets a template to this target, adding the {@link #url() base url} and any target-specific
* headers or query parameters.
For example:
*
* public Request apply(RequestTemplate input) {
* input.insert(0, url());
* input.replaceHeader("X-Auth", currentToken);
* return input.asRequest();
* }
*
* relationship to JAXRS 2.0 This call is similar to {@code
* javax.ws.rs.client.WebTarget.request()}, except that we expect transient, but necessary
* decoration to be applied on invocation.
*/
public Request apply(RequestTemplate input);
public static class HardCodedTarget implements Target {
private final Class type;
private final String name;
private final String url;
public HardCodedTarget(Class type, String url) {
this(type, url, url);
}
public HardCodedTarget(Class type, String name, String url) {
this.type = checkNotNull(type, "type");
this.name = checkNotNull(emptyToNull(name), "name");
this.url = checkNotNull(emptyToNull(url), "url");
}
@Override
public Class type() {
return type;
}
@Override
public String name() {
return name;
}
@Override
public String url() {
return url;
}
/* no authentication or other special activity. just insert the url. */
@Override
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
input.insert(0, url());
}
return input.request();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof HardCodedTarget) {
HardCodedTarget other = (HardCodedTarget) obj;
return type.equals(other.type)
&& name.equals(other.name)
&& url.equals(other.url);
}
return false;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + type.hashCode();
result = 31 * result + name.hashCode();
result = 31 * result + url.hashCode();
return result;
}
@Override
public String toString() {
if (name.equals(url)) {
return "HardCodedTarget(type="+ type.getSimpleName() + ", url="+ url + ")";
}
return "HardCodedTarget(type="+ type.getSimpleName() + ", name="+ name + ", url="+ url
+ ")";
}
}
public static final class EmptyTarget implements Target {
private final Class type;
private final String name;
EmptyTarget(Class type, String name) {
this.type = checkNotNull(type, "type");
this.name = checkNotNull(emptyToNull(name), "name");
}
public static EmptyTarget create(Class type) {
return new EmptyTarget(type, "empty:"+ type.getSimpleName());
}
public static EmptyTarget create(Class type, String name) {
return new EmptyTarget(type, name);
}
@Override
public Class type() {
return type;
}
@Override
public String name() {
return name;
}
@Override
public String url() {
throw new UnsupportedOperationException("Empty targets don't have URLs");
}
@Override
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
throw new UnsupportedOperationException(
"Request with non-absolute URL not supported with empty target");
}
return input.request();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof EmptyTarget) {
EmptyTarget other = (EmptyTarget) obj;
return type.equals(other.type)
&& name.equals(other.name);
}
return false;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + type.hashCode();
result = 31 * result + name.hashCode();
return result;
}
@Override
public String toString() {
if (name.equals("empty:"+ type.getSimpleName())) {
return "EmptyTarget(type="+ type.getSimpleName() + ")";
}
return "EmptyTarget(type="+ type.getSimpleName() + ", name="+ name + ")";
}
}
}
세 가지 인터페이스 방법이 있는데 type()는 Feign 호출 대상 인터페이스의class 대상이고name()는Target 대상의 유일한 표식이며 url()는 http 호출을 시작하는base url이다.Feign은 두 가지 기본 구현을 제공했다. 하나는 url 하드 인코딩이고 하나는 url이 없는 빈 Target이다. 우리가 평소에 사용하는 것은 보통HardCoded Target 즉 하드 인코딩이다.build () 방법으로 Reflective Feign 대상을 만들었습니다. 다음은 Reflective Feign 대상의 newInstance (Target target) 방법을 보십시오. public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
다음과 같은 작업을 수행했습니다. 1.Feign 호출 인터페이스에서 해결 방법 2.인터페이스를 만드는 프록시 대상 (jdk 동적 프록시) 3.기본 방법이 있으면 이 프록시 대상에도 연결합니다 생성된 proxy 대상은 Feign이 http 호출을 하는 실제 대상입니다