自己写Spring组件-01手写Bean注册器

news2024/12/26 0:46:49

简介

在进行web开发的时候,我们经常使用@Component、@Services、@Controller等注解去声明Bean,然后Spring就会帮我们注册。
如果我们想要自己写一个组件(方便后期使用),进行无感引入的时候,就需要自己去注册Bean,所以实现自写组件第一步,先整个Bean注册器。
代码示例

定义元注解

这里的元注解的意思就是类似于Controller、Service这样的注解,直接在类上加入,Spring就会帮我们自动创建。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Meta {
        /**
* 元注解:类似于Component
*/
    }

定义扫描注解

定义完元注解后,我们还需要定义一个扫描注解,告诉Spring我要扫描哪些包里面的类。就像MapperScan一样,你里面可以没有值,但是你得加上这个注解。

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

    /**
     * 当指定了值的时候,主要加载这些包路径下,包含@Meta注解的类;
     * 如果全是默认值(即为空),则扫描这个注解所在类对应的包路径下所有包含@Meta的类
     * @return
     */
    @AliasFor("basePackages") String[] value() default {};

    @AliasFor("value") String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

扫描类

元注解,扫描注解现在都有了。现在还需要一个扫描类,这个类的作用就是过滤类,如果类上面有元注解,那么就把类拦下来。具体怎么实现的呢?

使用ClassPathBeanDefinitionScanner类:

在这里插入图片描述

可以看到他原来的scanner的构造方法中有个注册默认过滤器方法registerDefaultFilters

在这里插入图片描述

可以看到Component注解的过滤就是在此生效的,没看到Service等注解,可能是因为这是Spring的jar包,这个没深入了解,以后再研究。
可以看到Spring原生的就是一个ClassPathScanningCandidateComponentProvider类,然后设置扫描的注解,就可以过滤出相应的类。我们按照这个逻辑去仿写。
:::
首先继承ClassPathScanningCandidateComponentProvider,仿照他的构造函数全CV一遍。
因为它的构造方法中只有四个参数的那个有注册过滤器方法,所以我们就写这一个。


/**
 * 扫描类
 */
public class MetaBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    /**
     * 仿照原生的构造方法全部来一遍
     * @param registry Bean定义注册中心
     * @param useDefaultFilters 是否使用用户默认的filter
     * @param environment 整个spring应用运行时的环境信息:类似于application.yml 里面就是。
     * @param resourceLoader 对资源封装的加载器:可以从文件中、网络中、流中加载资源。
     *                       资源加载器是个接口,默认从类路径下加载。
     *                       通过getResource方法从字符串中查找路径。
     *                       可以从容器中获取ResourceLoader,调用getResource方法。
     */
    public MetaBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
                                     Environment environment, ResourceLoader resourceLoader) {
        super(registry, useDefaultFilters, environment, resourceLoader);
        registerFilters();
    }

    /**
     * 注册过滤器  在此类的构造函数中引用
     */
    protected void registerFilters() {
        //注册一个AnnotationTypeFilter,确保过滤获取所有@Meta注解的类
        addIncludeFilter(new AnnotationTypeFilter(Meta.class));
    }
}

Bean自动注册类

相关的准备工作完成了,就可以进行完成自动注册类了。
自动注册类我们需要实现三个接口。

ImportBeanDefinitionRegistrar接口:
会执行ImportBeanDefinitionRegistrar的registerBeanDefinitions方法,然后生成BeanDefinition对象,并最终注册到BeanDefinitionRegistry中,为后续实例化bean做准备。
ResourceLoaderAware接口:
获取资源加载器,可以获得外部资源文件。在Java类中可以通过文件流等的形式加载外部资源文件,并能读取资源文件中的配置,通过下面的get属性可以获取Properties中的配置属性。

