简单实现Spring容器(四) 依赖注入

news2024/12/17 6:53:36

阶段4:

// 1.编写自己的Spring容器,实现扫描包,得到bean的class对象.
// 2.扫描将 bean 信息封装到 BeanDefinition对象,并放入到Map.
 //3.初始化单例池并完成getBean() createBean()方法
 4.完成依赖注入(如果创建某个Bean对象,存在依赖注入,需要进行bean组装操作)

思路:

1.在annotation包下创建新的注解@autowired
2.在component包下的MonsterService类下增加一个属性monsterDao,引用MonsterDao类
3.关键:creatBean方法里,当反射得到实例,这个实例加入依赖注入的业务逻辑.(因为这里已经拿到了Component组件的class对象cla,通过这个cla对象,是可以拿到类的字段filed.拿到这个字段再去判断你的这个字段有没有autowired的注解修饰,然后再进行下一步)

代码实现

注解@autowired

package com.elf.spring.annotation;

import java.lang.annotation.*;

/**
 * @author 45~
 * @version 1.0
 */
@Target({ ElementType.METHOD,  ElementType.FIELD}) //实现在方法上和字段上即可
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
   //required()属性是true,表示,如果字段用@Autowired修饰,那么一定要找到匹配的,找不到会报错
    // boolean required() default true;
    //这里简单做的,只要用@Autowired修饰了,那么就完成依赖属性,这里required属性没有也无所谓,
    //这里默认为true
}

2.在MonsterDao 里加一个方法,验证有没有注入成功

package com.elf.spring.component;
import com.elf.spring.annotation.Component;
/**
 * @author 45~
 * @version 1.0
 * 写第二个类是因为要有两个类才能做依赖注入
 */
@Component(value = "monsterDao")
public class MonsterDao {
    public void hi(){
        System.out.println("MonsterDao~hi()");
    }
}

3.MonsterService.java

package com.elf.spring.component;
import com.elf.spring.annotation.Autowired;
import com.elf.spring.annotation.Component;
import com.elf.spring.annotation.Scope;

/**
 * @author 45~
 * @version 1.0
 * 说明 MonsterService 是一个Servic
 */
@Component //把MonsterService注入到我们自己的spring容器中
@Scope(value = "prototype")
public class MonsterService {
    //这里@Autowired匹配的形式很多,不用每个都去实现,这里先按名字来装配
    //这里使用的是自己定义的@Autowired来修饰属性,表示该属性,是通过容器完成依赖注入的
    //说明:我们实现按照名字来进行组装
    @Autowired
    private MonsterDao monsterDao;
    public void m1(){
        monsterDao.hi();
    }
}

4.容器文件
在这里插入图片描述

java.lang.IllegalAccessException: Class
com.elf.spring.ioc.ElfSpringApplicationContext can not access a member
of class com.elf.spring.component.MonsterService with modifiers
“private”

Exception in thread “main” java.lang.NullPointerException at
com.elf.spring.AppMain.main(AppMain.java:22)
报空指针,非法访问异常.反射里如果属性是私有的,必须进行爆破.

package com.elf.spring.ioc;

import com.elf.spring.annotation.*;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author 45~
 * @version 1.0
 */
public class ElfSpringApplicationContext {
    //第一步,扫描包,得到bean的class对象,排除包下不是bean的,因此还没有放到容器中
    //因为现在写的spring容器比原先的基于注解的,要更加完善,所以不会直接把它放在ConcurrentHashMap
    private Class configClass;

    //定义属性BeanDefinitionMap -> 存放BeanDefinition对象
    private  ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap<>();
    //定义属性SingletonObjects -> 存放单例对象 (跟原生容器的名字保持一致)
    //因为将来存放单例池的时候肯定要指定单例对象是对应哪个Bean的,所以k用String来充当
    //存放单例对象的类型是不确定的,可能是Dog,Cat,或者其他的对象,所以用Object
    private  ConcurrentHashMap<String, Object> singletonObjects =
            new ConcurrentHashMap<>();

