实现Spring底层机制(二)

news2025/1/13 3:38:27

文章目录

  • 阶段2—封装bean定义信息到Map
    • 1.代码框架图
    • 2.代码实现
        • 1.文件目录
        • 2.新增注解Scope存储单例或多例信息Scope.java
        • 3.修改MonsterService.java指定多例注解
        • 4.新增bean定义对象存储bean定义信息BeanDefinition.java
        • 5.修改pom.xml增加依赖
        • 6.修改容器实现bean定义信息扫描SunSpringApplicationContext.java
        • 7.结果展示
    • 3.该阶段完成的任务
  • 阶段3—初始化bean单例池&实现依赖注入
    • 1.初始化bean单例池
        • 1.代码框架图
        • 2.代码实现
          • 1.修改SunSpringApplicationContext.java增加两个方法
          • 2.修改SunSpringApplicationContext.java在构造方法初始化单例池
          • 3.启动类AppMain.java
          • 4.结果
        • 3.该阶段完成的任务
    • 2.实现依赖注入
        • 1.代码实现
          • 1.增加注解Autowired用来表示自动装配
          • 2.修改MonsterDao.java
          • 3.修改MonsterService.java
          • 4.修改SunSpringApplicationContext.java的createBean,添加依赖注入逻辑
          • 5.启动类AppMain.java
          • 6.结果
    • 3.总结IOC
        • 1.单例bean的创建和依赖注入
        • 2.多例bean的特殊处理

阶段2—封装bean定义信息到Map

1.代码框架图

image-20240221183904450

2.代码实现

1.文件目录

image-20240221195548820

2.新增注解Scope存储单例或多例信息Scope.java
package com.sun.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 孙显圣
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    String value() default ""; //通过value来决定是singleton,prototype
}

3.修改MonsterService.java指定多例注解
package com.sun.spring.component;

import com.sun.spring.annotation.Component;
import com.sun.spring.annotation.Scope;

/**
 * @author 孙显圣
 * @version 1.0
 */

@Scope(value = "prototype") //指定是多例的
@Component(value = "monsterService")//自定义注解,自动反射创建bean对象,如果指定了value则id为value否则为首字母小写
public class MonsterService {
}

4.新增bean定义对象存储bean定义信息BeanDefinition.java
package com.sun.spring.ioc;

/**
 * 记录bean定义的信息{1.scope 表示单例还是多例 2.clazz 表示bean的Class对象,方便多例的时候创建实例}
 *
 * @author 孙显圣
 * @version 1.0
 */
public class BeanDefinition {
    private String scope; //单例或多例
    private Class clazz; //Class对象

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "scope='" + scope + '\'' +
                ", clazz=" + clazz +
                '}';
    }
}

5.修改pom.xml增加依赖
    <!--工具类,使类名首字母小写-->
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
    </dependency>
6.修改容器实现bean定义信息扫描SunSpringApplicationContext.java
package com.sun.spring.ioc;

import com.sun.spring.annotation.Component;
import com.sun.spring.annotation.ComponentScan;
import com.sun.spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;


import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 类似于Spring原生的ioc容器
 *
 * @author 孙显圣
 * @version 1.0
 */
public class SunSpringApplicationContext {
    //传进来一个配置类的Class对象
    private Class configClass;
    //bean定义字段
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    //bean对象字段
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