//TestBean必须实现ResourceLoaderAware接口,才能加载资源
public static void main(String[] args)
  {
    // 创建ApplicationContext容器
    ApplicationContext ctx = new
      ClassPathXmlApplicationContext("beans.xml");
    // 获取容器中名为test的Bean实例
    TestBean tb = ctx.getBean("test" , TestBean.class);
    // 通过tb实例来获取ResourceLoader对象
    ResourceLoader rl = tb.getResourceLoader();
    // 判断程序获得ResourceLoader和容器是否相同
    System.out.println(rl == ctx);
  }

EnvironmentAware接口:
凡注册到Spring容器内的bean,实现了EnvironmentAware接口重写setEnvironment方法后,在工程启动时可以获得application.properties的配置文件配置的属性值。

 @Configuration
 public class MyProjectc implements EnvironmentAware {
    @Override
    public void setEnvironment(Environment environment) {
            String projectName = environment.getProperty("project.name");
            System.out.println(projectName);
    }
 }  

然后这是最终自动注册类代码,必要注释已经写进去了。

package org.example.demo;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * 核心类
 * 流程就是:因为我们这里的目标是注册所有带 @Meta 注解的类,
 * 扫描所有的类,判断是否有@Meta注解,有则通过 registry 手动注册
 */

public class MetaAutoConfigureRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    /**
     *
     */
    private ResourceLoader resourceLoader;

    private Environment environment;

    /**
     * 实现ResourceLoaderAware接口方法,可以获得外部资源xml、txt等。
     * @param resourceLoader
     */
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    /**
     * 实现EnvironmentAware接口方法,此属性可以获得application.properties中的属性值
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    /**
     * 核心!!注册bean
     * @param importingClassMetadata 注解元数据,多半是用来获取注解的属性
     * @param registry bean 定义注册器
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //新建一个元注解扫描类,过滤出来要进行新建的Bean
        MetaBeanDefinitionScanner scanner =
                new MetaBeanDefinitionScanner(registry, true, this.environment, this.resourceLoader);
        //设置扫描包路径
        Set<String> packagesToScan = this.getPackagesToScan(importingClassMetadata);
        //根据扫描包路径,元注解扫描类去扫描对应的包,过滤出对应的类
        //找到以后就会注册到Bean容器中,当然我们的代码到这里就结束了,再向下的步骤就是Spring自己干了
        scanner.scan(packagesToScan.toArray(new String[]{}));
    }

    /**
     * 找到要扫描的包路径,set存储
     * @param metadata 注解元数据
     * @return
     */
    private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
        //获取使用元数据扫描注解的类的注解属性值
        AnnotationAttributes attributes =
                AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(MetaComponentScan.class.getName()));
        //取出注解中的basePackages值
        String[] basePackages = attributes.getStringArray("basePackages");
        //取出注解中的basePackageClasses值
        Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
        //新建双向链表存储 包扫描路径
        Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages));
        for (Class clz : basePackageClasses) {
            //添加扫描路径
            packagesToScan.add(ClassUtils.getPackageName(clz));
        }

        if (packagesToScan.isEmpty()) {
            //如果到这里包扫描路径为空,说明使用MetaComponentScan注解的时候没有设定值
            //那么就按照这个注解使用地的包路径作为扫描路径
            packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
        }
        // 返回包路径
        return packagesToScan;
    }
}

测试

完成代码后,开始写测试了。测试类的思路也很简单,就是整一个SpringApplication,上面加上元注解扫描注解,然后整四个普通类,对照实验加上注解。在这四个类的构造函数中,输出string,这样就可以看出来顺序和是否实现注解了。
下面是四个类

@Meta
public class MetaDemo {
    public  MetaDemo() {
        System.out.println("MetaDemo类 注册成功!");
    }
}
@Component
public class NormalMeta {
    public NormalMeta() {
        System.out.println("component注解的普通bean");
    }
}

