/** * 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: * *

   * 
   * 
* * 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); } } }