    //构造器,接收配置类的class对象
    public SunSpringApplicationContext(Class configClass) throws ClassNotFoundException {
        //bean定义扫描
        this.beanDefinitionScan(configClass);

    }
    public void beanDefinitionScan(Class configClass) throws ClassNotFoundException {
        this.configClass = configClass;
        //一、获取要扫描的包
        //1.首先反射获取类的注解信息
        ComponentScan componentScan = (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2.通过注解来获取要扫描的包的路径
        String path = componentScan.value();
        System.out.println("要扫描的包=" + path);

        //二、得到要扫描包的.class文件对象,从而得到全路径进行反射
        //1.获取类加载器
        ClassLoader classLoader = SunSpringApplicationContext.class.getClassLoader();
        //2.获取要扫描包的真实路径,默认刚开始在根目录下
        path = path.replace(".", "/");
        URL resource = classLoader.getResource(path);
        //3.由该路径创建一个文件对象,可使用resource.getFile()将URL类型转化为String类型
        File file = new File(resource.getFile());
        //4.遍历该文件夹下的所有.class文件对象
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                //反射注入容器
                //1.获取所有文件的绝对路径
                String absolutePath = f.getAbsolutePath();
                //只处理class文件
                if (absolutePath.endsWith(".class")) {
                    //2.分割出类名
                    String className = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1, absolutePath.indexOf("."));
                    //3.得到全路径
                    String fullPath = path.replace("/", ".") + "." + className;
                    //4.判断是否需要注入容器,查看有没有自定义的注解Component
                    Class<?> aClass = classLoader.loadClass(fullPath);

                    //如果该类使用了注解Component则说明是一个spring bean
                    if (aClass.isAnnotationPresent(Component.class)) {
                        System.out.println("这是一个Spring bean=" + aClass);
                        //将Bean的信息封装到BeanDefinition对象中并放入到beanDefinitionMap
                        BeanDefinition beanDefinition = new BeanDefinition();
                        //获取scope注解信息
                        Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
                        //只要scopeAnnotation是空的或者他不是空的但是值是空串,则返回singleon,否则就返回value
                        String scope = scopeAnnotation == null || scopeAnnotation.value().equals("") ?
                                "singleton" : scopeAnnotation.value();

                        //封装信息到bean定义字段中
                        beanDefinition.setScope(scope);
                        beanDefinition.setClazz(aClass);

                        //获取Component注解的value值作为beanName
                        Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                        //只要component注解的值是空串就返回类名首字母小写,否则返回这个注解的值
                        String beanName = componentAnnotation.value().equals("") ?
                                StringUtils.uncapitalize(className) : componentAnnotation.value();

                        //封装到beanDefinitionMap中
                        beanDefinitionMap.put(beanName, beanDefinition);


                    } else {
                        System.out.println("这不是一个Spring bean=" + aClass);
                    }

                }
            }

            //遍历输出beanDefinitionMap
            System.out.println("遍历输出beanDefinitionMap");
            for (Map.Entry<String, BeanDefinition> beanDefinitionMap : beanDefinitionMap.entrySet()) {
                System.out.println(beanDefinitionMap.getKey() + " " + beanDefinitionMap.getValue());
            }
        }

    }

    //返回容器中的对象
    public Object getBean(String name) {
        return null;
    }

}

7.结果展示

image-20240221200049728

3.该阶段完成的任务

  • 新增注解Scope来存储单例/多例信息
  • 新增bean定义对象,存储bean定义信息Scope和Class对象
  • 扫描所有组件的信息,将bean定义信息以beanName - bean定义对象的形式存储到Map中

阶段3—初始化bean单例池&实现依赖注入

1.初始化bean单例池

1.代码框架图

image-20240221201306781

2.代码实现
1.修改SunSpringApplicationContext.java增加两个方法
    //根据bean定义对象来创建bean对象
    private Object createBean(BeanDefinition beanDefinition){
        //获取Class对象,反射创建实例
        Class clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();
            //如果成功则返回这个对象
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

    }

    //返回容器中的对象
    public Object getBean(String name) {
        //如果是单例的,则从单例池中获取,如果是多例的则直接创建一个实例返回,名字没在bean定义中则抛出异常
        if (beanDefinitionMap.containsKey(name)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //判断是否是单例
            if ("singleton".equals(beanDefinition.getScope())) {
                //从单例池中获取对象
                return singletonObjects.get(name);
            } else {
                //直接创建对象
                return createBean(beanDefinition);
            }
        } else {
            //name不在bean定义中则抛出异常
            throw new NullPointerException("没有该bean");
        }
    }
2.修改SunSpringApplicationContext.java在构造方法初始化单例池
    //构造器,接收配置类的class对象
    public SunSpringApplicationContext(Class configClass) throws ClassNotFoundException {
        //bean定义扫描,并将信息封装到beanDefinitionMap中
        this.beanDefinitionScan(configClass);

        //初始化单例池
        //1.获取所有bean的名字
        Enumeration<String> keys = beanDefinitionMap.keys();
        //2.遍历名字
        while (keys.hasMoreElements()) {
            String beanName = keys.nextElement();
            //3.是单例类型的就创建bean对象并放到单例池中
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(beanDefinition);
                //4.放到单例池
                singletonObjects.put(beanName, bean);
            }
        }
        System.out.println("singletonObjects: " + singletonObjects);

    }