@Meta
public class DependBean {
    public DependBean(NormalMeta normalBean) {
        System.out.println("我是个Meta bean,我依赖于component注解下的bean " + normalBean);
    }
}
@Component
public class ABean {
    /**
     * 这里的构造函数用到MetaDemo,你的IDE可能会报错,因为无法自动装配
     * 因为Component等注解IDEA已经知道了,我们自定义的注解他还不认识,所以报错
     * 这里如果ABean也能成功构造,说明Bean已经注册成功了。
     * @param metaDemo
     */
    public ABean(MetaDemo metaDemo) {
        System.out.println("我在Bean容器中发现了Meta Bean : " + metaDemo);
    }
}

Main主类

@SpringBootApplication
@MetaComponentScan
public class SpringBootCreateBeanDemo {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootCreateBeanDemo.class, args);
    }
}

输出结果
在这里插入图片描述

可以看到顺序,单纯的使用元注解Meta的类先注册,然后Component注解注册,最后才是依赖于普通类的Meta类。至于为什么自定义的先注册这个我还没有搞懂顺序。
至此结束。

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

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

相关文章

【计算机网络】计算机网络复习总结 ----- 链路层

计算机网络 内容管理数据链路层 Data Link Layer相关概念术语link 链路data link 数据链路帧 frameMTU maximum transfer Unit 最大传送单元PPP point to point Protcol 点对点协议MAC 地址 media access control网桥 bridge交换机 switch成帧 规则成帧定界字符计数法字符填充分…

QT:文件介绍

UI&#xff1a;QT程序中使用的ui界面就保存在该文件中 组件 设置当前组件的属性 点谁显示谁的属性 调整最终页面的布局&#xff0c;使组件控件在水平或对齐 调整因页面放大缩小时&#xff0c;控件与控件之间的水平和垂直间距 各种各样的按钮 图像显示有关的视图 列表框 各…

Gradle学习笔记之Groovy简单使用

简介 groovy可以当成java的脚本化改良版&#xff0c;同样运行于JVM之上&#xff0c;可以很好地和java代码及相关库进行交互&#xff0c;既可以面向对象编程&#xff0c;也可以用作纯粹的脚本语言。Groovy支持动态类型转换、闭包、元编程、函数式编程、默认作用域为public&…

ArcGIS基础:使用线段分割面数据操作

本操作主要是实现线要素对面要素的分割。 先看一下原始数据&#xff0c;如下所示&#xff0c;2个面&#xff0c;1条线&#xff0c;面被线贯穿过去。 首先进行【面转线】操作&#xff0c;以提取面要素的边界线&#xff0c;如下所示 提取结果如下所示&#xff1a; 然后进行【…

java认证与证书

本文内容主要来自《Java加密与解密的艺术》 目前主要有JKS和PEM两种编码格式文件。 JKS&#xff08;Java Key Store&#xff09;&#xff0c;Java原生的密钥库/信任库文件。**PEM&#xff08;Privacy Enbanced Mail&#xff0c;隐私增强邮件&#xff09;**是使用多种加密方法提…

2022-12-04一周学习

这周基本上还是在对前端学习的更多一点&#xff0c;主要是之前没有重视vue3的学习,现在在补上来&#xff0c;学习了vue3的一些知识&#xff0c;前端的权限管理&#xff0c;设置路由守卫&#xff0c;pinia&#xff0c;还学习了redis的一些基本操作&#xff0c;之前只是照搬了别人…

Linux的线程创建

对于任何一个进程来讲&#xff0c;即便我们没有主动去创建线程&#xff0c;进程也是默认有一个主线程的。线程是负责执行二进制指令的&#xff0c;它会根据项目执行计划书&#xff0c;一行一行执行下去。进程要比线程管的宽多了&#xff0c;除了执行指令之外&#xff0c;内存、…

二、演练领域驱动的设计过程

一、业务分析&#xff1a;统一语言与事件风暴 1、统一语言&#xff1a; 客户明白自己的领域知识也就是业务&#xff0c;以及自己需要解决的问题&#xff0c;也叫做痛点&#xff0c;但是不知道技术。技术人员知道技术&#xff0c;但是不了解客户的业务。所以两者交流起来往往会…

[附源码]计算机毕业设计物品捎带系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

finereport公式帮助

