手把手教你写一个简单的ioc容器

news2024/11/26 2:28:48

Ioc

IOC(控制反转) 就是 依赖倒置原则的一种代码设计思路。就是把原先在代码里面需要实现的对象创建、对象之间的依赖,反转给容器来帮忙实现。
Spring IOC容器通过xml,注解等其它方式配置类及类之间的依赖关系,完成了对象的创建和依赖的管理注入。实现IOC的主要设计模式是工厂模式。
在这里插入图片描述

一、主要实现的功能

  • 创建自定义注解@WqxBean,该注解的功能是:被该注解标记的类,会被注册到ioc容器中
  • 创建自定义注解@Di,该注解的功能是:被该注解标记的属性,将会从ioc容器中取出对应的实例化对象,使用该对象将被标记的属性初始化。

二、实现的步骤

  • 1.创建模块 wqx-spring
  • 2.创建两个测试需要用到的类service,dao
  • 3.创建两个注解 @WqxBean @Di
  • 4.创建ioc容器接口
  • 5.实现ioc容器接口

2.1 创建模块

在这里插入图片描述

2.2 创建两个测试需要用到的接口及其实现类service,dao

interface UserDao.class

package wqx.dao;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public interface UserDao {
    public void run();
}

class UserDaoImpl.class

package wqx.dao.impl;

import wqx.anno.WqxBean;
import wqx.dao.UserDao;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
@WqxBean
public class UserDaoImpl implements UserDao {
    @Override
    public void run() {
        System.out.println("userDao-run...");
    }
}

interface UserService.class

package wqx.service;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public interface UserService {
    public void add();
}

class UserServiceImpl.class

package wqx.service.impl;

import wqx.anno.Di;
import wqx.anno.WqxBean;
import wqx.dao.UserDao;
import wqx.dao.impl.UserDaoImpl;
import wqx.service.UserService;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
@WqxBean
public class UserServiceImpl implements UserService {
    @Di
    UserDaoImpl userDaoImpl;

    @Override
    public void add() {
        System.out.println("add...");
        userDaoImpl.run();
    }
}

2.3 创建两个自定义注解

@WqxBean

package wqx.anno;

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

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:该自定义注解用于注册javabean进ioc容器,效果类似于@Component
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface WqxBean {
}

@Di

package wqx.anno;

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

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:该注解用于依赖注入,效果类似于@Resource
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Di {
}

2.4 创建BeanFactory接口的子接口ApplicationContext

2.4.1 IoC容器在Spring的实现

Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:

①BeanFactory

这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

③ApplicationContext的主要实现类

在这里插入图片描述

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

ApplicationContext
接口中创建了一个map集合,这个map集合就是ioc容器,在实现类中如果扫描到目标包下的类有标记@WqxBean注解,则将该类的实例化对象放进ioc容器,key为该类的Class对象,value为该类的实例化对象。

package wqx.BeanFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public interface ApplicationContext  {
    //创建Map集合存放Bean对象(ioc容器
    Map<Class<?>, Object> iocContainer = new HashMap<>();
    public Object getBean(Class<?> clazz);
}

2.5 实现ApplicationContext接口

实现ApplicationContext接口之后,在实现类中,我们需要做三件事

  • 实现getBean()方法,提供通过对象的Class对象获取实例化对象的方法
  • 完成@WqxBean注解功能的实现
  • 完成@Di注解功能的实现

在开发中,我们一般使用以下方式获取容器中的bean(将配置文件传入构造器函数),所以我们也可以为我们创建的AnnotationApplicationContext类创建一个构造器,构造器参数为要被扫描的包,在构造器中我们会扫描该包及其子包,如果发现包中的类上有@WqxBean注解,则将该类添加进ioc容器。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
     /**
      * Singleton:单实例(在容器启动完成之前就已经创建好了,保存在容器中了,任何时候获取都是获取之前创建好的那个对象)
      */
      User user = (User) applicationContext.getBean("UserSingleton");
      User user1 = (User) applicationContext.getBean("UserSingleton");

完整代码:

package wqx.BeanFactory.impl;