    //构造器
    public ElfSpringApplicationContext(Class configClass) {

        //完成扫描指定包
        beanDefinitionsByScan(configClass);

        //通过beanDefinitionMap,初始化singletonObjects单例池
        //封装成方法
        //遍历所有的beanDefinition,用到集合和枚举的知识
        Enumeration<String> keys = beanDefinitionMap.keys();//把所有bean的名字拿到
        while (keys.hasMoreElements()) {
            //得到beanName
            String beanName = keys.nextElement();
            //通过beanName得到对应的beanDefinition对象
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判断该bean是singleton还是prototype
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //将该bean实例放入到singletonObjects集合
                Object bean = createBean(beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
//        System.out.println("singletonObjects 单例池=" + singletonObjects);
//        System.out.println("beanDefinitionMap=" + beanDefinitionMap);

    }//构造器结束

    //该方法完成对指定包的扫描,并将Bean信息封装到BeanDefinition对象,在放入到Map
    public void beanDefinitionsByScan(Class configClass) {
        this.configClass = configClass;

        /**获取要扫描的包:
         1.先得到ElfSpringConfig配置的 @ComponentScan(value= "com.elf.spring.component")
         2.通过 @ComponentScan的value => 即要扫描的包 **/
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();
//        System.out.println("要扫描的包path=" + path);

        /**
         * 得到要扫描包下的所有资源(类.class)
         * 1.得到类的加载器 -> APP类加载器是可以拿到 target目录下的classes所有文件的
         * 2.通过类的加载器获取到要扫描的包的资源url => 类似一个路径
         * 3.将要加载的资源(.class)路径下的文件进行遍历 => io
         */
        ClassLoader classLoader = ElfSpringApplicationContext.class.getClassLoader();
        path = path.replace(".", "/"); // 把.替换成 /
        URL resource = classLoader.getResource(path);
//        System.out.println("resource=" + resource);

        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) { //把所有的文件都取出来
//                System.out.println("============================");
//                System.out.println("f.getAbsolutePath()=" + f.getAbsolutePath());
                String fileAbsolutePath = f.getAbsolutePath();//到target的classes目录下了

                //这里只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {
                    //1.获取类名
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1,
                            fileAbsolutePath.indexOf(".class"));
                    //2.获取类的完整路径(全类名)
                    String classFullName = path.replace("/", ".") + "." + className;
                    //System.out.println("classFullName=" + classFullName);
                    //3.判断该类是否需要注入,就看是不是有注解@Component @Service @Contoller @Re....
                    try {
                        Class<?> cla = classLoader.loadClass(classFullName);
                        if (cla.isAnnotationPresent(Component.class) ||
                                cla.isAnnotationPresent(Controller.class) ||
                                cla.isAnnotationPresent(Service.class) ||
                                cla.isAnnotationPresent(Repository.class)) {
                            //演示机制
                            //如果该类使用了@Component注解,说明是一个Spring bean
                            System.out.println("这是一个Spring bean=" + cla + " 类名=" + className);

                            //先得到beanName(有可能通过经典4注解,例如Component注解的value值来指定)
                            //1.得到类上的Component注解,此时的clazz已经是当前bean的class对象,通过类加载器得到的 反射知识
                            Component componentAnnotation = cla.getDeclaredAnnotation(Component.class);
                            //2.得到配置的value
                            String beanName = componentAnnotation.value();
                            if ("".equals(beanName)) {//如果没有写value,空串
                                //将该类的类名首字母小写作为beanName
                                //StringUtils其实是在springframework的框架下面的类,而这里本身我就是要自己写所以不用

                                beanName = StringUtils.uncapitalize(className);
                            }
                            //3.将Bean的信息封装到BeanDefinition对象中,然后将其放入到BeanDefinitionMap集合中
                            BeanDefinition beanDefinition = new BeanDefinition();
                            //!!!多看看这里多理解
                            beanDefinition.setClazz(cla);//由类加载器通过反射得到对象,Class<?> cla = classLoader.loadClass(classFullName);
                            //4.获取Scope值
                            if (cla.isAnnotationPresent(Scope.class)) {
                                //如果配置了Scope,就获取它配置的值
                                Scope scopeAnnotation = cla.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());
                            } else {
                                //如果没有配置Scope,就以默认的值singleton
                                beanDefinition.setScope("singleton");
                            }
                            //将beanDefinitionMap对象放入Map
                            beanDefinitionMap.put(beanName, beanDefinition);
                        } else {
                            //如果该类没有使用了@Component注解,说明是一个Spring bean
                            System.out.println("这不是一个Spring bean" + cla + " 类名=" + className);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
//                System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            }//遍历文件结束
        }
    }

    //完成createBean(BeanDefinition beanDefinition)方法
    private  Object createBean(BeanDefinition beanDefinition) {
        //得到Bean的Clazz对象
        Class clazz = beanDefinition.getClazz();
        try {
            //使用反射得到实例
            Object instance = clazz.getDeclaredConstructor().newInstance();

            //这里加入依赖注入的业务逻辑!!!
            //1.遍历当前要创建的对象的所有字段( 比如:要创建MonsterService,需要把所有的字段都扫描一遍,看看字段上有没有@Autowired的注解)
            for(Field declaredField : clazz.getDeclaredFields()){
               //2.判断这个字段是否有@Autwired的修饰
                if(declaredField.isAnnotationPresent(Autowired.class)){
                    //提示:这里处理@Autowired的required 
//                    Autowired annotation = declaredField.getAnnotation(Autowired.class);
//                    annotation.required()=>进行下一步其他处理
                    
                    //3.得到这个字段的名字
                    String name = declaredField.getName();
                    //4.通过getBean()方法来获取要组装的对象
                    Object bean = getBean(name);
                    //5.进行组装
                    declaredField.setAccessible(true);//因为反射里属性是私有的,须进行暴破.
                    declaredField.set(instance,bean);
                }
            }

            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        //如果反射创建对象失败
        return null;
    }

    //编写getBean(String name)方法,返回容器中的对象.
    // 既然是getBean()那么返回类型是Object,因为不管是单例池还是创建的对象也好,类型是不确定的
    public  Object getBean(String name) {
        //加一个判断,严谨. 传入的beanName是否在beanDefinitonMap中存在..
        if(beanDefinitionMap.containsKey(name)) {//如果存在
            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //得到beanDefinition的scope,分别进行处理
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //说明是单例配置,就直接从单例池获取
                return singletonObjects.get(name);
            } else {//如果不是单例的,我们就调用createBean, 反射一个对象
                return createBean(beanDefinition);
            }
        } else { //如果不存在,就抛出一个空指针异常,也可自定义
            throw new NullPointerException("没有该bean");

        }

    }

}

5.MainApp.java

package com.elf.spring;
import com.elf.spring.component.MonsterDao;
import com.elf.spring.component.MonsterService;
import com.elf.spring.ioc.ElfSpringApplicationContext;
import com.elf.spring.ioc.ElfSpringConfig;

/**
 * @author 45~
 * @version 1.0
 */
public class AppMain {
    public static void main(String[] args) {
        //把容器创建起来,在创建的时候传入了配置类的class类型/class对象
        //传进去后会根据自己写的容器机制 进行解析
        ElfSpringApplicationContext elfSpringApplicationContext =
                new ElfSpringApplicationContext(ElfSpringConfig.class);

        //测试一下依赖注入的功能
        MonsterService monsterService =
                (MonsterService)elfSpringApplicationContext.getBean("monsterService");

        monsterService.m1();
//        MonsterService monsterService =
//                (MonsterService)ElfSpringApplicationContext.getBean("monsterService");
//        MonsterService monsterService2 =
//                (MonsterService)ElfSpringApplicationContext.getBean("monsterService");
//        System.out.println("monsterService" + monsterService);
//        System.out.println("monsterService2" + monsterService2);
//
//        MonsterDao monsterDao =
//                (MonsterDao)ElfSpringApplicationContext.getBean("monsterDao");
//        System.out.println("monsterDao" + monsterDao);
//        MonsterDao monsterDao2 =
//                (MonsterDao)ElfSpringApplicationContext.getBean("monsterDao");
//        System.out.println("monsterDao2" + monsterDao2);
//        System.out.println("ok");
        System.out.println("ok");
    }
}

在这里插入图片描述

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

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

相关文章

面向对象三大特征——封装

目录 1. 封装概述&#xff08;封装与隐藏&#xff09; 2. private关键字 3. Getter & Setter方法 4. 变量访问原则和this关键字 5. 构造方法 5.1 构造方法概述 5.2 构造方法和set方法的比较 6. 静态 6.1 静态概述 6.2 静态效果 6.3 静态变量和非静态变量的区别 …

【小沐学Python】Python实现语音识别(vosk)

文章目录 1、简介1.1 vosk简介1.2 vosk模型1.3 vosk服务 2、安装3、测试3.1 命令行测试3.2 代码测试 结语 1、简介 https://alphacephei.com/vosk/index.zh.html Vosk 是一个语音识别工具包。 1.1 vosk简介 支持二十种语言 - 中文&#xff0c;英语&#xff0c;印度英语&#…

unity 2d 入门 飞翔小鸟 Cinemachine 镜头跟随小鸟 多边形碰撞器 解决镜头不会穿模问题(十二)

1、安装 window->package manager 2、创建Cinemachine 右键->Cinemachine->2D Carmera 3、创建空对象和多边形控制器如图 记得勾选 is Trigger 空对象位置记得要和小鸟保持一致&#xff0c;不然等下写完脚本后&#xff0c;镜头一开始会移动一下 4、将多边形触…

零一万物模型折腾笔记:官方 Yi-34B 模型基础使用

当争议和流量都消失后&#xff0c;或许现在是个合适的时间点&#xff0c;来抛开情绪、客观的聊聊这个 34B 模型本身&#xff0c;尤其是实践应用相关的一些细节。来近距离看看这个模型在各种实际使用场景中的真实表现和对硬件的性能要求。 或许&#xff0c;这会对也想在本地私有…

python安装与工具PyCharm

摘要&#xff1a; 周末闲来无事学习一下python&#xff01;不是你菜鸡&#xff0c;只不过是对手太强了&#xff01;所以你要不断努力&#xff0c;去追求更高的未来&#xff01;下面先了解python与环境的安装与工具的配置&#xff01; python安装&#xff1a; 官网 进入官网下载…

Child Mind Institute - Detect Sleep States(2023年第一次Kaggle拿到了银牌总结)

感谢 感谢艾兄&#xff08;大佬带队&#xff09;、rich师弟&#xff08;师弟通过这次比赛机械转码成功、耐心学习&#xff09;、张同学&#xff08;也很有耐心的在学习&#xff09;&#xff0c;感谢开源方案&#xff08;开源就是银牌&#xff09;&#xff0c;在此基础上一个月…

【SpringBoot篇】5种类型参数传递json数据传参的操作

&#x1f38a;专栏【SpringBoot】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f33a;普通参数&#x1f33a;POJO参数&#x1f33a;嵌套…

HarmonyOS--ArkTS(0)--目录

官方API文档&#xff1a; HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务 华为开发者官方网站_创新从这里开始

基于ssm农产品仓库管理系统系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本农产品仓库管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信…

基于Python+WaveNet+MFCC+Tensorflow智能方言分类—深度学习算法应用(含全部工程源码)(四)

目录 前言引言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理2. 模型构建3. 模型训练及保存4. 模型生成 系统测试1. 训练准确率2. 测试效果 相关其它博客工程源代码下载其它资料下载 前言 博主前段时间发布了一篇有关方言识别和分类模型训练的博客&#xff…

MFC CBCGPPropertySheet使用说明

CBCGPPropertySheet效果类似新建向导&#xff0c;部分效果如下&#xff1a; 总共需要设置的界面效果有以下几种&#xff0c;具体可以看看效果 PropSheetLook_Tabs, PropSheetLook_OutlookBar, PropSheetLook_Tree, PropSheetLook_OneNoteTabs, PropSheetLook_…

DIP——添加运动模糊与滤波

1.运动模糊 为了模拟图像退化的过程&#xff0c;在这里创建了一个用于模拟运动模糊的点扩散函数&#xff0c;具体模糊的方向取决于输入的motion_angle。如果运动方向接近水平&#xff0c;则模糊效果近似水平&#xff0c;如果运动方向接近垂直&#xff0c;则模糊效果近似垂直。具…

风力发电对讲 IP语音对讲终端IP安防一键呼叫对讲 医院对讲终端SV-6005网络音频终端

风力发电对讲 IP语音对讲终端IP安防一键呼叫对讲 医院对讲终端SV-6005网络音频终端 目 录 1、产品规格 2、接口使用 2.1、侧面接口功能 2.2、背面接口功能 2.3、面板接口功能 3、功能使用 1、产品规格 输入电源&#xff1a; 12V&#xff5e;24V的直流电源 网络接口&am…

【Go实现】实践GoF的23种设计模式:适配器模式

上一篇&#xff1a;【Go实现】实践GoF的23种设计模式&#xff1a;备忘录模式 简单的分布式应用系统&#xff08;示例代码工程&#xff09;&#xff1a;https://github.com/ruanrunxue/Practice-Design-Pattern–Go-Implementation 简介 适配器模式&#xff08;Adapter&#xf…

《形式语言与自动机理论(第4版)》笔记(三)

文章目录 [toc]前导《形式语言与自动机理论&#xff08;第4版&#xff09;》笔记&#xff08;一&#xff09;《形式语言与自动机理论&#xff08;第4版&#xff09;》笔记&#xff08;二&#xff09; 第四章&#xff1a;正则表达式4.1|启示4.2|正则表达式的形式定义正则表达式性…

软件设计师——计算机组成原理(二)

&#x1f4d1;前言 本文主要是【计算机组成原理】——软件设计师——计算机组成原理的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 …

iOS分段控件UISegmentedControl使用

在故事板中添加UISegmentedControl 具体添加步聚如下: 选择Xcode的View菜单下的Show Library (或者Shift+Common+L) 打开控件库如下 在控件库中输入seg搜索控件,在出现Segmented Control后,将其拖到View Controller Scene中 到这里,添加分段控件UI已完成, 接下来将控件与变量…

配置OSS后如何将服务器已有文件上传至OSS,推荐使用ossutil使用

1.下载安装ossutil sudo -v ; curl https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash2.交互式配置生成配置文件 ossutil config 根据提示分别设置配置文件路径、设置工具的语言、Endpoint、AccessKey ID、AccessKey Secret和STSToken参数&#xff0c;STSToken留…

软件接口安全设计规范

《软件项目接口安全设计规范》 1.token授权机制 2.https传输加密 3.接口调用防滥用 4.日志审计里监控 5.开发测试环境隔离&#xff0c;脱敏处理 6.数据库运维监控审计

万界星空科技低代码平台:搭建MES系统的优势

低代码MES系统&#xff1a;制造业数字化转型的捷径 随着制造业的数字化转型&#xff0c;企业对生产管理系统的需求逐渐提高。传统的MES系统实施过程复杂、成本高昂&#xff0c;已经无法满足现代企业的快速发展需求。而低代码搭建MES系统的出现&#xff0c;为企业提供了一种高…