/**
* Copyright 2012-2021 The Feign Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package feign;
import feign.Logger.NoOpLogger;
import feign.ReflectiveFeign.ParseHandlersByName;
import feign.Request.Options;
import feign.Target.HardCodedTarget;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.querymap.FieldQueryMapEncoder;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static feign.ExceptionPropagationPolicy.NONE;
/**
* Feign's purpose is to ease development against http apis that feign restfulness.
* In implementation, Feign is a {@link Feign#newInstance factory} for generating {@link Target
* targeted} http apis.
*/
public abstract class Feign {
public static Builder builder() {
return new Builder();
}
/**
* Configuration keys are formatted as unresolved see
* tags. This method exposes that format, in case you need to create the same value as
* {@link MethodMetadata#configKey()} for correlation purposes.
*
*
* Here are some sample encodings:
*
*
*
* - {@code Route53}: would match a class {@code route53.Route53}
* - {@code Route53#list()}: would match a method {@code route53.Route53#list()}
* - {@code Route53#listAt(Marker)}: would match a method {@code
* route53.Route53#listAt(Marker)}
* - {@code Route53#listByNameAndType(String, String)}: would match a method {@code
* route53.Route53#listAt(String, String)}
*
*
*
* Note that there is no whitespace expected in a key!
*
* @param targetType {@link feign.Target#type() type} of the Feign interface.
* @param method invoked method, present on {@code type} or its super.
* @see MethodMetadata#configKey()
*/
//route53.Route53#listAt(String, String)
public static String configKey(Class targetType, Method method) {
StringBuilder builder = new StringBuilder();
builder.append(targetType.getSimpleName());
builder.append('#').append(method.getName()).append('(');
for (Type param : method.getGenericParameterTypes()) {
param = Types.resolve(targetType, targetType, param);
builder.append(Types.getRawType(param).getSimpleName()).append(',');
}
if (method.getParameterTypes().length > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.append(')').toString();
}
/**
* @deprecated use {@link #configKey(Class, Method)} instead.
*/
@Deprecated
public static String configKey(Method method) {
return configKey(method.getDeclaringClass(), method);
}
/**
* Returns a new instance of an HTTP API, defined by annotations in the {@link Feign Contract},
* for the specified {@code target}. You should cache this result.
*/
public abstract T newInstance(Target target);
public static class Builder {
private final List requestInterceptors =
new ArrayList();
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 capabilities = new ArrayList<>();
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;
}
public Builder queryMapEncoder(QueryMapEncoder queryMapEncoder) {
this.queryMapEncoder = queryMapEncoder;
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 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;
}
/**
* This flag indicates that the response should not be automatically closed upon completion of
* decoding the message. This should be set if you plan on processing the response into a
* lazy-evaluated construct, such as a {@link java.util.Iterator}.
*
*
* Feign standard decoders do not have built in support for this flag. If you are using this
* flag, you MUST also use a custom Decoder, and be sure to close all resources appropriately
* somewhere in the Decoder (you can use {@link Util#ensureClosed} for convenience).
*
* @since 9.6
*
*/
public Builder doNotCloseAfterDecode() {
this.closeAfterDecode = false;
return this;
}
public Builder exceptionPropagationPolicy(ExceptionPropagationPolicy propagationPolicy) {
this.propagationPolicy = propagationPolicy;
return this;
}
public Builder addCapability(Capability capability) {
this.capabilities.add(capability);
return this;
}
/**
* Internal - used to indicate that the decoder should be immediately called
*/
Builder forceDecoding() {
this.forceDecoding = true;
return this;
}
public T target(Class apiType, String url) {
return target(new HardCodedTarget(apiType, url));
}
public T target(Target target) {
return build().newInstance(target);
}
public Feign build() {
Client client = Capability.enrich(this.client, capabilities);
Retryer retryer = Capability.enrich(this.retryer, capabilities);
List requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
Logger logger = Capability.enrich(this.logger, capabilities);
Contract contract = Capability.enrich(this.contract, capabilities);
Options options = Capability.enrich(this.options, capabilities);
Encoder encoder = Capability.enrich(this.encoder, capabilities);
Decoder decoder = Capability.enrich(this.decoder, capabilities);
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
// 构造反射的feign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
public static class ResponseMappingDecoder implements Decoder {
private final ResponseMapper mapper;
private final Decoder delegate;
public ResponseMappingDecoder(ResponseMapper mapper, Decoder decoder) {
this.mapper = mapper;
this.delegate = decoder;
}
@Override
public Object decode(Response response, Type type) throws IOException {
return delegate.decode(mapper.map(response, type), type);
}
}
}