3.启动类AppMain.java
package com.sun.spring;

import com.sun.spring.ioc.SunSpringApplicationContext;
import com.sun.spring.ioc.SunSpringConfig;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class AppMain {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        SunSpringApplicationContext ioc = new SunSpringApplicationContext(SunSpringConfig.class);
        Object bean01 = ioc.getBean("monsterDao111"); //单例对象
        Object bean02 = ioc.getBean("monsterDao111"); //单例对象
        System.out.println("单例对象1:" + bean01);
        System.out.println("单例对象2:" +bean02);
        //多例对象
        Object bean1 = ioc.getBean("monsterService");
        Object bean2 = ioc.getBean("monsterService");
        System.out.println("多例对象1:" + bean1);
        System.out.println("多例对象1:" + bean2);

    }
}

4.结果

image-20240221212306705

3.该阶段完成的任务
  • 新增createBean的方法,根据bean定义对象创建bean对象
  • 新增初始化单例池,在封装完bean定义信息之后,扫描bean定义信息,如果是单例对象则创建bean对象放到单例池中
  • 新增getBean方法,如果是单例的对象,则从单例池中查找,否则直接创建对象

2.实现依赖注入

1.代码实现
1.增加注解Autowired用来表示自动装配
package com.sun.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 孙显圣
 * @version 1.0
 */
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {

}

2.修改MonsterDao.java
package com.sun.spring.component;

import com.sun.spring.annotation.Component;

/**
 * @author 孙显圣
 * @version 1.0
 */
//scope默认是单例的
@Component(value = "monsterDao")
public class MonsterDao {
    public void hi() {
        System.out.println("MonsterDao say hi!");
    }

}

3.修改MonsterService.java
package com.sun.spring.component;

import com.sun.spring.annotation.Autowired;
import com.sun.spring.annotation.Component;
import com.sun.spring.annotation.Scope;

/**
 * @author 孙显圣
 * @version 1.0
 */

@Scope(value = "prototype") //指定是多例的
@Component(value = "monsterService")//自定义注解,自动反射创建bean对象,如果指定了value则id为value否则为首字母小写
public class MonsterService {
    //自定义注解,按照名字进行依赖注入
    @Autowired
    private MonsterDao monsterDao;

    public void m1() {
        monsterDao.hi();
    }
}

4.修改SunSpringApplicationContext.java的createBean,添加依赖注入逻辑
    //根据bean定义对象来创建bean对象
    private Object createBean(BeanDefinition beanDefinition){
        //获取Class对象,反射创建实例
        Class clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();
            //进行依赖注入
            //1.遍历所有字段
            for (Field field : clazz.getDeclaredFields()) {
                //2.判断字段中是否有autowired注解
                if (field.isAnnotationPresent(Autowired.class)) {
                    //3.依赖注入
                    String name = field.getName();
                    Object bean = getBean(name);
                    //反射爆破
                    field.setAccessible(true);
                    field.set(instance, bean);
                }
            }

            //如果成功则返回这个对象
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

    }
5.启动类AppMain.java
package com.sxs.spring;

import com.sxs.spring.aop.SmartAnimalable;
import com.sxs.spring.component.UserAction;
import com.sxs.spring.component.UserDao;
import com.sxs.spring.component.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 孙显圣
 * @version 1.0
 */
public class AppMain {
    public static void main(String[] args) {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        UserAction action1 = ioc.getBean(UserAction.class);
        UserAction action2 = ioc.getBean(UserAction.class);
        System.out.println("action1=" + action1);
        System.out.println("action2=" + action2);
        UserService service = ioc.getBean(UserService.class);
        System.out.println("service=" + service);
        service.m1();
        UserDao userDao = ioc.getBean(UserDao.class);
        System.out.println("userDao=" + userDao);

        //aop测试
        SmartAnimalable bean = ioc.getBean(SmartAnimalable.class);
        bean.getSum(1,2);
        bean.getSub(2,1);
    }

}

