【java sdk开发】参考spring注入接口代理类方式,注入接口代理类开发、open-sdk开发、sdk基础开发

背景:

我们在使用openFeign时只定义了相关的接口,并没有定义对应的实现类,但是最终实现了服务的远程调用。

最终实现经过了以下几个步骤:

    1.Spring在启动的时候对接口特定的注解进行了扫描@FeignClient(value = "provider"),之后生成了该接口的代理类。

    2.在proxy代理类中,获取到接口上注解FeignClient注解的value值,之后去注册中心(如eureka、nacos、zk)获取对应服务的ip及端口。

    3.在代理类中获取当前调用的method方法上的注解@RequestMapping("/open")的value值,将第二步中的ip与value拼接成一个完整的url

    4.使用okhttp或者httpclient调用目标服务的接口

但是这个只适用于公司内部系统之间的调用,因为使用openFeign不适合通过网关的场景,如果需要給外部公司或者系统需要调用,就得通过网关掉用,但是网关通常会有很多的鉴权参数之类的信息,所以需要额外的定制业务。或者各个外部系统自行实现其客户端的初始化。非常麻烦。

所以本文主要是参考openFeign,实现一个对外的接口的sdk,让客户只需要像openFeign一样实现一个接口,并且打上对应注解就行,不需要其关心其他的信息

实现方式:

1. 定义扫描包注解

该注解为了提示springboot在启动时,发现注解里面Import导入TestRegisterScan类,之后会自动解析此类

@Import(TestRegisterScan.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface TestScan {
     String back() default "";
}
@SpringBootApplication
back为扫描的包路径
@TestScan(back = "com.example.test1.bean")
public class Test1Application{
    public static void main(String[] args) {
        SpringApplication spring = new SpringApplication(Test1Application.class);
        ConfigurableApplicationContext run = spring.run(args);
    }
}
2.TestRegisterScan类实现

spring解析该类时发现该注册类实现了ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法

public class TestRegisterScan implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //取到springApplication上的注解对象
        AnnotationAttributes annotationAttributes =
                AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(TestScan.class.getName()));

        if(annotationAttributes != null){
            List<String> basePackages = new ArrayList<>();
            basePackages.addAll(Arrays.stream(annotationAttributes.getStringArray("back")).filter(StringUtils::hasText).collect(Collectors.toList()));
            //TestClassPathScan为自定义扫描类
            TestClassPathScan scan = new TestClassPathScan(registry);
            scan.doScan(StringUtils.collectionToCommaDelimitedString(basePackages));
        }
    }
}
3.实现自定义扫描类

该类继承ClassPathBeanDefinitionScanner扫描类,在实例化时将BeanDefinitionRegistry传入,并将其放入父类的构造方法。重写里面的doScan方法,在调用时使用扫描包路径调用父类的doScan对该包路径进行扫描,扫描出符合isCandidateComponent的接口或者类,并返回其beanDefinitionHolders(bean定义的持有者),该持有者中有其BeanDefinition(bean的定义)。之后对其BeanDefinition进行处理(参考processBeanDefinitions方法),在这里将BeanDefinition中的实例化工厂bean替换成自定义的工厂bean(非常关键)

public class TestClassPathScan extends ClassPathBeanDefinitionScanner {
    public TestClassPathScan(BeanDefinitionRegistry registry) {
        super(registry);
    }

    //不加重写这个方法就无法扫描出接口
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//         增加过滤,为接口类,并且接口上包含TestAnnotation注解
        return beanDefinition.getMetadata().isInterface() &&
                beanDefinition.getMetadata().isIndependent() &&
                beanDefinition.getMetadata().hasAnnotation(TestAnnotation.class.getName()) || !beanDefinition.getMetadata().isInterface();
    }



    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // spring默认不会扫描接口,此处设置为true,不做过滤
        this.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);

        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        if(beanDefinitionHolders.isEmpty()){
            System.out.println("没有扫描到包" + basePackages[0]);
        }else{
            processBeanDefinitions(beanDefinitionHolders);
        }
        return beanDefinitionHolders;
    }

    public void processBeanDefinitions(Set<BeanDefinitionHolder> definitionHolders){
        for(BeanDefinitionHolder beanDefinitionHolder : definitionHolders){
            GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
            String beanClassName = beanDefinition.getBeanClassName();
            //偷天换日,将其换成自定义的
            beanDefinition.setBeanClass(TestFactory.class);
           //实例化工厂bean时构造方法参数入参新增为bean的名称,这部也必须有
           beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
        }
    }
}

4.工厂bean的定义

在构造方法可以获取到bean的class对象,之后spring在用到该bean时,会根据bean的名称或者类型找到该工厂bean,最后调用getObject方法获取bean

public class TestFactory<T> implements FactoryBean<T> {

    private Class<T> beanClass;

    public TestFactory(Class<T> clazz){
        this.beanClass = clazz;
    }

    @Override
    public T getObject() throws Exception {
    	System.out.println("-----类名------" + beanClass.getName() + "-------" + Arrays.toString(beanClass.getInterfaces()));

        //为接口用上面的代理  这个参数要是接口,若代理类本身就是接口直接使用new Class[]{beanClass},不为接口才getInterfaces
        return (T) Proxy.newProxyInstance(beanClass.getClassLoader(), new Class[]{beanClass},
                 new TestProxy(beanClass));
        //非接口代理
        //T bean = (T) beanClass.getConstructor().newInstance();
        //return (T) Proxy.newProxyInstance(beanClass.getClassLoader(), bean.getClass().getInterfaces(),new TestProxy(bean));
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

5.代理类定义

最后在代理类的invoke方法中,实现各自接口的代理实现,当代理对象调用方法时会调用其invoke方法,这时可通过method获取代理接口的注解,之后解析类注解和自定义注解获取完整路径,之后组装相关的参数(鉴权或者其他前置的自定义的业务),最后使用httpclient或者okhttp进行远程调用并返回

public class TestProxy<T> implements InvocationHandler {

    private Class<T> beanClass;

    private T bean;

    //代理非接口使用
    public TestProxy(T bean) {
        this.bean = bean;

    }
    //代理接口使用
    public TestProxy(Class<T> beanClass) {
        this.beanClass = beanClass;

    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
    	System.out.println("--x--"+beanClass.getName());
    	//TestAnnotation注解只是用于标识代理的是否为接口,仅测试中用
        TestAnnotation annotation = beanClass.getAnnotation(TestAnnotation.class);
        System.out.println(bean.getClass().getName());
        if(ObjectUtils.isEmpty(annotation)){
            System.out.println("------非注解接口代理------" + bean);
            return method.invoke(bean, objects);
        }else{
        	//代理类业务实现后使用httpclient或okhttp进行远程调用
            System.out.println("------注解接口代理-----");
            return (Integer)objects[0] + 100;
        }
    }
}

最终效果

测试类:

//spring中获取接口代理类
ConfigurableApplicationContext run = spring.run(args);
Object zzz = run.getBean("testzzzInterface");
System.out.println("---返回值----" + ((testzzzInterface)zzz).getTest1(1));
       
//被代理类定义
@TestAnnotation
public interface testzzzInterface {
    int getTest1(int k);
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list型的高级数据结构,更适合论坛帖子列表这种型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库中的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作 jared jared是一个用来操作Windows注册表的 Java 库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList库 Blister Blister是一个用于操作苹果二进制PList文件格式的Java开源库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值