模仿Spring注入接口的代理类全过程

news2024/9/20 16:50:53

前言

在使用mybatis或者openFeign时只定义了一个接口类,并无实现类,可以把接口注入到service中并且能调用方法返回值。一个接口并无实现类,为什么可以实例化并且交给了spring管理。mybatis,OpenFeign又是怎么实现的?接下来给大家一一揭晓

1.先自定义注解

用于SpringBootApplication启动类。启动类加上CkScan注解,注解值即需要扫描那些包接口。springboot在启动时,发现注解里面Import导入CkScannerRegistrar类,会解析此类,此步就是实现入口。CkScannerRegistrar类下面会讲解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({CkScannerRegistrar.class})
public @interface CkScan {

    String[] value() default {};

    String[] basePackages() default {};

}
@org.springframework.boot.autoconfigure.SpringBootApplication
@MapperScan("com.ck.datacenter.**.dao")
@CkScan("com.ck.datacenter.itf")
@EnableOpenApi
public class SpringBootApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringBootApplication.class);
        application.run();
    }

}

2、CkScannerRegistrar类实现

解析此类时发现实现了spring的ImportBeanDefinitionRegistrar接口并且重新写了registerBeanDefinitions方法,会调用此方法。里面关键点new CkClassPathScanner类并且调用了doScan。
 

public class CkScannerRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        // 获取SpringBootApplication自定义注解CkScan值
        AnnotationAttributes attrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(CkScan.class.getName()));

        if (attrs != null) {
            List<String> basePackages = new ArrayList<>();
            basePackages.addAll(Arrays.stream(attrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
            basePackages.addAll(Arrays.stream(attrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));

            //将接口转换为BeanDefinition对象放入spring中
            //CkClassPathScanner为自定义扫描类
            CkClassPathScanner classPathScanner = new CkClassPathScanner(beanDefinitionRegistry);
            classPathScanner.doScan(StringUtils.collectionToCommaDelimitedString(basePackages));
        }

    }

}

3、CkClassPathScanner实现

继承ClassPathBeanDefinitionScanner扫描类,重写里面doScan,上步已经调用了doScan方法,进入此方法,调用了父类super.doScan(basePackages)得到所有满足条件BeanDefinitionHolder对象(接口一个包装类)。
扫描过滤条件:doScan中的addIncludeFilter方法可以增加过滤条件,isCandidateComponent方法也可以进行条件过滤得到所有BeanDefinitionHolder对象后,调用processBeanDefinitions进行加工处理,此方法也是关键

public class CkClassPathScanner extends ClassPathBeanDefinitionScanner {

    public CkClassPathScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        // 增加过滤,为接口类,并且接口上包含CkInterfaceAnnotation注解
        return beanDefinition.getMetadata().isInterface() &&
                beanDefinition.getMetadata().isIndependent() &&
                beanDefinition.getMetadata().hasAnnotation(CkInterfaceAnnotation.class.getName());
    }

    @Override
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            System.out.println("Skipping MapperFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface. Bean already defined with the same name!");
            return false;
        }
    }

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

        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            System.out.println("未扫描到有CkInterfaceAnnotation注解接口");
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {

        // 此段作用,将所有带CkInterfaceAnnotation注解接口,定义成beanDefinition对象
        // beanDefinitions中的bean对象指向接口的代理类
        // 在使用@Autowired注解注入接口时,其实注入的是接口代理对象

        beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            System.out.println("接口名称" + beanClassName);

            // 设置CkFactoryBean构造方法参数
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(CkFactoryBean.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        });
    }
}

4、processBeanDefinitions方法实现

直接看注释理解

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        // 可以理解为,将所有接口类转换为beanDefinition对象,
        // beanName为接口名称,bean对应实际实例化对象需要从CkFactoryBean对象对应的getObject获取
        // 在使用@Autowired注解注入接口时,其实注入的是接口代理对象,即CkFactoryBean类中getObject方法获取对象
        beanDefinitions.forEach((BeanDefinitionHolder holder) -> {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            System.out.println("接口名称" + beanClassName);

            // 实例化CkFactoryBean类时构造方法传的参数
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(CkFactoryBean.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
        });
    }

5、CkFactoryBean类实现

