手把手教你基于Retrofit实现本身的轻量级http调用工具

在《spring-boot项目最优雅的http客户端工具,用它就够了,太香了!》这篇文章中,咱们知道了retrofit-spring-boot-starter的使用方式。本篇文章继续继续介绍retrofit-spring-boot-starter的实现原理,从零开始介绍如何在spring-boot项目中基于Retrofit实现本身的轻量级http调用工具java

项目源码:retrofit-spring-boot-startergit

肯定实现思路

咱们首先直接看一下使用retrofit原始API是如何发起一个http请求的。github

  1. 定义接口spring

    public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
    }
    复制代码
  2. 建立接口代理对象sql

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();
    
    // 实际业务场景构建Retrofit比这复杂多了,这里最简单化处理
    
    GitHubService service = retrofit.create(GitHubService.class);
    复制代码
  3. 发起请求api

    Call<List<Repo>> repos = service.listRepos("octocat");
    复制代码

能够看到,Retrofit自己已经很好的支持了经过接口发起htp请求。可是若是咱们项目每个业务代码都要写上面的样板代码,会很是的繁琐。有没有一种方式让用户只关注接口定义,其它事情所有交给框架自动处理?这个时候咱们可能会联想到spring-boot项目下使用Mybatis,用户只须要定义Mapper接口和书写sql便可,彻底不用管与JDBC的交互细节。与之相似,咱们最终也要实现让用户只须要定义HttpService接口,不用管其余底层实现细节markdown

相关知识介绍

为了方便后面的介绍,咱们先得了解一下几个相关知识点。app

spring容器初始化

咱们首先要简单了解一下spring容器初始化。简单来说,spring容器初始化主要包含如下2个步骤:框架

  1. 注册Bean定义:扫描并解析配置文件或者某些注解获得Bean属性(包括beanNamebeanClassNamescopeisSingleton等等),而后基于这个bean属性建立BeanDefinition对象,最后将其注册到BeanDefinitionRegistry中。
  2. 建立Bean实例:根据BeanDefinitionRegistry里面的BeanDefinition信息,建立Bean实例,并将实例对象保存到spring容器中,建立的方式包括反射建立、工厂方法建立和工厂Bean(FactoryBean)建立等等。

固然,实际的spring容器初始化比这复杂的多,考虑到这块不是本文的重点,暂时这么理解就行。ide

Retrofit对象简介

咱们已经知道使用Retrofit对象能够建立接口代理对象,接下来看一下Retrofit的UML类图(只列出了咱们关注的依赖): 经过分析UML类图,咱们能够发现,构建Retrofit对象的时候,能够注入如下4个属性:

  1. HttpUrlhttp请求的baseUrl
  2. CallAdapter:将Call<T>适配为接口方法返回值类型。
  3. Converter:将@Body标记的方法参数序列化为请求体数据;将响应体数据反序列化为响应对象。
  4. OkHttpClient:底层发送http请求的客户端对象。

而构建OkHttpClient对象的时候,能够注入Interceptor(请求拦截器)和ConnectionPool(链接池)属性。

所以为了构建Retrofit对象,咱们要先建立HttpUrlCallAdapterConverterOkHttpClient;而要构建OkHttpClient对象就得先建立InterceptorConnectionPool

实现详解

注册Bean定义

为了实现将HttpService接口代理对象彻底交由spring容器管理,首先就得将HttpService接口扫描并注册到BeanDefinitionRegistry中。spring提供了ImportBeanDefinitionRegistrar接口,支持了自定义注册BeanDefinition的功能。所以咱们先定义RetrofitClientRegistrar类用来实现上述功能。具体实现以下:

  1. RetrofitClientRegistrar

    RetrofitClientRegistrar@RetrofitScan注解中提取出要扫描的基础包路径以后,将具体的扫描注册逻辑交给了ClassPathRetrofitClientScanner处理。

    public class RetrofitClientRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
    
        // 省略其它代码
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AnnotationAttributes attributes = AnnotationAttributes
                    .fromMap(metadata.getAnnotationAttributes(RetrofitScan.class.getName()));
            // 扫描指定路径下@RetrofitClient注解的接口,并注册到BeanDefinitionRegistry
            // 真正的扫描注册逻辑交给了ClassPathRetrofitClientScanner执行
            ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader);
            if (resourceLoader != null) {
                scanner.setResourceLoader(resourceLoader);
            }
            //指定扫描的基础包
            String[] basePackages = getPackagesToScan(attributes);
            scanner.registerFilters();
            // 扫描并注册到BeanDefinition
            scanner.doScan(basePackages);
        }
    }
    复制代码
  2. ClassPathRetrofitClientScanner

    ClassPathRetrofitClientScanner继承了ClassPathBeanDefinitionScanner,这是Spring提供的类路径下BeanDefinition的扫描器。须要注意的一点是:BeanDefinitionbeanClass属性所有设置为了RetrofitFactoryBean.class,同时将接口自身的类型传递到了RetrofitFactoryBeanretrofitInterface属性中。这说明,最终建立Bean实例是经过RetrofitFactoryBean来完成的。

    public class ClassPathRetrofitClientScanner extends ClassPathBeanDefinitionScanner {
    
        // 省略其它代码
    
        private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
            GenericBeanDefinition definition;
            for (BeanDefinitionHolder holder : beanDefinitions) {
                definition = (GenericBeanDefinition) holder.getBeanDefinition();
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating RetrofitClientBean with name '" + holder.getBeanName()
                            + "' and '" + definition.getBeanClassName() + "' Interface");
                }
                definition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(definition.getBeanClassName()));
                // beanClass所有设置为RetrofitFactoryBean
                definition.setBeanClass(RetrofitFactoryBean.class);
            }
        }
    }
    复制代码

