源码分析
核心注解:
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
注意:maven依赖包版本要适合
git官网链接