Retrofit+SpringBoot应用+源码分析

源码分析

核心注解:

RetrofitClient、RetrofitScan

核心类:

RetrofitClientScannerRegistrar

ClassPathRetrofitClientScanner

RetrofitFactoryBean

阅读开始

@SpringBootApplication
@RetrofitScan("com.xxx.api")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

        springboot入口使用了@RetrofitScan注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RetrofitClientScannerRegistrar.class)
public @interface RetrofitScan {
    略
}
     注解中定义了注册类RetrofitClientScannerRegistrar;追踪
  @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes attributes = AnnotationAttributes
                .fromMap(metadata.getAnnotationAttributes(RetrofitScan.class.getName()));
        if (attributes == null) {
            return;
        }
        // Scan the @RetrofitClient annotated interface under the specified path and register it to the BeanDefinitionRegistry
        ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader);
        if (resourceLoader != null) {
            scanner.setResourceLoader(resourceLoader);
        }
        // Specify the base package for scanning
        String[] basePackages = getPackagesToScan(attributes);
        scanner.registerFilters();
        // Scan and register to BeanDefinition
        scanner.doScan(basePackages);
    }

        发现使用了ClassPathRetrofitClientScanner进行扫描配置的路径;

 public void registerFilters() {
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(RetrofitClient.class);
        this.addIncludeFilter(annotationTypeFilter);
    }

      将RetrofitClient注解加入过滤器链列表:扫描class文件的时候,后续需要根据过滤器过滤出使用注解的文件

  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            logger.warn("No RetrofitClient was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }

        扫描带RetrofitClient注解的接口封装成BeanDefinitionHolder;实际还是使用的父类ClassPathBeanDefinitionScanner中的doscan方法进行扫描的;多了一段处理逻辑processBeanDefinitions

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

           由于扫描到的都是接口,因此在收尾的时候调用processBeanDefinitions(beanDefinitions)将扫描的接口信息转换为BeanFactory;

        到此为止基本上使用了注解的接口基本上都已经注册到IOC中了,接下来就是接口调用

Retrofit对应http的使用其实还是使用的OkHttp,那么是如何进行关联的呢,代码切换到

RetrofitFactoryBean

 @Override
    @SuppressWarnings("unchecked")
    public T getObject() throws Exception {
        checkRetrofitInterface(retrofitInterface);
        Retrofit retrofit = getRetrofit(retrofitInterface);
        // source
        T source = retrofit.create(retrofitInterface);

        RetrofitProperties retrofitProperties = retrofitConfigBean.getRetrofitProperties();
        Class<?> fallbackClass = retrofitClient.fallback();
        Object fallback = null;
        if (!void.class.isAssignableFrom(fallbackClass)) {
            fallback = ApplicationContextUtils.getBean(applicationContext, fallbackClass);
        }
        Class<?> fallbackFactoryClass = retrofitClient.fallbackFactory();
        FallbackFactory<?> fallbackFactory = null;
        if (!void.class.isAssignableFrom(fallbackFactoryClass)) {
            fallbackFactory = (FallbackFactory) ApplicationContextUtils.getBean(applicationContext, fallbackFactoryClass);
        }
        loadDegradeRules();
        // proxy
        return (T) Proxy.newProxyInstance(retrofitInterface.getClassLoader(),
                new Class<?>[]{retrofitInterface},
                new RetrofitInvocationHandler(source, fallback, fallbackFactory, retrofitProperties)

        );
    }

        工厂Bean getObject首先是获取getRetrofit

 private synchronized Retrofit getRetrofit(Class<?> retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        RetrofitClient retrofitClient = retrofitClientInterfaceClass.getAnnotation(RetrofitClient.class);
        String baseUrl = retrofitClient.baseUrl();

        baseUrl = RetrofitUtils.convertBaseUrl(retrofitClient, baseUrl, environment);

        OkHttpClient client = getOkHttpClient(retrofitClientInterfaceClass);
        Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .validateEagerly(retrofitClient.validateEagerly())
                .client(client);

        // 添加CallAdapter.Factory
        Class<? extends CallAdapter.Factory>[] callAdapterFactoryClasses = retrofitClient.callAdapterFactories();
        Class<? extends CallAdapter.Factory>[] globalCallAdapterFactoryClasses = retrofitConfigBean.getGlobalCallAdapterFactoryClasses();
        List<CallAdapter.Factory> callAdapterFactories = getCallAdapterFactories(callAdapterFactoryClasses, globalCallAdapterFactoryClasses);
        if (!CollectionUtils.isEmpty(callAdapterFactories)) {
            callAdapterFactories.forEach(retrofitBuilder::addCallAdapterFactory);
        }
        // 添加Converter.Factory
        Class<? extends Converter.Factory>[] converterFactoryClasses = retrofitClient.converterFactories();
        Class<? extends Converter.Factory>[] globalConverterFactoryClasses = retrofitConfigBean.getGlobalConverterFactoryClasses();

        List<Converter.Factory> converterFactories = getConverterFactories(converterFactoryClasses, globalConverterFactoryClasses);
        if (!CollectionUtils.isEmpty(converterFactories)) {
            converterFactories.forEach(retrofitBuilder::addConverterFactory);
        }
        return retrofitBuilder.build();
    }

        上述代码获取了注解中的baseUrl然后调用了getOkHttpClient方法

 private synchronized OkHttpClient getOkHttpClient(Class<?> retrofitClientInterfaceClass)
            throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        RetrofitClient retrofitClient = retrofitClientInterfaceClass.getAnnotation(RetrofitClient.class);
        
        //查询是否手动配置OkHttpClient.Builder 如果手动配置采用手动配置的
        Method method = findOkHttpClientBuilderMethod(retrofitClientInterfaceClass);
        OkHttpClient.Builder okHttpClientBuilder;
        if (method != null) {
            //method如果不为空那么使用方法中返回的OkHttpClient.Builder
            okHttpClientBuilder = (OkHttpClient.Builder) method.invoke(null);
        } else {
            //如果么有自定义OkHttpClient.Builder那么就使用默认配置的
            okhttp3.ConnectionPool connectionPool = getConnectionPool(retrofitClientInterfaceClass);
            // Construct an OkHttpClient object
            okHttpClientBuilder = new OkHttpClient.Builder()
                    .connectTimeout(retrofitClient.connectTimeoutMs(), TimeUnit.MILLISECONDS)
                    .readTimeout(retrofitClient.readTimeoutMs(), TimeUnit.MILLISECONDS)
                    .writeTimeout(retrofitClient.writeTimeoutMs(), TimeUnit.MILLISECONDS)
                    .callTimeout(retrofitClient.callTimeoutMs(), TimeUnit.MILLISECONDS)
                    .retryOnConnectionFailure(retrofitClient.retryOnConnectionFailure())
                    .followRedirects(retrofitClient.followRedirects())
                    .followSslRedirects(retrofitClient.followSslRedirects())
                    .pingInterval(retrofitClient.pingIntervalMs(), TimeUnit.MILLISECONDS)
                    .connectionPool(connectionPool);
        }

      ...
        return okHttpClientBuilder.build();
    }

    可以看到在这里跟OkHttpClient产生了关联。关于OkHttpClient.Builder的获取有两条途径;一条途径是通过手动配置的方式进行;第二条是采用默认配置的方式;

 private Method findOkHttpClientBuilderMethod(Class<?> retrofitClientInterfaceClass) {
        Method[] methods = retrofitClientInterfaceClass.getMethods();
        for (Method method : methods) {
            if (Modifier.isStatic(method.getModifiers())
                    && method.isAnnotationPresent(OkHttpClientBuilder.class)
                    && method.getReturnType().equals(OkHttpClient.Builder.class)) {
                return method;
            }
        }
        return null;
    }
   上述代码描述需要定义一个static修饰的接口 返回类型是OkHttpClient.Builder 并且使用注解OkHttpClientBuilder;通过这种方式来进行定义;具体如下