1 if(inarray($$$,ds1.select(销售员))<$TOPN,$$$,"其他")&#xff0c;将第 N 个销售员之后的所有销售员合并为其他&#xff0c; 2 "["((roundup($$$/$num)-1)*$num1)"~"(roundup($$$/$num)*$num)"]" 3 SQL语句用if语句&#xff0c…

HTML网页设计作业:文化网站设计——基于HTML古典中国风工艺美术网页设计(9页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

SpringBoot @InitBinder注解绑定请求参数

参考资料 springMVC之InitBinder 和 ValidatorspringMVC之InitBinder的用法1springMVC之InitBinder的用法2 目录一. 作用二. 前期准备三. Get请求 URL传值处理3.1 前台-test16.html3.2 Controller层3.3 效果四. Post请求 表单传值 自定义日期属性绑定器4.1 前台-test16.htm…

华为机试 - 任务最优调度

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 给定一个正整数数组表示待系统执行的任务列表&#xff0c;数组的每一个元素代表一个任务&#xff0c;元素的值表示该任务的类型。 请计算执行完所有任务所需的最短时间。 任务执行规则如下: 任务可…

Springboot RabbitMq源码解析之RabbitListener注解 (四)

文章目录1.RabbitListener注解介绍2.EnableRabbit和RabbitBootstrapConfiguration3.RabbitListenerAnnotationBeanPostProcessor4.对RabbitListener注解的解析5.RabbitListenerEndpointRegistrar1.RabbitListener注解介绍 RabbitListener是Springboot RabbitMq中经常用到的一个…

D-023 DVI硬件电路设计

DVI硬件电路设计1 简介1.1 连接器1.2 接口信号定义1.3 DVI的分类1.4 DVI规格2 硬件设计实战3 硬件设计要点3.1 注意事项3.2 补充说明3.3 VGA 和 DVI 优缺点1 简介 DVI(Digital Visual Interface)是一种数字视频接口&#xff0c;它是基于TMDS (Transition Minimized Differenti…

MFC列表控件的用法(基于对话框的编程)

目录 一、List Control列表控件属性 1.List Control 2.View属性 二、OnInitDialog初始化列表 1.创建List Control的变量 2.找OnInitDialog ​3. InsertColumn插入表头 4. InsertColumn设置对齐方式和列宽 5. 设置List的正文内容 ​6.循环结构创建列表 7.设置列表整行…

Windows内核--子系统(3.5)

到底什么是子系统? 子系统是用户层概念。在Windows内核之上&#xff0c;如果想要执行类UNIX应用程序&#xff0c;就是POSIX子系统&#xff0c;如果要类似OS/2环境&#xff0c;就是OS/2子系统。 如何能模拟出不同子系统呢? 一般需要子系统用户态应用程序和相关DLL支援。 对于W…

腾讯云服务器mysql安装

1.选择mysql版本 2.安装mysql源 sudo wget https://repo.mysql.com//mysql80-community-release-el7-1.noarch.rpm 3.下载mysql.rpm源 wget https://repo.mysql.com//mysql80-community-release-el7-1.noarch.rpm 4.安装下载好的rpm包 sudo rpm -ivh mysql80-community-rele…

PCB入门介绍与电阻电容电感类元件的创建

摘自凡亿教育 目录 一、PCB入门介绍 二、电阻电容电感类元件的创建 1.绘制电阻的原理图库 2.绘制电容的原理图库 3.绘制电感的原理图 一、PCB入门介绍 1.EDA工具 Cadence Allegro :IC-芯片设计 Mentor PADS:做消费类电子产品、手机、机顶盒、平板电脑 Altium Designer…

多线程初阶(二)

目录 前言&#xff1a; synchronized 解析 可重入和不可重入问题 解析 Java中线程安全类 死锁问题 解析 解决死锁问题 解析 内存可见性 解析 volatile关键字 解析 wait&#xff0c;notify 解析 小结&#xff1a; 前言&#xff1a; 针对上篇文章讲到的线程安全…