这样,咱们就完成了扫描指定路径下带有@RetrofitClient注解的接口,并将其注册到BeanDefinitionRegistry的功能了

@RetrofitClient注解要标识在HttpService的接口上!@RetrofitScan指定了要扫描的包路径。具体可看考源码。

建立Bean实例

上面已经说了建立Bean实例其实是经过RetrofitFactoryBean实现的。具体就是实现FactoryBean<T>接口,而后重写getObject()方法来完成建立接口Bean实例的逻辑。而且,咱们也已经知道经过Retrofit对象可以生成接口代理对象。所以getObject()方法的核心就是构建Retrofit对象,并基于今生成http接口代理对象。

  1. 配置项和@RetrofitClient 为了更加灵活的构建Retrofit对象,咱们能够经过配置项以及@RetrofitClient注解属性传递一些动态参数信息。@RetrofitClient包含的属性以下:

    1. baseUrl:用来建立RetrofitHttpUrl,表示该接口下全部请求都适用的基础url
    2. poolName:该接口下请求使用的链接池的名称,决定了ConnectionPool对象的取值。
    3. connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用于构建OkHttpClient对象的超时时间设置。
    4. logLevel/logStrategy:配置该接口下请求的日志打印级别和日志打印策略,可用来建立日志打印拦截器Interceptor
  2. RetrofitFactoryBean RetrofitFactoryBean实现逻辑很是复杂,归纳起来主要包含如下几点:

    1. 经过配置项数据以及@RetrofitClient注解数据完成了Retrofit对象的构建。
    2. 每个HttpService接口就会构建一个Retrofit对象,每个Retrofit对象就会构建对应的OkHttpClient对象。
    3. 可扩展的注解式拦截器是经过InterceptMark注解标记实现的,路径拦截匹配是经过BasePathMatchInterceptor实现的。
    public class RetrofitFactoryBean<T> implements FactoryBean<T>, EnvironmentAware, ApplicationContextAware {
    
        // 省略其它代码
    
        public RetrofitFactoryBean(Class<T> retrofitInterface) {
            this.retrofitInterface = retrofitInterface;
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public T getObject() throws Exception {
            // 接口校验
            checkRetrofitInterface(retrofitInterface);
            // 构建Retrofit对象
            Retrofit retrofit = getRetrofit(retrofitInterface);
            // 基于Retrofit建立接口代理对象
            return retrofit.create(retrofitInterface);
        }
    
        /** * 获取OkHttpClient实例,一个接口接口对应一个OkHttpClient * * @param retrofitClientInterfaceClass retrofitClient接口类 * @return OkHttpClient实例 */
        private synchronized OkHttpClient getOkHttpClient(Class<?> retrofitClientInterfaceClass) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            // 基于各类条件构建OkHttpClient
        }
    
        /** * 获取Retrofit实例,一个retrofitClient接口对应一个Retrofit实例 * * @param retrofitClientInterfaceClass retrofitClient接口类 * @return Retrofit实例 */
        private synchronized Retrofit getRetrofit(Class<?> retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
            // 构建retrofit
        }
    复制代码

这样,咱们就完成了建立HttpServiceBean实例的功能了。在使用的时候直接注入HttpService,而后调用其方法就能发送对应的http请求。

结语

总的来讲,在spring-boot项目中基于Retrofit实现本身的轻量级http调用工具的核心只有两点:第一是注册HttpService接口的BeanDefinition,第二就是构建Retrofit来建立HttpService的代理对象。如需了解更多细节,建议直接查看retrofit-spring-boot-starter源码

原创不易,以为文章写得不错的小伙伴,点个赞👍 鼓励一下吧~

欢迎关注个人开源项目:一款适用于SpringBoot的轻量级HTTP调用框架

相关文章
相关标签/搜索