【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);
}

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值