实现spring的FactoryBean接口即可。spring在初始化bean时,会调用getObject方法获取实例,我们只需要在此方法返回接口的代理类即可。在service中调用接口的方法时,实际就会调用到我们写的CkInterfaceProxy代理类
 

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

    private Class<T> ckInterface;

    public CkFactoryBean() {
    }

    public CkFactoryBean(Class<T> ckInterface) {
        this.ckInterface = ckInterface;
    }

    /**
     * bean实例化对象,指向代理类即可
     */
    @Override
    public T getObject() throws Exception {
        // 返回CkInterfaceProxy代理对象
        return (T) Proxy.newProxyInstance(ckInterface.getClassLoader(),
                new Class[]{ckInterface},
                new CkInterfaceProxy<>(ckInterface));
    }

    /**
     * bean对象类型
     */
    @Override
    public Class<T> getObjectType() {
        return this.ckInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

6、CkInterfaceProxy代理类的实现

比如我们使用OpenFeign,我们在此步做处理,获取类上注解和方法上的注解,通过类上注解值再从注册中心获取实际的服务器,再拼接方法上注解路径,就得到完整请求路径

public class CkInterfaceProxy<T> implements InvocationHandler, Serializable {

    private final Class<T> ckInterface;

    public CkInterfaceProxy(Class<T> ckInterface) {
        this.ckInterface = ckInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (this.ckInterface.isAnnotationPresent(CkInterfaceAnnotation.class)) {
            // 读取类上注解
            CkInterfaceAnnotation interfaceAnnotation = this.ckInterface.getAnnotation(CkInterfaceAnnotation.class);
            System.out.println("调用接口类名:" + interfaceAnnotation.value());
            if (method.isAnnotationPresent(CkMethodAnnotation.class)) {
                // 读取方法上注解
                CkMethodAnnotation methodAnnotation = method.getAnnotation(CkMethodAnnotation.class);
                System.out.println("调用接口方法名:" + methodAnnotation.value());
            }
        }

        return null;
    }
}

7、测试

增加接口类,并且没有任何类实现此接口

 Controller里面注入接口,调用Controller接口方法,日志是代理类打印出来

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/60573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

生物化学 核磁共振 氢谱 n+1定律 邻碳耦合 同碳耦合

核磁共振氢谱 基础 自旋量子数 自旋为S的粒子&#xff0c;取向的范围为[-S,S],但是需要间隔1。比如质子的自旋为1/2,则有-1/2 &#xff0c;1/2两个取向。取值的个数等于在外加磁场的情况下能够分裂的能级的个数。但是质量数和原子序数都为0的原子(C12,O16C^{12},O^{16}C12,O1…

Java ~ Executor ~ LinkedBlockingQueue【总结】

一 概括 简介 LinkedBlockingQueue&#xff08;链接阻塞队列&#xff09;类是BlockingQueue&#xff08;阻塞队列&#xff09;接口的主要实现类之一&#xff0c;也是Executor&#xff08;执行器&#xff09;框架最常搭配使用的实现之一&#xff0c;采用链表的方式实现。相比基于…

Qt+opencv 鼠标画线实现几何图形识别并动态创建

前言 使用Qt OpenCV实现&#xff0c;通过鼠标画线绘制几何图形&#xff0c;然后通过opencv进行图形轮廓识别&#xff0c;返回图形顶点&#xff0c;然后创建对应的几何图形添加到场景中。绘制使用QGraphics体系完成。 看效果图&#xff1a; 本文demo在这里 点击下载 环境: …

python在centos下安装以及配置

python在centos下安装以及配置 1.背景 centos下默认的都是python2.7下载需要更换为3.x使用&#xff0c;目前大部分应用都是基于pyhton3了 具体步骤&#xff1a; 我下载一个3.8.15的包 https://www.python.org/ftp/python/3.8.15/Python-3.8.15.tgz 小注释&#xff1a;如果…

动手学深度学习(2)—— 线性神经网络

文章目录线性神经网络线性回归线性回归从零开始的实现生成数据集读取数据集初始化模型参数定义模型定义损失函数定义优化算法训练线性回归的简洁实现生成数据集读取数据集定义模型初始化模型参数定义损失函数定义优化算法训练softmax 回归softmax运算交叉熵损失图像分类数据集读…

浅谈降维实操,一种用于处理特征的方式——后附Python代码

&#x1f466;&#x1f466;一个帅气的boy&#xff0c;你可以叫我Love And Program &#x1f5b1; ⌨个人主页&#xff1a;Love And Program的个人主页 &#x1f496;&#x1f496;如果对你有帮助的话希望三连&#x1f4a8;&#x1f4a8;支持一下博主 降维实操前言线性降维低…

list的模拟实现(万字解读+由浅入深)

先申明一下本篇总体介绍过程是按照逐步深入去写的&#xff0c;所以可能有些同样类型不在一块&#xff01; 前言&#xff1a; 写这篇博客的时候&#xff0c;我是边思考边写它&#xff01;自己其中感觉自己对于list的理解更加的深入&#xff0c;其中提出的很多问题让我明白了lis…

Android Studio 实现桌面小组件(APPWidget)

前言 微件是定制主屏幕的一个重要方面。它允许您从用户的主屏幕直接看到最重要的应用程序数据和功能。用户可以在主屏幕面板之间移动微件、调整其大小&#xff0c;并根据自己的喜好自定义微件中的信息量。微贱类型主要分为&#xff1a;信息微件&#xff08;显示对用户来说很重…

Service详解

Service详解 文章目录Service详解Service介绍kube-proxy目前支持三种工作模式:userspace 模式iptables 模式ipvs 模式Service类型Service使用实验环境准备ClusterIP类型的ServiceEndpoint负载分发策略HeadLiness类型的ServiceNodePort类型的ServiceLoadBalancer类型的ServiceEx…

嵌入式Linux 开发经验:编写用户态应用程序 ioctl 控制 misc 设备

参考文章 VSCode SSH 连接远程ubuntu Linux 主机 ubuntu 20.04 qemu linux6.0.1 开发环境搭建 ubuntu 20.04 qemu linux6.0.1 制作ext4根文件系统 嵌入式Linux 开发经验&#xff1a;platform_driver_register 的使用方法 嵌入式Linux 开发经验&#xff1a;注册一个 misc 设…

阿里巴巴专场——第322场周赛题解

目录 模拟法&#xff1a;6253.回环句 排序后模拟&#xff1a;6254. 划分技能点相等的团队 BFS&#xff1a;6255. 两个城市间路径的最小分数 BFS&#xff1a;6256. 将节点分成尽可能多的组 模拟法&#xff1a;6253.回环句 这道题直接按照题目的意思暴力模拟即可&#xff1a;…

Ubuntu20.04 安装配置 Ros2

记录一下折磨了一周的ros2配置qaq以及踩的无数坑 第一次按照一个教程安装后&#xff0c;命令行输入sudo apt-update 报错 The repository http://packages.ros.org/ros/ubuntu $(lsb_release-sc) Release does not have a Release file. 卸载后&#xff0c;按照第二个教程安装…

(十) 共享模型之内存【有序性】

JVM 会在不影响正确性的前提下&#xff0c;可以调整语句的执行顺序这种特性称之为『指令重排』&#xff0c;多线程下『指令重排』会影响正确性。为什么要有重排指令这项优化呢&#xff1f;从 CPU 执行指令的原理来理解一下吧 一、原理之指令级并行&#xff08;了解&#xff09;…

[附源码]Python计算机毕业设计Django企业人事管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

如何使用HTML制作个人网站( web期末大作业)

&#x1f4c2;文章目录一、&#x1f468;‍&#x1f393;网站题目二、✍️网站描述三、&#x1f4da;网站介绍四、&#x1f310;网站演示五、⚙️ 网站代码&#x1f9f1;HTML结构代码&#x1f492;CSS样式代码六、&#x1f947; 如何让学习不再盲目七、&#x1f381;更多干货一…

Linux安装mysql

1、 查看是否已经安装 Mysql rpm -qa | grep mysql 如果你查看出来有东西&#xff0c;可以使用下面命令将其删除 rpm -e 文件名 2 、下载官方 Mysql 包 wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm 如果安装有提示&#xff1a;Cannot…

HTML5期末大作业:北京旅游网页设计制作(1页) 简单静态HTML网页作品 我的旅游网页作业成品 学生旅游网站模板

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

gin 集成 Swagger

前言 一个好的项目工程&#xff0c;必然离不开一个好的 API 文档&#xff0c;如果要自己编写 API 文档&#xff0c;维护起来比较困难&#xff0c;而且难以保证一致性&#xff0c;因此我们要自动生成在线接口文档。 swaggo swagger 在 java 里面&#xff0c;是一个非常流行的…

后渗透之日志分析实验

目录 一、实验项目名称 二、实验目的 三、实验内容 四、实验环境 五、实验步骤 六、实验结果 七、实验总结 一、实验项目名称 后渗透之日志分析实验 二、实验目的 1.掌握meterpreter进行端口转发的方法 2.掌握网站日志的分析方法 三、实验内容 针对目标网站服务器…

AlphaFold2源码解析(7)--模型之Evoformer

AlphaFold2源码解析(7)–模型之Evoformer 这篇文章我们主要药讲解AlphaFold2的Evoformer的代码细节。 Evoformer Stack 该网络有一个双塔结构&#xff0c;在MSA堆栈中具有轴向的自我注意&#xff1b;在Pair堆栈中具有三角形的乘法更新和三角形的自我注意&#xff1b;以及外积…