在《【spring-boot】项目下最优雅的http客户端工具,用它就够了》这篇文章中,我们知道了retrofit-spring-boot-starter
的使用方式。本篇文章继续继续介绍retrofit-spring-boot-starter
的实现原理,从零开始介绍如何在spring-boot项目中基于Retrofit实现自己的轻量级http调用工具。
项目源码:retrofit-spring-boot-starter
确定实现思路
我们首先直接看一下使用retrofit
原始API是如何发起一个http请求的。
-
定义接口
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); } 复制代码
-
创建接口代理对象
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); // 实际业务场景构建Retrofit比这复杂多了,这里最简单化处理 GitHubService service = retrofit.create(GitHubService.class); 复制代码
-
发起请求
Call<List<Repo>> repos = service.listRepos("octocat"); 复制代码
可以看到,Retrofit
本身已经很好的支持了通过接口发起htp
请求。但是如果我们项目每一个业务代码都要写上面的样板代码,会非常的繁琐。有没有一种方式让用户只关注接口定义,其它事情全部交给框架自动处理?这个时候我们可能会联想到spring-boot
项目下使用Mybatis
,用户只需要定义Mapper
接口和书写sql
即可,完全不用管与JDBC
的交互细节。与之类似,我们最终也要实现让用户只需要定义HttpService
接口,不用管其他底层实现细节。
相关知识介绍
为了方便后面的介绍,我们先得了解一下几个相关知识点。
spring容器初始化
我们首先要简单了解一下spring
容器初始化。简单来讲,spring
容器初始化主要包含以下2个步骤:
- 注册Bean定义:扫描并解析配置文件或者某些注解得到Bean属性(包括
beanName
、beanClassName
、scope
、isSingleton
等等),然后基于这个bean
属性创建BeanDefinition
对象,最后将其注册到BeanDefinitionRegistry
中。 - 创建Bean实例:根据
BeanDefinitionRegistry
里面的BeanDefinition
信息,创建Bean实例,并将实例对象保存到spring
容器中,创建的方式包括反射创建、工厂方法创建和工厂Bean(FactoryBean
)创建等等。
当然,实际的spring
容器初始化比这复杂的多,考虑到这块不是本文的重点,暂时这么理解就行。
Retrofit
对象简介
我们已经知道使用Retrofit
对象可以创建接口代理对象,接下来看一下Retrofit
的UML类图(只列出了我们关注的依赖):
通过分析UML类图,我们可以发现,构建Retrofit
对象的时候,可以注入以下4个属性:
HttpUrl
:http
请求的baseUrl
。CallAdapter
:将Call<T>
适配为接口方法返回值类型。Converter
:将@Body
标记的方法参数序列化为请求体数据;将响应体数据反序列化为响应对象。OkHttpClient
:底层发送http
请求的客户端对象。
而构建OkHttpClient
对象的时候,可以注入Interceptor
(请求拦截器)和ConnectionPool
(连接池)属性。
因此为了构建Retrofit
对象,我们要先创建HttpUrl
、CallAdapter
、Converter
和OkHttpClient
;而要构建OkHttpClient
对象就得先创建Interceptor
和ConnectionPool
。
实现详解
注册Bean定义
为了实现将HttpService
接口代理对象完全交由spring
容器管理,首先就得将HttpService
接口扫描并注册到BeanDefinitionRegistry
中。spring
提供了ImportBeanDefinitionRegistrar
接口,支持了自定义注册BeanDefinition
的功能。因此我们先定义RetrofitClientRegistrar
类用来实现上述功能。具体实现如下:
-
RetrofitClientRegistrar
RetrofitClientRegistrar
从@RetrofitScan
注解中提取出要扫描的基础包路径之后,将具体的扫描注册逻辑交给了ClassPathRetrofitClientScanner
处理。public class RetrofitClientRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware { private ResourceLoader resourceLoader; private ClassLoader classLoader; @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(RetrofitScan.class.getName())); // 扫描指定路径下@RetrofitClient注解的接口,并注册到BeanDefinitionRegistry ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader); if (resourceLoader != null) { scanner.setResourceLoader(resourceLoader); } //指定扫描的基础包 String[] basePackages = getPackagesToScan(attributes); scanner.registerFilters(); // 扫描并注册到BeanDefinition scanner.doScan(basePackages); } /** * 获取扫描的基础包路径 * * @return 基础包路径 */ private String[] getPackagesToScan(AnnotationAttributes attributes) { String[] value = attributes.getStringArray("value"); String[] basePackages = attributes.getStringArray("basePackages"); Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses"); if (!ObjectUtils.isEmpty(value)) { Assert.state(ObjectUtils.isEmpty(basePackages), "@RetrofitScan basePackages and value attributes are mutually exclusive"); } Set<String> packagesToScan = new LinkedHashSet<>(); packagesToScan.addAll(Arrays.asList(value)); packagesToScan.addAll(Arrays.asList(basePackages)); for (Class<?> basePackageClass : basePackageClasses) { packagesToScan.add(ClassUtils.getPackageName(basePackageClass)); } return packagesToScan.toArray(new String[packagesToScan.size()]); } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } } 复制代码
-
ClassPathRetrofitClientScanner
在
ClassPathRetrofitClientScanner
实现中,BeanDefinition
的beanClass
属性全部设置为了RetrofitFactoryBean.class
,同时将接口自身的类型传递到了RetrofitFactoryBean
的retrofitInterface
属性中。这说明,最终创建Bean实例是通过RetrofitFactoryBean
来完成的。public class ClassPathRetrofitClientScanner extends ClassPathBeanDefinitionScanner { private final ClassLoader classLoader; private final static Logger logger = LoggerFactory.getLogger(ClassPathRetrofitClientScanner.class); public ClassPathRetrofitClientScanner(BeanDefinitionRegistry registry, ClassLoader classLoader) { super(registry, false); this.classLoader = classLoader; } public void registerFilters() { AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(RetrofitClient.class); this.addIncludeFilter(annotationTypeFilter); } @Override 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; } @Override protected boolean isCandidateComponent( AnnotatedBeanDefinition beanDefinition) { if (beanDefinition.getMetadata().isInterface()) { try { Class<?> target = ClassUtils.forName( beanDefinition.getMetadata().getClassName(), classLoader); return !target.isAnnotation(); } catch (Exception ex) { logger.error("load class exception:", ex); } } return false; } 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
接口代理对象。
-
配置项和
@RetrofitClient
为了更加灵活的构建Retrofit
对象,我们可以通过配置项以及@RetrofitClient
注解属性传递一些动态参数信息。@RetrofitClient
包含的属性如下:baseUrl
:用来创建Retrofit
的HttpUrl
,表示该接口下所有请求都适用的基础url
。poolName
:该接口下请求使用的连接池的名称,决定了ConnectionPool
对象的取值。connectTimeoutMs/readTimeoutMs/writeTimeoutMs
:用于构建OkHttpClien
t对象的超时时间设置。logLevel/logStrategy
:配置该接口下请求的日志打印级别和日志打印策略,可用来创建日志打印拦截器Interceptor
。
-
RetrofitFactoryBean
RetrofitFactoryBean
实现逻辑非常复杂,概括起来主要包含以下几点:- 通过配置项数据以及
@RetrofitClient
注解数据完成了Retrofit
对象的构建。 - 每一个
HttpService
接口就会构建一个Retrofit
对象,每一个Retrofit
对象就会构建对应的OkHttpClient
对象。 - 可扩展的注解式拦截器是通过
InterceptMark
注解标记实现的,路径拦截匹配是通过BasePathMatchInterceptor
实现的。
public class RetrofitFactoryBean<T> implements FactoryBean<T>, EnvironmentAware, ApplicationContextAware { private Class<T> retrofitInterface; private Environment environment; private RetrofitProperties retrofitProperties; private RetrofitConfigBean retrofitConfigBean; private ApplicationContext applicationContext; public RetrofitFactoryBean(Class<T> retrofitInterface) { this.retrofitInterface = retrofitInterface; } @Override @SuppressWarnings("unchecked") public T getObject() throws Exception { checkRetrofitInterface(retrofitInterface); Retrofit retrofit = getRetrofit(retrofitInterface); return retrofit.create(retrofitInterface); } /** * RetrofitInterface检查 * * @param retrofitInterface . */ private void checkRetrofitInterface(Class<T> retrofitInterface) { // check class type Assert.isTrue(retrofitInterface.isInterface(), "@RetrofitClient只能作用在接口类型上!"); Method[] methods = retrofitInterface.getMethods(); for (Method method : methods) { Class<?> returnType = method.getReturnType(); Assert.isTrue(!void.class.isAssignableFrom(returnType), "不支持使用void关键字做返回类型,请使用java.lang.Void! method=" + method); if (retrofitProperties.isDisableVoidReturnType()) { Assert.isTrue(!Void.class.isAssignableFrom(returnType), "已配置禁用Void作为返回值,请指定其他返回类型!method=" + method); } } } @Override public Class<T> getObjectType() { return this.retrofitInterface; } @Override public boolean isSingleton() { return true; } /** * 获取okhttp3连接池 * * @param retrofitClientInterfaceClass retrofitClient接口类 * @return okhttp3连接池 */ private synchronized okhttp3.ConnectionPool getConnectionPool(Class<?> retrofitClientInterfaceClass) { RetrofitClient retrofitClient = retrofitClientInterfaceClass.getAnnotation(RetrofitClient.class); String poolName = retrofitClient.poolName(); Map<String, ConnectionPool> poolRegistry = retrofitConfigBean.getPoolRegistry(); Assert.notNull(poolRegistry, "poolRegistry不存在!请设置retrofitConfigBean.poolRegistry!"); ConnectionPool connectionPool = poolRegistry.get(poolName); Assert.notNull(connectionPool, "当前poolName对应的连接池不存在!poolName = " + poolName); return connectionPool; } /** * 获取OkHttpClient实例,一个接口接口对应一个OkHttpClient * * @param retrofitClientInterfaceClass retrofitClient接口类 * @return OkHttpClient实例 */ private synchronized OkHttpClient getOkHttpClient(Class<?> retrofitClientInterfaceClass) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { okhttp3.ConnectionPool connectionPool = getConnectionPool(retrofitClientInterfaceClass); RetrofitClient retrofitClient = retrofitClientInterfaceClass.getAnnotation(RetrofitClient.class); // 构建一个OkHttpClient对象 OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder() .connectTimeout(retrofitClient.connectTimeoutMs(), TimeUnit.MILLISECONDS) .readTimeout(retrofitClient.readTimeoutMs(), TimeUnit.MILLISECONDS) .writeTimeout(retrofitClient.writeTimeoutMs(), TimeUnit.MILLISECONDS) .connectionPool(connectionPool); // 添加接口上注解定义的拦截器 List<Interceptor> interceptors = new ArrayList<>(findInterceptorByAnnotation(retrofitClientInterfaceClass)); // 添加全局拦截器 Collection<BaseGlobalInterceptor> globalInterceptors = retrofitConfigBean.getGlobalInterceptors(); if (!CollectionUtils.isEmpty(globalInterceptors)) { interceptors.addAll(globalInterceptors); } interceptors.forEach(okHttpClientBuilder::addInterceptor); // 日志打印拦截器 if (retrofitProperties.isEnableLog()) { Class<? extends BaseLoggingInterceptor> loggingInterceptorClass = retrofitProperties.getLoggingInterceptor(); Constructor<? extends BaseLoggingInterceptor> constructor = loggingInterceptorClass.getConstructor(Level.class, BaseLoggingInterceptor.LogStrategy.class); BaseLoggingInterceptor loggingInterceptor = constructor.newInstance(retrofitClient.logLevel(), retrofitClient.logStrategy()); okHttpClientBuilder.addInterceptor(loggingInterceptor); } // http异常信息格式化 HttpExceptionMessageFormatterInterceptor httpExceptionMessageFormatterInterceptor = retrofitConfigBean.getHttpExceptionMessageFormatterInterceptor(); if (httpExceptionMessageFormatterInterceptor != null) { okHttpClientBuilder.addInterceptor(httpExceptionMessageFormatterInterceptor); } return okHttpClientBuilder.build(); } /** * 获取retrofitClient接口类上定义的拦截器集合 * * @param retrofitClientInterfaceClass retrofitClient接口类 * @return 拦截器实例集合 */ @SuppressWarnings("unchecked") private List<Interceptor> findInterceptorByAnnotation(Class<?> retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException { Annotation[] classAnnotations = retrofitClientInterfaceClass.getAnnotations(); List<Interceptor> interceptors = new ArrayList<>(); // 找出被@InterceptMark标记的注解 List<Annotation> interceptAnnotations = new ArrayList<>(); for (Annotation classAnnotation : classAnnotations) { Class<? extends Annotation> annotationType = classAnnotation.annotationType(); if (annotationType.isAnnotationPresent(InterceptMark.class)) { interceptAnnotations.add(classAnnotation); } } for (Annotation interceptAnnotation : interceptAnnotations) { // 获取注解属性数据 Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(interceptAnnotation); Object handler = annotationAttributes.get("handler"); Assert.notNull(handler, "@InterceptMark标记的注解必须配置: Class<? extends BasePathMatchInterceptor> handler()"); Assert.notNull(annotationAttributes.get("include"), "@InterceptMark标记的注解必须配置: String[] include()"); Assert.notNull(annotationAttributes.get("exclude"), "@InterceptMark标记的注解必须配置: String[] exclude()"); Class<? extends BasePathMatchInterceptor> interceptorClass = (Class<? extends BasePathMatchInterceptor>) handler; BasePathMatchInterceptor interceptor = getInterceptorInstance(interceptorClass); Map<String, Object> annotationResolveAttributes = new HashMap<>(8); // 占位符属性替换 annotationAttributes.forEach((key, value) -> { if (value instanceof String) { String newValue = environment.resolvePlaceholders((String) value); annotationResolveAttributes.put(key, newValue); } else { annotationResolveAttributes.put(key, value); } }); // 动态设置属性值 BeanExtendUtils.populate(interceptor, annotationResolveAttributes); interceptors.add(interceptor); } return interceptors; } /** * 获取路径拦截器实例,优先从spring容器中取。如果spring容器中不存在,则无参构造器实例化一个 * * @param interceptorClass 路径拦截器类的子类,参见@{@link BasePathMatchInterceptor} * @return 路径拦截器实例 */ private BasePathMatchInterceptor getInterceptorInstance(Class<? extends BasePathMatchInterceptor> interceptorClass) throws IllegalAccessException, InstantiationException { // spring bean try { return applicationContext.getBean(interceptorClass); } catch (BeansException e) { // spring容器获取失败,反射创建 return interceptorClass.newInstance(); } } /** * 获取Retrofit实例,一个retrofitClient接口对应一个Retrofit实例 * * @param retrofitClientInterfaceClass retrofitClient接口类 * @return Retrofit实例 */ private synchronized Retrofit getRetrofit(Class<?> retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { RetrofitClient retrofitClient = retrofitClientInterfaceClass.getAnnotation(RetrofitClient.class); String baseUrl = retrofitClient.baseUrl(); // 解析baseUrl占位符 baseUrl = environment.resolveRequiredPlaceholders(baseUrl); OkHttpClient client = getOkHttpClient(retrofitClientInterfaceClass); Retrofit.Builder retrofitBuilder = new Retrofit.Builder() .baseUrl(baseUrl) .client(client); // 添加CallAdapter.Factory List<CallAdapter.Factory> callAdapterFactories = retrofitConfigBean.getCallAdapterFactories(); if (!CollectionUtils.isEmpty(callAdapterFactories)) { callAdapterFactories.forEach(retrofitBuilder::addCallAdapterFactory); } // 添加Converter.Factory List<Converter.Factory> converterFactories = retrofitConfigBean.getConverterFactories(); if (!CollectionUtils.isEmpty(converterFactories)) { converterFactories.forEach(retrofitBuilder::addConverterFactory); } return retrofitBuilder.build(); } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; this.retrofitConfigBean = applicationContext.getBean(RetrofitConfigBean.class); this.retrofitProperties = retrofitConfigBean.getRetrofitProperties(); } 复制代码
- 通过配置项数据以及
这样,我们就完成了创建HttpService
Bean实例的功能了。在使用的时候直接注入HttpService
,然后调用其方法就能发送对应的http
请求。
结语
总的来说,在spring-boot项目中基于Retrofit实现自己的轻量级http调用工具的核心只有两点:第一是注册HttpService
接口的BeanDefinition
,第二就是构建Retrofit
来创建HttpService
的代理对象。如需了解更多细节,建议直接查看retrofit-spring-boot-starter源码。