选择已经完善的
其它
项目简单用不到或者之前已经开始用retrofit2,但是不够优雅,所以学习大佬的封装,改进了一点自己的项目。
之前
安装retrofit2教程写了多个bean,比如
public interface BgProductBasicApi {
//保存商品
@POST("/product/basic/save")
Result saveProductBasic(@Body BgSaveProductBasicDto dto);
//删除商品
@DELETE("/product/basic/{id}")
Result delProductBasic(@Path("id") Integer id);
//分页查询商品
@POST("/product/basic/page")
Result pageProductBasic(@Body BgPageProductBasicDto dto);
//查询商品详情
@GET("/product/basic/{id}")
Result getProductBasic(@Path("id") Integer id);
@GET("/product/list")
Result getProductIdList(@Query("appKey") String appKey);
}
public interface BgProductPriceApi {
@POST("/product/price/save")
Result saveProductPrice(@Body BgSaveProductPriceDto dto);
@DELETE("/product/price/{id}")
Result delProductPrice(@Path("id") Integer id);
@POST("/product/price/page")
Result pageProductPrice(@Body BgPageProductPriceDto dto);
@GET("/product/price/{id}")
Result getProductPrice(@Path("id") Integer id);
}
如果需要在消费者端调用,就需要通过bean一个一个创建接口,例如
@Configuration
@Slf4j
public class ProductRestAdapterConfig {
@Value("${api.product.pre:http}")
String orderPre;
@Value("${api.product.url:127.0.0.1}")
String orderUrl;
@Value("${api.product.port:80}")
String orderPort;
@Bean
public BgProductBasicApi getBgProductBasicApi(Retrofit productRestAdapter) {
return productRestAdapter.create(BgProductBasicApi.class);
}
@Bean
public BgProductPriceApi getBgProductPriceApi(Retrofit productRestAdapter) {
return productRestAdapter.create(BgProductPriceApi.class);
}
@Bean(name = "productRestAdapter")
public Retrofit getProductRestAdapter() {
/**
* setEndpoint("http://localhost:8081"):指定基本的URL,
* API接口中的URL是相对于该URL的路径的,
* 不能少了协议名,例如写成:localhost:8081就不行
*/
return new Retrofit.Builder()
.baseUrl(orderPre + "://" + orderUrl + ":" + orderPort)
.client(OkHttpUtils.getOkHttpClient())
.addConverterFactory(Retrofit2ConverterFactory.create())
.addCallAdapterFactory(new ResultCallAdapterFactory())
.build();
}
}
这样写有2个问题
一随着接口增加,Configuration里的配置越来越多;
二 生产者也需要引用这些接口,无形中注入了很多没有用的bean;
所有在这里将改成自动注入的方式
核心:
sprngboot 批量注入多个bean
springboot enable**的@Import
首先定义一个注解:EnableRetrofitInterface 当开启注解时接口才会注入容器
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AutoConfiguredRetrofitScannerRegistrar.class})
public @interface EnableRetrofitInterface {
}
其次定义一个注解:RetrofitInterface 表示接口是一个retrofit2接口可用来远程调用,里面需要url地址和path(对标rescontroller上统一的path)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface RetrofitInterface {
String baseUrl() default "";
String path() default "";
}
由于我们在Enable中引入了AutoConfiguredRetrofitScannerRegistrar,所以我们要向springbootApplication一样扫描Enable包下的所有类
@Slf4j
public class AutoConfiguredRetrofitScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
private ClassLoader classLoader;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
log.debug("Could not determine auto-configuration package, automatic retrofit scanning disabled.");
return;
}
log.debug("Searching for retrofits annotated with @RetrofitClient");
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (log.isDebugEnabled()) {
packages.forEach(pkg -> log.debug("Using auto-configuration base package '{}'", pkg));
}
// Scan the @RetrofitClient annotated interface under the specified path and register it to the BeanDefinitionRegistry
ClassPathRetrofitInterfaceScanner scanner = new ClassPathRetrofitInterfaceScanner(registry, classLoader);
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
String[] packageArr = packages.toArray(new String[0]);
scanner.registerFilters();
// Scan and register to BeanDefinition
scanner.doScan(packageArr);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}
之后在ClassPathRetrofitInterfaceScanner 中将筛选满足要求的类(RetrofitInterface)单独处理
@Slf4j
public class ClassPathRetrofitInterfaceScanner extends ClassPathBeanDefinitionScanner {
private final ClassLoader classLoader;
public ClassPathRetrofitInterfaceScanner(BeanDefinitionRegistry registry, ClassLoader classLoader) {
super(registry, false);
this.classLoader = classLoader;
}
public void registerFilters() {
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(RetrofitInterface.class);
this.addIncludeFilter(annotationTypeFilter);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
log.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) {
log.error("load class exception:", ex);
}
}
return false;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
log.debug("Creating RetrofitClientBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' Interface");
definition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(definition.getBeanClassName()));
// beanClass全部设置为RetrofitFactoryBean
definition.setBeanClass(RetrofitFactoryBean.class);
}
}
}
通过工厂模式RetrofitFactoryBean批量输出Bean,引入EnvironmentAware 是为了替换注解中的配置变量
@Slf4j
public class RetrofitFactoryBean<T> implements FactoryBean<T>, EnvironmentAware {
private Class<T> retrofitClasses;
private Environment environment;
public RetrofitFactoryBean(Class<T> retrofitClasses) {
this.retrofitClasses = retrofitClasses;
}
@Override
public T getObject() throws Exception {
Assert.isTrue(retrofitClasses.isInterface(), "RetrofitInterface is only interface");
RetrofitInterface retrofitInterface = retrofitClasses.getAnnotation(RetrofitInterface.class);
String baseUrl = environment.resolveRequiredPlaceholders(retrofitInterface.baseUrl());
baseUrl = baseUrl + retrofitInterface.path();
OkHttpClient client = OkHttpUtils.createOkHttpClient();
Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(client);
// 添加CallAdapter.Factory
retrofitBuilder.addConverterFactory(Retrofit2ConverterFactory.create());
retrofitBuilder.addCallAdapterFactory(new ResultCallAdapterFactory());
Retrofit build = retrofitBuilder.build();
return build.create(retrofitClasses);
}
@Override
public Class<?> getObjectType() {
return retrofitClasses;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
这样基本框架就改好了,之后来看应用
现在可以直接删除ProductRestAdapterConfig
在接口上打上注解RetrofitInterface
@RetrofitInterface(baseUrl = "${api.product.url}")
public interface BgProductPriceApi {
@POST("/product/price/save")
Result saveProductPrice(@Body BgSaveProductPriceDto dto);
@DELETE("/product/price/{id}")
Result delProductPrice(@Path("id") Integer id);
@POST("/product/price/page")
Result pageProductPrice(@Body BgPageProductPriceDto dto);
@GET("/product/price/{id}")
Result getProductPrice(@Path("id") Integer id);
}
在消费者端的application上添加@EnableRetrofitInterface
原来引用是使用的@Autowired,虽然运行不会报错,但是在idea中还是会提示小红线,所以改为@Resource
retrofit2与springboot整合的简易改进至此完成