• 如何阅读 Spring Cloud OpenFein 源码


    背景

    一直以来,使用 Spring Cloud OpenFeign 都是阅读官方文档,虽然也大概知道其实现原理,但终究是没有"证据"的。关于 Spring 的源码阅读,自认为是一件十分令人头疼的事情。最近,在学习 Feign 的原生 API,乘此机会,也就阅读一下 Spring Cloud OpenFeign 的源码,并将分享出来,希望能帮到有需要的人吧。

    概述

    关于 Spring Cloud OpenFeign 源码的博客有很多,但是,不知道为什么,照着博客,一边读博客,一边读源码,还一边 debug,总是认为还有很多不清楚的地方。究其原因,我认为,博客都是按照源码的流程讲解,虽然附上了大段代码,可能还是无法清晰的理解。不知道你们是不是,反正我是这样的。

    目标

    首先,我们明确一下今天探究的问题:

    1. 我们知道,当我们使用 @FeignClient,是使用了JDK动态代理,那么是如何实现的,那一步创建的代理类。

    2. 当我们知道第一个问题后,我们就基本清楚整个流程了,那么,我们就可以手写一个简易的入门测试了。

    源码

    启动流程

    • org.springframework.cloud.openfeign.EnableFeignClients
    • org.springframework.cloud.openfeign.FeignAutoConfiguration
    • org.springframework.cloud.openfeign.FeignClientsRegistrar
    • org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions
    • org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient
    • org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject
    • org.springframework.cloud.openfeign.Targeter
    • feign.Feign.Builder#build
    • feign.SynchronousMethodHandler.Factory
    • feign.ReflectiveFeign.ParseHandlersByName
    • feign.ReflectiveFeign#ReflectiveFeign
    • feign.Feign#newInstance
    • feign.ReflectiveFeign#newInstance
    • feign.InvocationHandlerFactory#create
    • feign.InvocationHandlerFactory.Default#create
    • feign.ReflectiveFeign.FeignInvocationHandler#FeignInvocationHandler

    贴上图吧,看看完整版的

    注册流程:

    注册流程

    自动配置:

    自动配置

    调用流程

    • feign.ReflectiveFeign.FeignInvocationHandler#invoke
    • feign.InvocationHandlerFactory.MethodHandler#invoke
    • feign.SynchronousMethodHandler#invoke

    调用流程

    MyRpc

    经过上面的流程,我们手写一个 RPC。

    下面给出主要代码。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import({MyRpcRegister.class, MyRpcAutoConfig.class})
    public @interface EnableMyRpc {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    getObject()

    @Data
    public class MyRpcFactoryBean implements FactoryBean<Object> {
    
        private String url;
    
        private String contextPath;
    
        private String name;
    
        private Class<?> type;
    
        private BeanFactory beanFactory;
    
        private MyClient myClient;
    
        @Override
        public Object getObject() {
    
            Map<Method, RpcBean> map = new HashMap<>();
    
            Method[] methods = type.getMethods();
    
            myClient = beanFactory.getBean(MyClient.class);
    
            for (Method method : methods) {
                Annotation[] annotations = method.getAnnotations();
                String httpMethod = "";
                String path = "";
                for (Annotation annotation : annotations) {
                    if (annotation.annotationType() == PostMapping.class) {
                        httpMethod = "POST";
                        path = ((PostMapping) annotation).value()[0];
                        break;
                    } else if (annotation.annotationType() == GetMapping.class) {
                        httpMethod = "GET";
                        path = ((GetMapping) annotation).value()[0];
                        break;
                    } else if (annotation.annotationType() == RequestMapping.class) {
                        RequestMapping requestMapping = ((RequestMapping) annotation);
                        httpMethod = requestMapping.method()[0].name();
                        path = requestMapping.value()[0];
                        break;
                    }
                }
                RpcBean rpcBean = new RpcBean()
                        .setUrl(url + contextPath)
                        .setPath(path)
                        .setHttpMethod(httpMethod)
                        .setMyClient(myClient)
                        ;
                map.put(method, rpcBean);
            }
    
            ClassLoader loader = type.getClassLoader();
    
            return Proxy.newProxyInstance(loader, new Class<?>[] {type}, new MyRpcInvocationHandler(map));
        }
    
        @Override
        public Class<?> getObjectType() {
            return type;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    handler

    @Slf4j
    public class MyRpcInvocationHandler implements InvocationHandler {
    
        private final Map<Method, RpcBean> map;
    
        public MyRpcInvocationHandler(Map<Method, RpcBean> map) {
            this.map = map;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            log.info("proxy handler");
    
            return request(method, args);
        }
    
        public Object request(Method method, Object[] args) {
            String result = "";
            RpcBean rpcBean = map.get(method);
            Parameter[] parameters = method.getParameters();
            Class<?> returnType = method.getReturnType();
            String url = rpcBean.getUrl() + rpcBean.getPath();
            String httpMethod = rpcBean.getHttpMethod();
            String param = getParam(httpMethod, parameters, args);
            log.info("url: [{}], param: [{}]", url, param);
            MyClient myClient = rpcBean.getMyClient();
            if ("POST".equals(httpMethod)) {
                result = myClient.post(url, param);
            } else if ("GET".equals(httpMethod)) {
                result = myClient.get(url, param);
            }
            if (StringUtils.hasText(result)) {
                return JsonUtils.convertObject(result, returnType);
            }
            return "";
        }
    
        public String getParam(String httpMethod, Parameter[] parameters, Object[] args) {
            if ("POST".equals(httpMethod)) {
                return JsonUtils.convertString(args[0]);
            } else if ("GET".equals(httpMethod)) {
                if (Objects.isNull(parameters) || parameters.length == 0
                        || Objects.isNull(args) || args.length == 0) {
                    return "";
                }
                String param = "";
                StringBuilder urlBuilder = new StringBuilder(param);
                for (int i = 0; i < parameters.length; i++) {
                    if (Objects.nonNull(args[i])) {
                        urlBuilder.append(String.format("%s=%s&", parameters[i].getName(), args[i]));
                    }
                }
                param = urlBuilder.toString();
                param = param.substring(0, param.length() - 1);
                return param;
            }
            return "";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    完整代码

  • 相关阅读:
    .NET Core企业微信网页授权登录
    基于springboot实现新生宿舍管理系统演示【项目源码+论文说明】分享
    【python基础】函数-参数形式
    0基础跟我学Django 教程----(1)
    基于JAVA,SpringBoot和HTML校园二手商城系统设计
    Python全栈开发【基础-07】与用户交互
    Java 网络教程ServerSocket的简介说明
    计算机毕业设计之java+ssm基于个人需求和地域特色的外卖推荐系统
    前端技能树,面试复习第 49 天—— 虚拟 DOM | 虚拟 DOM 比真实 DOM 真的快吗 | Diff 算法原理
    实例讲解将Graph Explorer搬上JupyterLab
  • 原文地址:https://blog.csdn.net/qq_28336351/article/details/127456770