6.结果

image-20240222103029837

3.总结IOC

1.单例bean的创建和依赖注入
  • 获取Spring容器对象,读取配置文件,得到要扫描的包
  • 扫描指定的包,将bean定义信息放到map中
  • 初始化单例池,如果是单例的则直接反射创建bean对象并放到单例池中
  • 依赖注入,扫描单例池中实例的所有字段,如果有自动装配注解则进行依赖注入
  • 初始化bean对象
  • getBean的时候从单例池中获取bean对象
  • 销毁bean
2.多例bean的特殊处理
  • getBean的时候反射创建bean对象,依赖注入,初始化bean,然后再得到bean对象
  • 销毁bean

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

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

相关文章

修改element-ui中el-calendar(日历)的样式

效果图如下&#xff1a; <template><div class"dashboard-container"><el-card style"width: 350px; height: auto; border-radius: 8px"><div class"custom-style"><p class"new-data">{{ newDate }}&…

Qt使用miniblink第三方浏览器模块

文章目录 一、前言二、miniblink简介三、miniblink使用四、运行效果五、工程结构 一、前言 本文取自刘典武大师&#xff1a;Qt编写地图综合应用58-兼容多浏览器内核 用Qt做项目过程中&#xff0c;遇到需要用到浏览器控件的项目&#xff0c;可能都会绕不开一个问题&#xff0c;那…

12-Makefile_03(续)

使用变量 使用C自动编译成*.o的默认规则有个缺陷&#xff0c;由于没有显式地表示*.o依赖于.h头文件&#xff0c;假如修改了头文件的内容&#xff0c;那么*.o并不会更新&#xff0c;这是不可接受的。并且默认规则使用固定的“cc”进行编译&#xff0c;假如想使用ARM-GCC进行交叉…

Nacos原理简单介绍

注册中心原理 官网&#xff1a;Nacos 注册中心的设计原理 | Nacos nacos注册中心采用了 &#xff1a;pull &#xff08;客户端的轮询&#xff09;和push &#xff08;服务端主动push&#xff09;策略 客户端启动时会将当前服务的信息包含ip、端口号、服务名、集群名等信息封装…

树莓教育坚持特色引领,建设一流应用型影像培训

树莓教育&#xff0c;作为树莓集团旗下的子公司&#xff0c;自创立以来已经走过了十余个春秋。在这漫长的教育征程中&#xff0c;树莓教育始终坚守初心&#xff0c;秉持着七个坚持和十项行动的原则为数字影像产业的建设和发展做出了巨大的贡献。 七个坚持&#xff0c;是树莓教育…

【网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序(万字博文)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 目录 …

大小端解释以及如何使用程序判断IDE的存储模式

今天让我们来了解一下大小端的概念吧 什么是大小端&#xff1f; 大端&#xff08;存储&#xff09;模式&#xff1a;指的是数据的低位保存在内存的高地址处&#xff0c;而数据的高位则保存在内存的低地址处。 小端&#xff08;存储&#xff09;模式&#xff1a;指的是数据的低位…

使用表格法插入公式和编号

如何将公式和编号优雅地插入到论文当中呢&#xff1f; 首先插入一个1行2列的表格 调整一下 输入公式方法一&#xff1a;感觉墨迹公式挺好用的&#xff0c;word自带的 输入公式方法二&#xff1a;图片转LATEX代码 这个方法更快 分享一个公式识别网站 图片识别得到LATEX代码&…

免费PNG素材网站推荐:设计效率倍增!

一、即时设计 新一代协同设计工具即时设计&#xff0c;内置丰富社区资源&#xff0c;可以在此获得设计前线的各类PNG图像&#xff0c;以及矢量图标&#xff0c;包括毛玻璃、3D混搭、全息投影、单色、平面化等&#xff0c;都是符合目前市场的主流风格。通过最近更新、作品、资源…

【ks爬虫软件】把快手评论API接口封装成GUI采集工具