import wqx.BeanFactory.ApplicationContext;
import wqx.anno.Di;
import wqx.anno.WqxBean;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public class AnnotationApplicationContext implements ApplicationContext {
    //1.通过Class类型直接获取Bean对象
    @Override
    public Object getBean(Class<?> clazz) {
        return iocContainer.get(clazz);
    }

    //2.设置包扫描规则
    //当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
    //创建有参构造函数,通过构造器传递包路径
    public AnnotationApplicationContext(String basePackage) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        String packagePath = basePackage.replaceAll("\\.", "\\\\");
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);//获取PackagePath的绝对路径
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            //需要将url中被转码的部分解码
            String filePath = URLDecoder.decode(url.getFile(), "utf-8");
            //获取包名前面的物理磁盘路径名
            String rootPath = filePath.substring(0, filePath.length() - basePackage.length());
            //1.扫描传入的文件路径下的所有文件,找到带有@WqxBean注解的类,将该类注册进ioc容器
            loadBean(filePath, rootPath);
        }
        //依赖注入
        loadDi();
    }


    //扫描类上有@WqxBean注解的类,使用反射创建该类的对象并存入map集合
    private void loadBean(String filePath, String rootPath) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        File file = new File(filePath);
        //1.判断是否是文件夹
        if (file.isDirectory()) {
            //1.1 如果是文件夹,则判断是否为空
            File[] files = file.listFiles();
            if (files == null || files.length == 0) {
                //1.1.1 如果为空,则直接退出
                return;
            }
            //1.2 如果不为空,则遍历文件夹
            for (File f : files) {
                if (f.isDirectory()) {
                    //1.3如果遇到文件夹,则递归进入
                    loadBean(f.getAbsolutePath(), rootPath);
                } else {
                    //2.如果是文件,则扫描是否存在注解
                    //2.1 获取文件路径,判断文件是否为.class类型
                    if (f.getAbsolutePath().endsWith(".class")) {
                        //2.2 将文件路径中的\替换为. 并去除.class,得到全类名
                        String packageClassName = f.getAbsolutePath().substring(rootPath.length() - 1, f.getAbsolutePath().length() - ".class".length()).replaceAll("\\\\", "\\.");
                        //2.3 通过反射获取类上的注解,判断是否存在 @WqxBean
                        Class<?> clazz = Class.forName(packageClassName);
                        WqxBean annotation = clazz.getAnnotation(WqxBean.class);
                        //2.4 如果存在@WqxBean注解,则使用反射创建该对象并将其存放进Map集合中
                        if (!clazz.isInterface() && annotation != null) {
                            //存入ioc容器,key为类的class对象
                            Object o = clazz.getConstructor().newInstance();
                            iocContainer.put(clazz,o);
                        }
                    }
                }
            }
        }
    }

    /**
     * 实现注解注入
     */
    private void loadDi() throws IllegalAccessException {
        Set<Map.Entry<Class<?>, Object>> entries = iocContainer.entrySet();
        //1.从容器中取出所有k-v对象
        for (Map.Entry<Class<?>, Object> entry : entries) {
            //2.获取容器中已经实例化好的对象
            Object obj = entry.getValue();
            //3.获取clazz对象的所有属性
            //3.1 获取obj实例化对象的Class对象clazz
            Class<?> clazz = entry.getKey();
            //3.2 通过clazz对象获取obj对象的所有属性
            Field[] declaredFields = clazz.getDeclaredFields();
            //4.遍历所有属性,判断属性上是否有@Di注解
            for (Field declaredField : declaredFields) {
                Di di = declaredField.getAnnotation(Di.class);
                //4.1 如果有Di注解,那么将容器中的对应value值赋给他,value值是一个被实例化的对象
                if (di != null) {
                    declaredField.setAccessible(true);//私有属性被修改前,Accessible需要被设置为true
                    //4.2 从ioc容器中获取declaredField属性对应的实例化对象
                    Object o = iocContainer.get(declaredField.getType());
                    //4.3 将实例化对象赋值给declareField代表的属性
                    //public void set(Object obj, Object value) obj:被修改的字段所属的对象 value:被修改的字段的新值
                    declaredField.set(obj,o);
                }
            }
        }
    }
}