在接口中定义如下方法

  @OkHttpClientBuilder
    static OkHttpClient.Builder okhttpClientBuilder() {
        return new OkHttpClient.Builder()
                .connectTimeout(1, TimeUnit.MILLISECONDS)
                .readTimeout(1, TimeUnit.MILLISECONDS)
                .writeTimeout(1, TimeUnit.MILLISECONDS).retryOnConnectionFailure(false);

    }
默认方式定义即不在api接口中定义上述的方法,直接在注解中配置比如
@RetrofitClient(baseUrl = "${baseUrl}",
        connectTimeoutMs=1000,
        readTimeoutMs=1000,
        writeTimeoutMs=1000)
public interface DemoAPI {
}

    这种方式比较死板;定义的属性参数比较少,因为注解中定义的参数较少,大部分都是在源码中定死了,因此如果要开放的动态配置,可以采用第一种方式;可以通过上述两种方式配置超时时间等一些自定义配置;还有一种缺陷就是没法使用占位符的方式配置超时时间,因为超时时间是int类型,编译不通过

小结:

        spring基本组件扫描,延伸出自定义组件扫描,Retrofit实际就是自定义注解扫描;后续如果想做一个基础的组件服务,使用自定义注解,可以参考这套方案,继承ClassPathBeanDefinitionScanner 

        Retrofit的注解方式的层层递进扫描注入原理其实跟Mybatis原理类似MapperScan ComponentScan;一法通万法通

参考资料

Spring ClassPathBeanDefinitionScanner 浅析

https://www.jianshu.com/p/d5ffdccc4f5d

Retrofit SpringBoot引用

https://blog.youkuaiyun.com/why_still_confused/article/details/108041657

https://blog.youkuaiyun.com/afreon/article/details/110730127?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-7&spm=1001.2101.3001.4242

注意:maven依赖包版本要适合

git官网链接

https://github.com/LianjiaTech/retrofit-spring-boot-starter

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值