精华内容
下载资源
问答
  • ioc容器
    千次阅读 多人点赞
    2021-02-02 19:58:25

    假设我们现在正在使用三层架构开发一个项目,其中有一个用户模块,包含登录、注册等功能。现在已经写好了User实体类和UserDao数据访问层:

    public class User
    {
        private Integer id;
        private String username;
        private String password;
    
        // 以下是getter和setter方法
    }
    
    public interface UserDao
    {
        // 查找用户
        User get(String username, String password);
    
        // 插入用户
        void insert(User user);
    }
    
    public class UserDaoImpl implements UserDao
    {
        @Override
        public User getByUsername(String username, String password)
        {
            ...
        }
    
        @Override
        public void insert(User user)
        {
            ...
        }
    }
    

    UserDao封装了对数据库中的用户表进行操作。现在,需要一个UserService来封装登录、注册这两个业务逻辑:

    public interface UserService
    {
        // 登录
        User login(String username, String password);
    
        // 注册
        void register(User user);
    }
    
    public class UserServiceImpl implements UserService
    {
        @Override
        public User login(String username, String password)
        {
            User user = userDao.get(username, password); // userDao从哪里来?
            if (user == null)
            {
                // 用户名或密码错误
            }
            return user;
        }
    
        @Override
        public void register(User user)
        {
            userDao.insert(user); // userDao从哪里来?
        }
    }
    

    显然,UserServiceImpl需要一个UserDao的实例userDao来访问数据库,那么问题来了:这个userDao如何该获取呢?

    很多人都会用如下代码来获取userDao

    public class UserServiceImpl implements UserService
    {
        private UserDao userDao = new UserDaoImpl();
        ...
    }
    

    直接在UserServiceImpl内部new一个UserDaoImpl,看起来很方便,也可以正常工作,但是它存在一些问题:

    1. 现在UserServiceImpl依赖于UserDaoImpl,如果这两个类是由两个不同的人开发的,则他们无法同时工作,因为在UserDaoImpl完成之前,UserServiceImpl无法通过编译
    2. UserServiceImpl无法被测试,因为它与某个特定的UserDao实现类绑定在了一起,我们不能把它替换成一个用于单元测试的MockUserDao
    3. 如果我们有多套数据库实现(即多个UserDao实现类),那么不能很方便地切换

    为了解决上面几个问题,可以使用一种被称为依赖注入的技巧:

    public class UserServiceImpl implements UserService
    {
        private UserDao userDao;
    
        // 构造函数注入
        public UserServiceImpl(UserDao userDao)
        {
            this.userDao = userDao;
        }
        ...
    }
    
    // 外部程序
    UserService userService = new UserServiceImpl(new UserDaoImpl());
    

    现在,userDao不是由UserServiceImpl本身构造,而是让外部程序通过UserServiceImpl的构造函数传入进来,这种操作称为构造函数注入

    还可以使用另一种注入方式——setter方法注入

    public class UserServiceImpl implements UserService
    {
        private UserDao userDao;
    
        // setter方法注入
        public void setUserDao(UserDao userDao)
        {
            this.userDao = userDao;
        }
        ...
    }
    
    // 外部程序
    UserService userService = new UserServiceImpl();
    userService.setUserDao(new UserDaoImpl());
    

    不论哪种注入方式,其基本逻辑都是一样的:组件不负责创建自己依赖的组件,而是让外部程序创建依赖组件,然后通过构造函数或setter函数注入进来。其实,这里也蕴含着控制反转的思想,因为创建依赖组件的任务从组件内部转移到了外部程序

    使用了依赖注入,前面的几个问题就迎刃而解了,因为UserServiceImpl不再依赖UserDao的具体实现类,我们可以轻松地替换UserDao的实现。

    但是问题又来了:该由谁负责对象的组装呢?

    答案是:应该由应用的最外层负责对象的组装。例如,在三层架构中,可以在controller层负责service类的组装;如果我们的程序有main函数,也可以在main函数中进行相关组件的组装。

    public class UserController
    {
        private UserService userService = new UserServiceImpl(new UserDaoImpl());
    
        public void handleLoginRequest(...)
        {
            userService.login(...);
            ...
        }
    }
    

    按照这种方式写程序,项目中的所有组件都按照依赖注入的方式管理自己的依赖,所有组件都由最外层统一组装,如果想替换掉某个组件的实现也很方便,看起来很美好。但是,当项目逐渐变得庞大,组件之间的依赖变多的时候,某个组件可能需要依赖于几十个大大小小的其它组件,创建这样的组件就成了一种折磨:

    // 创建一个复杂的组件
    Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    

    如果这个组件只需要被使用一次,看起来还是可以接受,但是如果这个组件在很多地方都要使用,那么在每个使用的地方都需要写一遍上面创建的代码,这将会产生大量的代码冗余:

    public class A
    {
        Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    
        public void f1()
        {
            // 使用c1
            ...
        }
    }
    
    public class B
    {
        Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    
        public void f2()
        {
            // 使用c1
            ...
        }
    }
    
    public class C
    {
        Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
    
        public void f3()
        {
            // 使用c1
            ...
        }
    }
    

    更糟糕的是,如果组件c1依赖的其中一个组件将要被替换,那么上面所有创建c1的代码都要修改,这简直是维护的噩梦!

    为了避免这个问题,可以把系统中所有的组件放进一个“容器”中统一管理:

    public class Container
    {
        public static Component1 getComponent1()
        {
            ...
        }
    
        public static Component2 getComponent2()
        {
            ...
        }
    
        public static Component3 getComponent3()
        {
            ...
        }
        ...
    }
    

    然后,系统中所有需要使用组件的地方都通过Container类来获取:

    public class A
    {
        Component1 c1 = Container.getComponent1();
    
        public void f1()
        {
            // 使用c1
            ...
        }
    }
    
    public class B
    {
        Component1 c1 = Container.getComponent1();
    
        public void f2()
        {
            // 使用c1
            ...
        }
    }
    
    public class C
    {
        Component1 c1 = Container.getComponent1();
    
        public void f3()
        {
            // 使用c1
            ...
        }
    }
    

    使用这种方法,不论是获取组件还是替换组件都非常方便。但是,现在Container类是通过Java代码来实现的,如果系统中的组件有任何变动,就需要修改代码,然后重新编译项目。在某些场景下,我们可能需要在项目运行时动态地添加、移除或者替换组件。

    为了实现组件的动态管理,可以将如何创建组件以及组件之间的依赖关系等信息写入配置文件中,然后项目启动时通过读取配置文件来动态创建所有组件,再放到Container中。这样就可以在项目运行时修改配置文件中的组件信息,而无需重新编译,甚至无需重启服务器:

    // 创建Container
    Container container = new ContainerFactory("container.xml").create();
    
    // 获取Component1
    Component1 c1 = (Component1) container.create("c1");
    

    其实,上面的Container就是一个简单的IOC容器。IOC表示控制反转,意思是创建组件的工作不再由程序员控制,而是由IOC容器控制,程序员只负责告诉IOC容器如何创建某个组件,如果想要这个组件,直接从容器中取就是了,这就是IOC容器的基本逻辑。

    更多相关内容
  • IOC容器初始化过程

    2021-01-27 12:30:37
    IOC容器的初始化分为三个过程实现:第一个过程是Resource资源定位。这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。 第二个过程是BeanDefinition的载入...
  • 本文通过实例代码给大家详解了springboot获取ioc容器中注入的bean问题,非常不错,具有一定的参考借鉴价值,需要的朋友参考下吧
  • 一个非常简单的 JavaScript IoC 容器,共 50 行。 是的,你没看错:50 行。 控制反转容器的主要目的是用作放置创建对象的特定于应用程序的代码的位置。 它实际上是工厂的注册表。 这个问题的其他 JavaScript 解决...
  • sp轻量级IOC容器

    2018-11-23 17:54:46
    ioc的注解轻量集框架。你只需要声音接口,并在接口方法上添加注解,然后new该接口,空实现就好。再使用工厂方法,创建代理接口对象即可。
  • 本文实例讲述了PHP依赖注入与Ioc容器。分享给大家供大家参考,具体如下: 背景 在很多编程语言(例如java)开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的...
  • 绑定是将适当的抽象具体实现(实现)引入IoC容器的过程。 在此过程中,您还将确定解析类型,单例或瞬态。 在单例绑定中,容器仅提供一次实例,并为所有请求返回该实例。 在临时绑定中,容器始终为每个请求返回一个...
  • 简单的国际奥委会 Golang 中的简单 IoC 容器。什么是IoC? 控制反转(IoC)是一种编程原理,用于反转控制以实现松散耦合。 控件是指类具有的任何附加职责,例如应用程序的流程或对对象创建或依赖对象创建和绑定流程...
  • Kontena (コンテナ)Swift 中的 IOC 容器服务定位器/IOC 容器的简单 Swift 实现,具有有限的 DI 功能。 适用于 Swift 和 Objective C 类。 有关如何使用 Kontena 的非常基本的示例,请查看演示项目。补充笔记如果要...
  • 主要介绍了基于Java反射技术实现简单IOC容器,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • IOC容器的创建流程.png

    2021-07-11 10:19:25
    Spring IOC容器的创建流程图
  • 最近在学习laravel,正好学习到了ioc容器,但发现网上这方面的资料较少,所以将自己学习的总结下,下面这篇文章主要给大家介绍了关于Laravel学习教程之IOC容器的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧...
  • 主要介绍了Spring创建IOC容器的方式解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • cppdepinject 用于C ++的简单IOC容器仅标头,可放入任何应用程序类型安全的注册和查找在注册时选择单个或多个实例(可选)使用名称注册以允许更高阶的结构首次查找时简单创建,允许以任何顺序在复杂图形中注册每个...
  • IOC容器

    千次阅读 2022-01-17 09:54:38
    Spring的IOC容器 1.入门案例 2. IOC底层原理 3. IOC接口(BeanFactory) 4. IOC操作Bean管理(基于xml) 5. IOC操作Bean管理(基于注解) 1.入门案例 (1)创建项目,项目下创建目录lib 导入相关的jar包 在...

    Spring的IOC容器

    		 1. 入门案例
             2. IOC底层原理
             3. IOC接口(BeanFactory)
             4. IOC操作Bean管理(基于xml)
             5. IOC操作Bean管理(基于注解)
    

    1.入门案例

    (1)创建项目,项目下创建目录lib
    导入相关的jar包
    在spring-framework的libs下找到四个相应的jar包
    四个基本的jar包
    Commons logging需要自己下载,不导入则会报错
    公用的日志记录,不导入会报错
    在这里插入图片描述
    导入后在在项目中进行添加即可在这里插入图片描述
    (2)使用普通方法创建一个类

    public class User {
        public void add() {
            System.out.println("add....");
        }
    }
    

    (3)创建Spring的配置文件,在配置文件中创建对象
    在这里插入图片描述
    在这里插入图片描述

    <bean id="user" class="com.zlp.spring5.User"></bean></beans>
    

    (4)进行测试代码编写

    public class TestSpring6 {
    
        @Test
        public void testAdd() {
            //1.加载spring配置文件(或者BeanFactory接口(加载配置文件时不会创建对象,使用对象时才会创建对象),一般内部使用)
            //而ApplicationContext在加载配置文件时就会创建对象
            ApplicationContext context =
                    new ClassPathXmlApplicationContext("bean1.xml");//在src路径下使用classpath
            //若在磁盘路径下是用FileSystemXmlApplicationContext
    
            //2.获取配置创建的对象
            User user = context.getBean("user", User.class);
            System.out.println(user);
            user.add();
        }
    }
    

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

    2.IOC底层原理

    1.什么是IOC
    (1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
    (2)使用 IOC 目的:为了耦合度降低
    (3)做入门案例就是 IOC 实现(即上所给的入门案例)
    2. IOC底层原理
    (1)xml 解析、工厂模式、反射
    3. 画图讲解IOC底层原理
    在这里插入图片描述

    3. IOC接口(BeanFactory)

    1、IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
    2、Spring 提供 IOC 容器实现两种方式:(两个接口)
    (1)BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用

    • 加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
      (2)ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人
      员进行使用
    • 加载配置文件时候就会把在配置文件对象进行创建
      3、ApplicationContext 接口有实现类
      在这里插入图片描述

    IOC操作Bean管理

    概念
    1、什么是 Bean 管理
    (0)Bean 管理指的是两个操作
    (1)Spring 创建对象
    (2)Spirng 注入属性
    2、Bean 管理操作有两种方式
    (1)基于 xml 配置文件方式实现
    (2)基于注解方式实现(或完全有注解方式实现,后期工作基本用完全湖州街方式实现)

    4. IOC操作Bean管理(基于xml)

    1.基于xml方式创建对象

    <!--    配置User对象的创建-->
        <bean id="user" class="com.zlp.spring5.User"></bean></beans>
    

    (1)在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
    (2)在 bean 标签有很多属性,常用的属性:

    • id 属性:唯一标识(即给对象起一个别名别名)
    • class 属性:类全路径(包类路径)
      (3)创建对象时候,默认也是执行无参数构造方法完成对象创建

    2、基于 xml 方式注入属性
    (1)DI:依赖注入,就是注入属性

    3、第一种注入方式:使用 set 方法进行注入
    (1)创建类,定义属性和对应的 set 方法

     private String bNAme;
        private String author;
        private String address;
    
        public void setAddress(String address) {
            this.address = address;
        }
        //set方法注入属性
        public void setbNAme(String bNAme) {
            this.bNAme = bNAme;
        }
        public void setAuthor(String author)
        {
            this.author = author;
        }
    }
    

    (2)在 spring 配置文件配置对象创建,配置属性注入

    <!--   set方法注入属性 -->
        <bean id="book" class="com.zlp.spring5.Book">
            <!--property注入属性
            name:类里面属性名称
            value:向属性注入的属性值-->
            <property name="bNAme" value="易经经"></property>
            <property name="author" value="达摩"></property>
    

    若属性值需要设置为空

    <property name="address">
                <null></null>
            </property>
    

    属性值有特殊符号时使用CDATA方法

    <property name="address">
                <value><![CDATA[<<南京>>]]></value>
            </property>
    

    4、第二种注入方式:使用有参数构造进行注入
    (1)创建类,定义属性,创建属性对应有参数构造方法

    public class Order {
        private String oname;
        private String address;
        //有参数的构造
        public Order(String oname,String address){
            this.address = address;
            this.oname = oname;
        }
    }
    

    (2)在 spring 配置文件中进行配置

    <!--   有参构造注入属性 -->
        <bean id="order" class="com.zlp.spring5.Order">
        <constructor-arg name="address" value="电脑"></constructor-arg>
        <constructor-arg name="oname" value="china"></constructor-arg>
        <constructor-arg index="0" value="电脑"></constructor-arg>
        </bean>
    

    5、p 名称空间注入(了解)
    (1)使用 p 名称空间注入,可以简化基于 xml 配置方式
    第一步 添加 p 名称空间在配置文件中

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    

    第二步 进行属性注入,在 bean 标签里面进行操作

    <bean id="book" class="com.zlp.spring5.Book" p:bNAme="九阳神功" p:author="无名"></bean>
    

    4. IOC操作Bean管理(基于xml)

    1.null值
    2.属性值包含特殊符号(1 ,2已经在前面属行注入里有讲到)
    3.注入属性外部Bean
    (1)创建两个类 service 类和 dao 类
    (2)在 service 调用 dao 里面的方法
    (3)在 spring 配置文件中进行配置

    public class UserService {
        //创建UserDo类型属性,生成set方法
        private UserDao userDao;
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        public void add(){
            System.out.println("service add......");
            userDao.update();
            //创建UserDao对象
    
        }
    }
    Spring配置:
        <bean id="userService" class="com.zlp.spring5.service.UserService">
    	<!--name属性:类里面属性名称
            ref属性:创建userDao对象bean标签的ID值-->
            <property name="userDao" ref="userDao"></property>
        </bean>
    	<!--外部建一个对象在把其引入(外部bean)-->
        <bean id="userDao" class="com.zlp.spring5.dao.UserDaoImpl"></bean>
    

    3、注入属性-内部 bean
    (1)一对多关系:部门和员工
    一个部门有多个员工,一个员工属于一个部门
    部门是一,员工是多
    (2)在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示

    //部门类
    public class Dept {
        private String dname;
    
        public void setDname(String dname) {
            this.dname = dname;
        }
    
        @Override
        public String toString() {
            return "Dept{"+
                    "danme='"+dname + '\'' + '}';
        }
    }
    
    //员工类
    public class Emp {
        private String ename;
        private String gender;
        //员工属于某个部门,使用对象形式表示
        private Dept dept;
        public void setDept(Dept dept) {
            this.dept = dept;
        }
    
        public void setEname(String ename) {
            this.ename = ename;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
        public void add(){
            System.out.println(ename+"::"+gender+"::"+dept);
        }
    }
    

    (3)在 spring 配置文件中进行配置

    <!--内部bean-->
        <bean id="emp" class="com.zlp.spring5.bean.Emp">
            <property name="ename" value="zlp"></property>
            <property name="gender" value="男"></property>
            <property name="dept">
                <bean id="dept" class="com.zlp.spring5.bean.Dept">
                    <property name="dname" value="安保部"></property>
                </bean>
            </property>
        </bean>
    

    5. IOC操作Bean管理(基于注解)

    1、注入数组类型属性
    2、注入 List 集合类型属性
    3、注入 Map 集合类型属性

    (1)创建类,定义数组、list、map、set 类型属性,生成对应 set 方法

    public class Stu {
    
        private String[] coursers;
    
        private List<String> list;
    
        private Map<String,String> maps;
        
    	private Set<String> sets;
        //学生选择多门课程
        private List<Course> courseList;
    
     public void setCoursers(String[] coursers) {
            this.coursers = coursers;
        }
        public void setSets(Set<String> sets) {
            this.sets = sets;
        }
        public void setMaps(Map<String, String> maps) {
            this.maps = maps;
        }
    	public void setList(List<String> list) {
            this.list = list;
        }
    

    (2)在 spring 配置文件进行配置

       <bean id="stu" class="com.zlp.spring5.collectiontype.Stu">
    <!--数组类型属性注入-->
            <property name="coursers">
                <array>
                    <value>java课程</value>
                    <value>数据库课程</value>
                </array>
            </property>
            <!--list类型属性注入-->
            <property name="list">
                <list>
                    <value>张三</value>
                    <value>李四</value>
                </list>
            <!--map类型属性注入-->
            </property>
            <property name="maps">
                <map>
                    <entry key="JAVA" value="java"></entry>
                    <entry key="PHP" value="php"></entry>
                </map>
            </property>
            <!--set类型属性注入-->
            <property name="sets">
                <set>
                    <value>Mysql</value>
                    <value>Redis</value>
                </set>
            </property>
    

    集合中设置对象类型

    <!--创建多个 course 对象--> <bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
     <property name="cname" value="Spring5 框架"></property>
    </bean> <bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
     <property name="cname" value="MyBatis 框架"></property>
    </bean>
    <!--注入 list 集合类型,值是对象--> <property name="courseList">
     <list>
     <ref bean="course1"></ref>
     <ref bean="course2"></ref>
     </list>
    </property>
    

    提取集合
    (1)在Spring配置文件中引入名称空间
    即增加一个util空间,和增加p空间相同,下面的schemaLocation中地址复制,把beans改为util即可

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    

    (2)配置文件

    <bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
    <!--提取list集合类型属性注入-->
            <util:list id="booklist">
                <value>太极</value>
                <value>咏春</value>
                <value>跆拳道</value>
            </util:list>
        <!--提前list集合类型属性注入使用
        scope设置多实例和单实例对象,单实例在加载spring配置文件时就会创建单实例对象
        而多实例则在调用getbean方法时候才创建多实例对象-->
            <bean id="book" class="com.zlp.spring5.collectiontype.Book" scope="prototype">
                <property name="list" ref="booklist"></property>
            </bean>
    
    展开全文
  • 其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IoC、DI以及Ioc容器等概念。通过本文我们将一起学习这些概念,并理清他们之间微妙的关系。对于大部分小菜来说,当听到大牛们高谈DIP、IoC、DI以及...
  • IOC容器结构 IOC(控制反转): 传统javaSE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象,而Ioc是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建。 大家都知道spring的...

    IOC容器结构

    IOC(控制反转): 传统javaSE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象,而Ioc是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建。

    大家都知道spring的核心组件分别是IOC和AOP等,其中IOC是目前开发中用的最多也最熟悉的,那么spring是怎么实现IOC组件功能的呢?我们先从IOC容器的结构出发深入浅出的讲解Spring的IOC组件,由于Spring中的IOC功能实属很强大, 我会将这个作为一个系列去讲解Spring的IOC功能,当然,本人对SpringIOC功能也不是全知全会,只是将自己的一些浅薄的见解通过文章表现出来,如果有什么理解不对的地方,希望各位能在评论或者私信我。

    IOC容器组件概述

    • 资源加载器(ResourceLoader):Spring用于加载资源文件并生成Resource的对象的组件(可类比jdk中的classloader)。
    • 资源描述组件(Resouurce):Spring对资源文件,如:xml、properties等资源的描述
    • Bean对象构建组件(BeanDefinitionReader):Spring中将Reource转换生成beanDefinition对象的组件
    • Bean描述组件(BeanDefinition):Spring对Bean描述的基本数据结构
    • bean注册器(BeanRegister):用于将所有Bean注册到IOC容器中的BeanFactory中的组件
      -bean容器 (BeanFactory):Spring容器,里面存放Spring所管理的所拥有的bean容器(Map)。

    springIOC容器的启动过程如下:

    1. 创建Spring容器,也就是BeanFactory
    2. Spring容器通过ResourceLoader+ResourceResolver解析资源生成Resource对象
    3. 将生成的Resource对象通过BeanDefinitionReader转换成BeanDefinition对象
    4. 将生成的BeanDefinition对象通过BeanRegister(AliasRegister、SingletonRegister)注册到容器中
    5. 容器初始化完成

    请添加图片描述

    这个过程就是对Spring源码IOC容器初始化的一个抽象理解,如果是不太清楚SpringIOC容器的源码的小伙伴可能会很陌生,对于这种对有看过spring源码的小伙伴,这里只需要你们有个粗略的印象就好,后面我会通过一个专栏去讲解这个SpringIOC容器的启动过程和Bean的创建过程,所以不要着急,先有个大概的印象,对之后我们去跑源码的时候会很有帮助。

    IOC容器组建详解

    上面,我们已经对SpringIOC容器组件进行了一个粗略的了解,下面我们再对SpringIOC容器的各个组件的继承关系、具体功能做一个解释:

    资源描述组件(Resource)

    是Spring对资源描述的组件,是信息的一个载体,像各种类型的文件、二进制流都是资源,这里大家应该也能想到,Spring使用过程中会使用很多资源,如xml、properties等,而Resource就是这些文件的一个载体,这个组件中有两个常用的类,如FileSystemResource表示文件系统上的资源,UrlResource则是表示网络资源。
    在这里插入图片描述
    从Resource接口的继承关系,我们也能清楚的知道它就是对文件资源的一种描述。Resource的类图中的ServletContextResource故名思义就是对tonmcat中的ServletContext资源的描述;ClassPathResource表示的类加载路径上的资源;ByteArrayResource表示的是字节数组资源。FileSystemResource和UrlResource的含义,上面已经说过了。
    在这里插入图片描述

    资源加载组件(ResourceLoader+ResourceResolver)

    这个组件其实有两个功能其中一个功能是负责相关资源的加载,大家可以类比一下类加载器,这个功能有ResourceLoader体系完成,还有一个功能是资源的解析,而资源的解析是通过ResourceResolver(资源解析器)实现完成的。

    而spring支持的几种加载的资源的方式有:

    1. URL位置资源,如“file:C:/application.txt”
    2. ClassPath位置资源,如“classpath:application.xml"(大家对这种资源加载应该是比较熟悉的)
    3. 相对路径资源,如“WEB-INF/application.xml”

    而spring实现几种加载资源的方式其实就是通过策略模式所实现的,对于不同的方式加载资源采用不同的策略。而且返回的Resource对象也不同,这个过程也同样在源码中有体现。

    下面这是FileSystemResourceLoader获取资源的策略,返回的Resource是继承了FileSystemResource的FileSystemContextResource,同样的其他几种加载方式在源码中都是类似的结构,这里就不过多介绍了,之后spring系列的博客会有详细说明。
    在这里插入图片描述

    看下面类图,我们发现了,ResouceLoader有两个继承分支,这两个分支分别代表着资源加载和资源解析两种功能。
    在这里插入图片描述

    BeanDefinitionReader(Bean构造器组件)

    将Resouce对象转换成BeanDefinition对象,就是将内部资源数据转换成Spring Bean描述数据。这种转换的模式在Dom4j解析器中也有体现。

    在这里插入图片描述
    上面这个图中,是不完全的,在最新的spring版本中还加了一个GroovyBeanDefinitionReader,是用于构建groovy配置文件中的Bean信息的(这是一种新的定义bean的方式,个人觉得很少能用上,这里就不过多细说了,如果可以想了解的,可以去问度娘)。而PropertiesBeanDefinitionReader和XmlBeanDefinitionReader这两个类分别是构建.propertie文件中的bean对象和构建xml文件中bean对象。

    Bean注册组件(BeanRegistry)

    将BeanDefinition对象注册到Bean容器中。
    在这里插入图片描述
    spring一共有AliasRegistry和SingletonBeanRegistry这两种体系,分别表示通过别名注册和单例注册,这两种注册在我们使用spring的时候应该深有体会。

    Bean容器组件(BeanFactory)

    是整个SpringIOC的核心组件,其他组件都是为Bean容器组件服务,但又不单纯为其服务。这个组件中装着Spring容器所管理的所有的Bean对象以及所需要的各种数据。其中BeanFactory有很多文章说他是一个初级容器,是因为这个这个容器的功能很简单,就是装Bean对象,是一个纯粹的容器,而ApplicationContext这个容器不单纯是用于装Bean,它还有着其他的功能,如Bean生命周期的管理等。

    可以说BeanFactory是面向spring本身而言的,而ApplicationContext是面向用户而言的。

    对于ApplicationContext它不仅继承了BeanFactory的功能还和应用环境有着藕断丝连的关系。因此的它被命名为应用上下文对象是非常的合适的。
    在这里插入图片描述

    ApplicationContext 继承了 HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此基础上,还通过多个其他的接口扩展了 BeanFactory 的功能。

    • ClassPathXmlApplicationContext:默认从类路径加载配置文件
    • FileSystemXmlApplicationContext:默认从文件系统中装载配置文件
    • ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了 ApplicationListener 事件监听接口的 Bean 可以接收到容器事件 , 并对事件进行响应处理 。 在ApplicationContext 抽象实现类AbstractApplicationContext 中,我们可以发现存在一个 ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者
    • MessageSource:为应用提供 i18n 国际化消息访问的功能;
    • ResourcePatternResolver : 所 有 ApplicationContext 实现类都实现了类似于PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
    • LifeCycle:该接口是 Spring 2.0 加入的,该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的
    • ConfigurableApplicationContext 扩展于 ApplicationContext,它新增加了两个主要的方法: refresh()和 close(),让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用 refresh()即可启动应用上下文,在已经启动的状态下,调用 refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要过多关心这些方法。
    展开全文
  • 来,从零手写一个IOC容器

    万次阅读 2022-06-14 17:25:18
    IOC(控制翻转)是程序设计的一种思想,其本质就是上端对象不能直接依赖于下端对象,要是依赖的话就要通过抽象来依赖。

    一、简介

    IOC(控制翻转)是程序设计的一种思想,其本质就是上端对象不能直接依赖于下端对象,要是依赖的话就要通过抽象来依赖。比如,上端对象如 BLL 层中,需要调用下端对象的 DAL 层时不能直接调用 DAl 的具体实现,而是通过抽象的方式来进行调用。

    有这么一个场景,项目本来是用 Sqlserver 来进行数据访问的,那么就会有一个 SqlserverDal 对象。BLL 层调用的时候通过 new SqlserverDal(),直接创建一个 SqlserverDal 对象进行数据访问,现在项目又要改为 Mysql 数据库,用 MysqlDal 进行数据访问。这时候就麻烦了,你的 BLL 层将 new SqlserverDal() 全部改为 new MysqlDal()。同理 BLL 层也是这个道理。这么做,从程序的架构而言是相当不合理的。

    这时 IOC 就排上用场了,IOC 的核心理念就是上端对象通过抽象来依赖下端对象,那么我们在 BLL 中,不能直接通过 new SqlserverDal()来创建一个对象,而是通过结构来声明(抽象的形式来进行依赖),当我们替换 MysqlDal 时我们只需让 MysqlDal 也继承这个接口,那么我们 BLL 层的逻辑就不用动了。

    现在又有一个问题,对象我们可以用接口来接收,所有子类出现的地方都可以用父类来替代,这没毛病。但对象的创建还是要知道具体的类型,还是通过之前的 new SqlserverDal() 这种方式创建对象。肯定是不合理的,这里我们还是依赖于细节。这时候 IOC 容器就该上场了,IOC 容器可以理解为一个第三方的类,专门为我们创建对象用的,它不需要关注具体的业务逻辑,也不关注具体的细节。你只需将你需要的创建的对象类型传给它,它就能帮我们完成对象的创建。

    二、.Net 内置 IOC

    接触 .Net Core 的小伙伴可能对容器很熟悉,.Net Core 中将 IOC 容器内置了,创建对象需要先进行注册。如下:

    public void ConfigureServices(IServiceCollection services)
    {
           services.AddTransient<IHomeBll,HomeBll>();
           services.AddTransient<Iservice,LoginService>();
    }
    

    三、手写 IOC 容器

    从 .Net Core 中可以看出,是通过 ServiceCollection 容器帮我们完成对象的创建,我们只需将接口的类型和要创建对象的类型传进去,它就能帮我们完成对象的创建。那么它是什么原理呢,我们能不能创建自已的容器来帮我们完成对象的创建呢? 答案:当然可以。

    3.1 容器雏形

    首先不考虑那么多,先写一个容器,帮我们完成对象的创建工作。如下:

    public interface IHTContainer
    {
        void RegisterType<IT, T>();
        IT Resolve<IT>();
    }
    
    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            string key = typeof(IT).FullName;
    
            //获取要创建对象的类型
            Type type = this.TypeDictionary[key];
    
            //这里先不考虑有参构造函数的问题,后面会逐一的解决这些问题
            //通过反射完成对象的创建,这里我们先不考虑参数问题
            return (IT)Activator.CreateInstance(type);
        }
    }
    

    简单调用,现在是通过一个第三方的容器为我们创建对象,并且我们不用依赖于细节,通过接口的类型完成对象的创建,当要将 SqlserverDal 替换为MysqlDal 时,我们只需要在注册的时候将 SqlserverDal 替换为 MysqlDal 即可。

     //实例化容器对象
    IHTContainer container = new HTContainer();
    //注册对象
    container.RegisterType<IDatabase,SqlserverDal>();
    //通过容器完成对象的创建,不体现细节,用抽象完成对象的创建
    IDatabase dal = container.Resolve<IDatabase>();
    dal.Connection("con");
    

    3.2 升级容器(单构造函数)

    上面将传统对象创建的方式,改为使用第三方容器来帮我们完成对象的创建。但这个容器考虑的还不是那么的全面,例如有参构造的问题,以及对象的依赖问题还没有考虑到。接下来继续完善这个容器,这里先不考虑多个构造函数的问题,这里先解决只有一个构造函数场景的参数问题。

    3.2.1 一个参数

    构造函数,单个参数情况。如下:

    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            //获取要创建对象的类型
            string key = typeof(IT).FullName;
            Type type = this.TypeDictionary[key];
    
            //这里先考虑只有一个构造函数的场景
            var ctor = type.GetConstructors()[0];
    
            //一个参数的形式
            var paraList = ctor.GetParameters();
            var para = paraList[0];
    
            Type paraInterfaceType = para.ParameterType;// 获取参数接口类型
            Type paraType = this.TypeDictionary[paraInterfaceType.FullName]; //获取依赖对象类型
            object oPara = Activator.CreateInstance(paraType);   //创建参数中所依赖的对象
    
            return (IT)Activator.CreateInstance(type, oPara);   //创建对象并传递所依赖的对象
        }
    }
    

    3.2.2 多个参数

    上面看了构造函数只有一个参数的问题,是通过构造函数的类型创建一个对象,并将这个对象作为参数传递到要实例化的对象中。那么多参数就需要创建多个参数的对象传递到要实例的对象中。如下:

    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            //获取要创建对象的类型
            string key = typeof(IT).FullName;
            Type type = this.TypeDictionary[key];
    
            //这里先考虑只有一个构造函数的场景
            var ctor = type.GetConstructors()[0];
    
            //多个参数的形式,声明一个list来存储参数类型的对象
            List<object> paraList = new List<object>();
    
            foreach (var para in ctor.GetParameters())
            {
                Type paraInterfaceType = para.ParameterType;
                Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
                object oPara = Activator.CreateInstance(paraType);
                paraList.Add(oPara);
            }
    
            return (IT)Activator.CreateInstance(type, paraList.ToArray()); //创建对象并传递所依赖的对象数组
        }
    }
    

    3.2.3 循环依赖

    通过上面的两步操作,已经能对构造函数中的参数初始化对象并传递到要实例的对象中,但这只是一个层级的。刚才做的只是解决了这么一个问题,假设要创建 A 对象,A 对象依赖于 B 对象。要做的就是创建了 B 对象作为参数传递给 A 并创建 A 对象,这只是一个层级的。当 B 对象又依赖于 C 对象,C 对象又依赖于 D 对象,这么一直循环下去。这样的场景该怎么解决呢?下面将通过递归的方式来解决这一问题。如下:

    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            return (IT)this.ResolveObject(typeof(IT));
        }
    
        //通过递归的方式创建多层级的对象
        private object ResolveObject(Type abstractType)
        {
            //获取要创建对象的类型
            string key = abstractType.FullName;
            Type type = this.TypeDictionary[key];
    
            //这里先考虑只有一个构造函数的场景
            var ctor = type.GetConstructors()[0];
    
            //多个参数的形式
            List<object> paraList = new List<object>();
    
            foreach (var para in ctor.GetParameters())
            {
                Type paraInterfaceType = para.ParameterType;
                Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
    
                //自已调用自己,实现递归操作,完成各个层级对象的创建
                object oPara = ResolveObject(paraInterfaceType);
    
                paraList.Add(oPara);
            }
    
            return (object)Activator.CreateInstance(type, paraList.ToArray());
        }
    }
    

    3.3 升级容器(多构造函数)

    上面只是考虑了只有一个构造函数的问题,那初始化的对象有多个构造函数该如何处理呢,可以像 Autofac 那样选择一个参数最多的构造函数,也可以像 ServiceCollection 那样选择一个参数的超集来进行构造,当然也可以声明一个特性,那个构造函数中标记了这个特性,然后就采用那个构造函数。如下:

    public class HTAttribute : Attribute
    {
    }
    
    public class HTContainer : IHTContainer
    {
        //创建一个Dictionary数据类型的对象用来存储注册的对象
        private Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
    
        //注册方法,用接口的FullName为key值,value为要创建对象的类型
        public void RegisterType<IT, T>()
        {
            this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
        }
    
        //创建对象通过传递的类型进行匹配
        public IT Resolve<IT>()
        {
            return (IT)this.ResolveObject(typeof(IT));
        }
    
        //通过递归的方式创建多层级的对象
        private object ResolveObject(Type abstractType)
        {
            //获取要创建对象的类型
            string key = abstractType.FullName;
            Type type = this.TypeDictionary[key];
    
            //获取对象的所有构造函数
            var ctorArray = type.GetConstructors();
    
            ConstructorInfo ctor = null;
            //判断构造函数中是否标记了HTAttribute这个特性
            if (ctorArray.Count(c => c.IsDefined(typeof(HTAttribute), true)) > 0)
            {
                //若标记了HTAttribute特性,默认就采用这个构造函数
                ctor = ctorArray.FirstOrDefault(c => c.IsDefined(typeof(HTAttribute), true));
            }
            else
            {
                //若都没有标记特性,那就采用构造函数中参数最多的构造函数
                ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
            }
    
            //多个参数的形式
            List<object> paraList = new List<object>();
    
            foreach (var para in ctor.GetParameters())
            {
                Type paraInterfaceType = para.ParameterType;
                Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
                object oPara = ResolveObject(paraInterfaceType);  //自已调用自己,实现递归操作,完成各个层级对象的创建
                paraList.Add(oPara);
            }
    
            return (object)Activator.CreateInstance(type, paraList.ToArray());
        }
    }
    

    四、总结

    通过上面的教程,可以自己写一个 IOC 容器了,也就可以看到 IOC 容器的本质了,其实用了大量的反射。实际生产使用,会有 Singleton、Transient、Scoped 三种生命周期,设计时需要结合 static、httpcontext 等进行实现(这个以后再进行讲解)。如果用现有的 autofac、Microsoft.Extensions.DependencyInjection 等则就不需要啦。

    展开全文
  • IOC容器简单实现

    2017-12-27 14:27:24
    一个简单的IOC容器的java实现,一个简单的IOC容器的java实现
  • IOC容器的实现

    千次阅读 2022-01-29 23:13:15
    自己实现一个简易版的spring ioc容器 步骤 1.创建注解 2.提取标记对象 3.实现容器 4.依赖注入 创建注解 创建4个注解,用来自动装配 package org.simpleframework.core; import java.lang.annotation.Element...
  • 这是用于PHP的功能齐全的IoC(控制反转)容器。 您可以使用AutoFac或Ninject for .NET等模块类对其进行配置。
  • Spring IOC容器启动整体流程

    千次阅读 2021-11-23 20:40:31
    二、IOC整体流程 一、简介 Spring核心有两个: IOC:控制反转 AOP:面向切面编程 相信大家在项目中都使用过Spring进行开发,都知道Spring非常强大,那么在熟练掌握Spring进行开发后,我们有必要去了解一下其中的...
  • IOC容器的介绍

    2021-12-07 10:38:12
    简单介绍Spring中的IOC容器
  • wire.js 是一个轻量级的 JavaScript IoC 容器实现。 标签:wirejs
  • IOC容器概念

    2022-01-25 21:43:58
    IOC容器的概念 IOC容器就是具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装。在Spring中...
  • 3. IOC容器概念及原理 3.1 演变过程 普通方式–>工厂模式实现–>IOC过程 3.2 普通方式: 在使用相应类的地方new一个新对象,然后调用其相应的方法进行使用。 ​ **缺点:**耦合度过高,如先前service层中创建...
  • BasicDI:C#内置的基本IoC容器,用于注册类型并返回已注册的类型
  • C#写简单的IOC容器

    千次阅读 2021-11-16 09:14:06
    文章目录前言一、IOC和DIP1.IOC2.DIP二、写一个超简单的IOC容器实例1.代码准备2.IOC容器3.调用结果总结 前言 IOC个人之前一直搞不明白,不够理解,写这篇文章也是为了加强学习。 一、IOC和DIP 1.IOC IOC中文叫做...
  • 什么是Spring IOC容器

    2022-05-16 10:34:52
    SpringIoc的介绍

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 213,441
精华内容 85,376
关键字:

ioc容器