2022-04-15 springboot + retrofit2 服务间通信封装

选择已经完善的

retrofit-spring-boot-starter

其它

项目简单用不到或者之前已经开始用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整合的简易改进至此完成

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容