0. Getting started demo
- This code is an example code of OpenFeign, which gets all contributors of a Github warehouse and creates an issue. It is recommended to DEBUG and read the source code
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.build injection dependency configuration item
public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }
Calling JDK dynamic proxy to generate interface proxy class
Dynamic proxy generation interface object
public class ReflectiveFeign extends Feign { @Override public <t> T newInstance(Target<t> target) { //Use Contract to parse the methods and annotations on the interface class, and transform the individual MethodHandler processing Map<string, methodhandler> nameToHandler = targetToHandlersByName.apply(target); // DK dynamic proxy is used to generate proxy objects for the interface. The actual business logic is handed to InvocationHandler for processing, which is actually to call MethodHandler InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<!--?-->[]{target.type()}, handler); return proxy; } }
Parsing interface method annotation information
- How to parse the annotation information of Github.contributors method in Demo above. Feign provides a Contract resolution protocol, which is implemented as follows.
Parsing logic is supported by default
class Default extends Contract.BaseContract { protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { Class<!--? extends Annotation--> annotationType = methodAnnotation.annotationType(); if (annotationType == RequestLine.class) { //@RequestLine annotation processing logic } else if (annotationType == Body.class) { //@Body annotation processing logic } else if (annotationType == Headers.class) { //@Headers annotation processing logic } } protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { boolean isHttpAnnotation = false; for (Annotation annotation : annotations) { Class<!--? extends Annotation--> annotationType = annotation.annotationType(); if (annotationType == Param.class) { Param paramAnnotation = (Param) annotation; //@Param annotation processing logic } else if (annotationType == QueryMap.class) { //@QueryMap annotation processing logic } else if (annotationType == HeaderMap.class) { //@HeaderMap annotation processing logic } } return isHttpAnnotation; } }
Native common annotations
Annotation | Interface Target |
---|---|
@RequestLine | Method |
@Param | Parameter |
@Headers | Method, Type |
@QueryMap | Parameter |
@HeaderMap | Parameter |
@Body | Method |
Spring MVC extension annotation
- Spring mvccontract supports spring MVC annotation for the extension of spring cloud open feign. Now feign version also supports spring MVC annotation
public class SpringMvcContract { // Process @ RequestMapping on class @Override protected void processAnnotationOnClass(MethodMetadata data, Class<!--?--> clz) { if (clz.getInterfaces().length == 0) { RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class); } } // Processing @ RequestMapping annotation, of course, also supports the processing of derived annotation @ GetMapping @PostMapping, etc @Override protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation .annotationType().isAnnotationPresent(RequestMapping.class)) { return; } RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class); // Get request method RequestMethod[] methods = methodMapping.method(); // produce processing parseProduces(data, method, methodMapping); // Consumers processing parseConsumes(data, method, methodMapping); // headers header processing parseHeaders(data, method, methodMapping); data.indexToExpander(new LinkedHashMap<integer, param.expander>()); } // Processing request parameter spring MVC native annotation @Override protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { Param.Expander expander = this.convertingExpanderFactory .getExpander(typeDescriptor); if (expander != null) { data.indexToExpander().put(paramIndex, expander); } return isHttpAnnotation; } }
MethodHandler request processing logic
MethodHandler routing
As shown in the figure above, routing to different MethodHandler implementations according to different request methods
final class SynchronousMethodHandler implements MethodHandler { @Override public Object invoke(Object[] argv) throws Throwable { // Get request template RequestTemplate template = buildTemplateFromArgs.create(argv); // Parameter processing Options options = findOptions(argv); // Default retrier Retryer retryer = this.retryer.clone(); while (true) { try { // Execution request interceptor Request request = targetRequest(template); // Output request message if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response = client.execute(request, options); // Decode according to the returned status code ... return response; } catch (RetryableException e) { // Related logic of retry } } } }
Build request template according to different parameters
- Form submission or direct body submission
Execute Request interceptor to generate final Request
// Get all request interceptors and execute one by one Request targetRequest(RequestTemplate template) { for (RequestInterceptor interceptor : requestInterceptors) { interceptor.apply(template); } return target.apply(template); }
Request log processing
- Log output level, configuring
public enum Level { /** * No output */ NONE, /** * Only the output Http method, URL, status code and execution time are recorded */ BASIC, /** * Output request header and Http method, URL, status code, execution time */ HEADERS, /** * Output request header, report style and Http method, URL, status code, execution time */ FULL }
Client performs the final Requst request
default processing
- Through the java.net package of JDK, the connection implementation will be created if there is no request. High performance implementation of HttpClient or OKHttp can be configured
class Default implements Client { private final SSLSocketFactory sslContextFactory; private final HostnameVerifier hostnameVerifier; @Override public Response execute(Request request, Request.Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection, request); } 」
Load balancing of Spring Cloud
// Client implementation of Spring Cloud public class FeignBlockingLoadBalancerClient { @Override public Response execute(Request request, Request.Options options) throws IOException { // For example, request: http://pig-auth-server/token/info final URI originalUri = URI.create(request.url()); // Intercept to serviceid: Pig auth server String serviceId = originalUri.getHost(); // Call loadBalancer API to get available service instances ServiceInstance instance = loadBalancerClient.choose(serviceId); // Build the real request URL http://172.17.0.110:8763/token/info String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri) .toString(); // Create request and execute Request newRequest = Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.requestBody()); return delegate.execute(newRequest, options); } }
Return message Decoder processing
- Default handling
class Default implements Encoder { @Override public void encode(Object object, Type bodyType, RequestTemplate template) { if (bodyType == String.class) { template.body(object.toString()); } else if (bodyType == byte[].class) { template.body((byte[]) object, null); } else if (object != null) { throw new EncodeException( format("%s is not a type supported by this encoder.", object.getClass())); } } }
- In case of return message error handling
public static class Default implements ErrorDecoder { private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder(); @Override public Exception decode(String methodKey, Response response) { FeignException exception = errorStatus(methodKey, response); Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER)); if (retryAfter != null) { return new RetryableException( response.status(), exception.getMessage(), response.request().httpMethod(), exception, retryAfter, response.request()); } return exception; } private <t> T firstOrNull(Map<string, collection<t>> map, String key) { if (map.containsKey(key) && !map.get(key).isEmpty()) { return map.get(key).iterator().next(); } return null; } } }
Injection of custom ErrorDecoder is common.
The above content is the request processing flow of open feign. The following is how to initialize and run the extended content spring cloud open feign?
[Extension] Spring Cloud OpenFeign
Analysis of EnableFeignClients
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { }
- When we add @ EnableFeignClients annotation to the Main method, the related functions of spring cloud open feign are enabled.
- Import (feignclientsregister. Class) import feignclientsregister, scan @ FeignClient to inject into the container
FeignClientsRegistrar
class FeignClientsRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerFeignClients(metadata, registry); } public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // Scan @ FeignClient in configuration scope in configuration annotation for (String basePackage : basePackages) { // Fill IOC container registerClientConfiguration(registry, name, attributes.get("configuration")); } } //Feignclient & lt; -- & gt; bean construction private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<string, object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); ... BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } }
Default condition
public class FeignAutoConfiguration { // If feign hystrix module is not introduced, defaulttarget will be injected @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("feign.hystrix.HystrixFeign") protected static class DefaultFeignTargeterConfiguration { @Bean @ConditionalOnMissingBean public Targeter feignTargeter() { return new DefaultTargeter(); } } }
If feign hystrix is not introduced, the above process is the same as the original process. When we call feignclient.method, the dynamic agent will be triggered to execute the logic of MethodHandler
HystrixFeign
- First of all, does the introduction of hystrix feign mean that the logic has changed
At the beginning, 0. Get started with Demo Feign.builder(), which becomes HystrixFeign.builder()
public final class HystrixFeign { public static Builder builder() { return new Builder(); } public static final class Builder extends Feign.Builder { // Implementation of injection into HystrixInvocationHandler Feign build(final FallbackFactory<!--?--> nullableFallbackFactory) { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<method, methodhandler> dispatch) { return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); } }); super.contract(new HystrixDelegatingContract(contract)); return super.build(); } } }
- Inject the implementation of HystrixInvocationHandler, wrap it with HystrixCommand, and finally use methodhandler to call the final interface
final class HystrixInvocationHandler implements InvocationHandler { @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { // Wrapping with HystrixCommand HystrixCommand<object> hystrixCommand = new HystrixCommand<object>(setterMethodMap.get(method)) { @Override protected Object run() throws Exception { try { // Call methodhandler to process the final request return HystrixInvocationHandler.this.dispatch.get(method).invoke(args); } catch (Exception e) { throw e; } catch (Throwable t) { throw (Error) t; } } }; return hystrixCommand.execute(); } }
SentinelFeign
- First, look at the class annotation like {@link HystrixFeign.Builder}, "learn from" HystrixFeign
/** * {@link Feign.Builder} like {@link HystrixFeign.Builder}. */ public final class SentinelFeign { }
- Inject the implementation of SentinelInvocationHandler, use Sentinel wrapper, and finally use methodhandler to call the final interface
public class SentinelInvocationHandler implements InvocationHandler { @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { // Wrapping requests with sentinel try { ContextUtil.enter(resourceName); entry = SphU.entry(resourceName, EntryType.OUT, 1, args); result = methodHandler.invoke(args); } catch (Throwable ex) { // fallback logic } finally { ContextUtil.exit(); } return result; } }
Summary sequence diagram
Follow up plan
Welcome to follow me, and update Ribbon, Hystrix, Sentinel, Nacos and other components source text analysis. >Project recommendation: Welcome to RBAC permission management system of Spring Cloud and Spring Security OAuth2 </object></object></method,></string,></string,></t></integer,></string,></t></t></contributor></string></string></contributor>