用Python开发爬虫采集软件&#xff0c;可自动抓取快手评论数据&#xff0c;且包含二级评论。 快手的评论接口URL&#xff1a; # 请求地址 url https://www.kuaishou.com/graphql开发者模式分析过程&#xff1a; 进而封装成GUI界面软件&#xff0c;如下&#xff1a; 软件效…

【Linux】开关机命令和服务管理类命令

一般Linux是不会经常进行关机的,关机的正确流程是: sync->shutdown->reboot->poweroff sync: 将内存中的数据同步到硬盘中poweroff: 关闭系统,等同于shutdown -h nowreboot: 重启系统,等同于 shutdown -r nowshutdown[选项] [时间] shutdown命令常见用法: shutdown:…

[vite] ts写配置根目录别名

参考:配置 Vite | Vite 写对象的形式吧 import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path// https://vitejs.dev/config/ export default defineConfig({plugins: [vue()],resolve: {alias: {"": path.resolve(__dirname…

电磁仿真--基本操作-CST-(2)

目录 1. 回顾基操 2. 操作流程 2.1 创建工程 2.2 修改单位 2.3 创建 Shape 2.4 使用拉伸 Extrude 2.5 修改形状 Modify Locally 2.6 导入材料 2.7 材料解释 2.8 材料分配 2.9 查看已分配的材料 2.10 设置频率、背景和边界 2.11 选择 Edge&#xff0c;设置端口 2.…

Linux-进程间通信:System V消息队列

目录 System V IPC概述标识符与IPC Key System V消息队列创建或打开一个消息队列发送消息接收消息控制消息队列1、IPC_STAT2、IPC_SET3、IPC_RMID 查看系统当前的消息队列代码示例 System V IPC&#xff08;Inter-Process Communication&#xff09;是一组用于在 Unix-like 操作…

五款最受欢迎的上网行为管理软件

五款最受欢迎的上网行为管理软件 员工上网看视频怎么办&#xff1f;员工偷偷刷抖音怎么办&#xff1f;员工天天上招聘网&#xff0c;是不是有离职打算&#xff1f; 解决上述困扰的最好办法是使用监控软件了解员工一言一行。以下是几款推荐的上网行为管理和监控软件&#xff1a;…

基于Linux系统命令行安装KingbaseES数据库

人大金仓通用性数据库&#xff08;Kingbase&#xff09;下载网址&#xff1a;人大金仓-成为世界卓越的数据库产品与服务提供商 选择“软件版本-数据库”&#xff0c;筛选条件Linux、完整版。找到需要的版本&#xff0c;点击下载。我下载的是KingbaseES_V008R006C008B0014_Lin6…

机器学习理论基础—神经网络算法公式学习

机器学习理论基础—神经网络公式学习 M-P神经元 M-P神经元&#xff08;一个用来模拟生物行为的数学模型&#xff09;&#xff1a;接收n个输入(通常是来自其他神经 元)&#xff0c;并给各个输入赋予权重计算加权和&#xff0c;然后和自身特有的阈值进行比较 (作减法&#xff0…

【大语言模型基础】Transformer模型Torch代码详解和训练实战

一、Transformer概述 Transformer是由谷歌在17年提出并应用于神经机器翻译的seq2seq模型&#xff0c;其结构完全通过自注意力机制完成对源语言序列和目标语言序列的全局依赖建模。 Transformer由编码器和解码器构成。下图展示了它的结构&#xff0c;其左侧和右侧分别对应着编…

奇妙的探索——偶然发现的bug

今天想在腾讯招聘官网找几个前端的岗位投一下&#xff0c;最近自己也在找工作&#xff0c;结果简历还没有投出去&#xff0c;就发现了腾旭招聘官网的3个前端bug。 1.有时候鼠标hover还没有滑倒下拉选框的菜单上&#xff0c;就消失了&#xff0c;消失的太快了&#xff0c;根本点…

方便快捷!使用Roboflow进行数据增强(附详细操作)

最近使用自定义数据集训练yolov8模型的时候突然发现一件很令人头疼的事情。那就是&#xff0c;数据集中图片太少了。于是想通过数据增强的方法扩大数据集。 通过查阅资料发现&#xff0c;大部分人都是用python中的imgaug库进行图像处理&#xff1b;这种方法最大的不便就是需要转…