2.6 测试

我们已经在UserDaoImpl类和UserServiceImpl类上添加了***@WqxBean***注解,那么这两个类会被注册进spring容器。

在这里插入图片描述
以及在UserServiceImpl中使用@Di将UserDaoImpl注入进来,可以在UserServiceImpl中使用UserDaoImpl的方法了
在这里插入图片描述
测试类

package wqx.Test;

import wqx.BeanFactory.ApplicationContext;
import wqx.BeanFactory.impl.AnnotationApplicationContext;
import wqx.service.UserService;
import wqx.service.impl.UserServiceImpl;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    	//通过构造方法创建Beanfactory对象,并将要扫描的包的包名传进构造函数
        ApplicationContext applicationContext = new AnnotationApplicationContext("wqx");
        //通过getBean(Class<?> clazz)方法获取目标对象
        UserService userService= (UserServiceImpl) applicationContext.getBean(UserServiceImpl.class);
        System.out.println(userService);//输出该对象
        //调用userService的add方法,add方法中使用USerDaoimpl调用了userDaoImpl的方法
        userService.add();
    }
}

结果:
成功
在这里插入图片描述

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

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

相关文章

wu-ui 多平台快速开发的UI框架

WU-UI 多平台快速开发的UI框架(无论平台&#xff0c;一致体验) 官方群 wu-ui官方1群: 767943089 说明 wu-ui(如虎添翼) 是 全面兼容多端的uniapp生态框架&#xff0c;基于vue2、vue3和nvue开发。丰富组件库&#xff0c;便捷工具库&#xff0c;简单高效。无论平台&#x…

COMSOL Multiphysics软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 COMSOL Multiphysics是一款基于有限元分析&#xff08;FEA&#xff09;的多物理场仿真软件。它提供了一个强大的平台&#xff0c;用于模拟和优化各种物理现象和工程问题。 COMSOL Multiphysics具有广泛的应用领域&#xff0c;包括…

redis持久化、主从和哨兵架构

一、redis持久化 1、RDB快照&#xff08;snapshot&#xff09; redis配置RDB存储模式&#xff0c;修改redis.conf文件如下配置&#xff1a; # 在300s内有100个或者以上的key被修改就会把redis中的数据持久化到dump.rdb文件中 # save 300 100# 配置数据存放目录&#xff08;现…

【前端】场景题:如何在ul标签中插入多个节点 使用文档片段

直接插入的问题&#xff1a;会回流多次。每插入一次li就会回流一次&#xff0c;消耗性能。 这里可以使用文档片段来解决这个问题。 // 创建文档片段 let node document.createDocumentFragment()DocumentFragment节点存在于内存中&#xff0c;并不在DOM中&#xff0c;所以将子…

VR农学虚拟仿真情景实训教学演示

首先&#xff0c;VR农学虚拟仿真情景实训教学提供了更为真实的实践环境。传统的农学实训往往受制于时间、空间和资源的限制&#xff0c;学生只能通过观察或简单的模拟来学习农业知识和技能。而借助虚拟现实技术&#xff0c;学生可以进入虚拟农场&#xff0c;与各种农作物、工具…

直播软件app开发流程全解析

直播软件app开发是当今互联网行业中备受瞩目的领域。随着移动用户的爆发式增长和即时互动的需求日益增加&#xff0c;开发一款高质量的直播应用已经成为各个企业和个人创作者追逐的目标。本文将深入探讨直播软件app开发的全过程&#xff0c;为您揭示开发直播应用的关键步骤&…

uni-app语音转文字功能demo(同声传译)

目录 首先去微信开发者官网申请一下同声传译的插件 微信公众平台 在文件中开始引用&#xff1a; 首先去微信开发者官网申请一下同声传译的插件 微信公众平台 后续使用的时候可以看详情里面的信息进行使用 在文件中开始引用&#xff1a; 注意&#xff01;&#xff01;在这个…

【RuoYi移动端】uni-app中通过vuex的store来实现全局变量的修改和读取

一、在store文件中新建csjVar.js文件 const csjVar {csjMess: [{aaa:"ok"},{bbb:"no"}] } export default csjVar 二、修改store文件中新建index.js文件 import Vue from vue import Vuex from vuex import user from /store/modules/user import gette…

对于array的.toLocaleString()与.flat()区别

最近都使用到这两个方法&#xff0c;对于array。记录下具体区别 先是他们的简介,我这里用的就是string的.toLocaleString() &#xff0c;因为array的就是分别去调用里面的.toLocaleString() 在拼接成 string.toLocaleString() string[].toLocaleString()方法是一个JavaScrip…

解决Maven依赖下载问题:从阿里云公共仓库入手

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Vue 2 条件渲染

条件渲染相关的指令有哪些&#xff1f; v-if、v-else、v-else-ifv-show v-if 的作用 <div v-if"expression"></div>v-if 根据表达式 expression 返回的值是否为 truthy 来决定其内容是否被渲染。 Vue还实现了 v-else 和 v-else-if&#xff1a; <d…

北斗提供关键技术支撑,车辆智能监管将迎来广阔发展前景

随着车辆数量的快速增长和道路交通压力的持续增加&#xff0c;如何保障交通安全和提升出行效率成为了亟待解决的问题。而车辆智能监管作为一种基于现代信息技术的管理方式&#xff0c;具有实时监控、数据分析和智能预警等优势&#xff0c;可以提高交通管理的精细化水平&#xf…

ClickHouse进阶(九):Clickhouse数据查询-3

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &#x1f4cc;订阅…

node socket.io

装包&#xff1a; yarn add socket.io node后台&#xff1a; const express require(express) const http require(http) const socket require(socket.io) const { getUserInfoByToken } require(../../utils/light/tools)let app express() const server http.createS…

武汉芯源半导体CW32F030系列MCU在电焊机的应用

随着工业技术的发展&#xff0c;单片机在许多领域都发挥了重要的作用。在电焊机中应用单片机&#xff0c;通过编写特定的程序&#xff0c;可以实现自动化控制、提高焊接质量和效率。 电焊机是一种用于金属焊接的设备&#xff0c;利用电弧热将金属熔化实现焊接。电焊机主要由电源…

Flutter实现CombineExecutor进行多个异步分组监听,监听第一个异步执行的开始和最后一个异步执行结束时机。

1.场景 我们在调用接口时&#xff0c;很多时候会同时调用多个接口&#xff0c;接口都是异步执行&#xff0c;我们很难知道调用的多个接口哪个会最后执行完成&#xff0c;我们有时候需要对最后一个接口执行完成的时机监听&#xff0c;所以基于该需求&#xff0c;设计了CombineE…

反序列化漏洞复现(typecho)

文章目录 执行phpinfogetshell 执行phpinfo 将下面这段代码复制到一个php文件&#xff0c;命名为typecho_1.0-14.10.10_unserialize_phpinfo.php&#xff0c;代码中定义的类名与typecho中的类相同&#xff0c;是它能识别的类&#xff1a; <?php class Typecho_Feed{const…

IPv6改造深化之路

01 IPv6改造问题及整体改造思路 随着“十四五”期间国家政策对IPv6深化改造及规模部署的推动&#xff0c;在IPv6改造过程中出现了越来越多的系统性问题&#xff0c;如图1所示。 图1 关于IPv6改造的各种疑问所有跨设备通信的IT软硬件系统均需要处理IP地址&#xff0c;各领域均需…

LeetCode —— 复写零(双指针)

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 将数组中出现的每个零复写一遍&#xff0c;然后将其他元素向右平移&#xff0c;数组长度不能改变。 法一&#xff1a;使用额外空间的做法 class Solution { public:void duplica…

以指标管理为抓手,亿信华辰助力锦州银行打造全行级数据应用

随着金融数字化转型的持续深入&#xff0c;数据智能的业务化应用等正逐步受到行业关注。锦州银行携手亿信华辰&#xff0c;开展全行级指标管理体系建设&#xff0c;实现指标数据统一管理、统一标准、统一来源、统一汇总、统一加工、统一呈现&#xff0c;以及持续推动基础数据治…