Spring——IOC底层原理

本文深入探讨了Spring的IOC(控制反转)原理,包括什么是IOC及其目的,详细阐述了IOC的XML解析、工厂设计模式和反射的底层实现。接着,介绍了BeanFactory和ApplicationContext两大IOC接口,分析了它们的特点和应用场景。最后,详细讨论了基于XML的Bean管理,包括对象创建、依赖注入的各种方式,如set方法注入、构造器注入、p名称空间注入、注入其他类型属性、注入外部和内部bean、注入集合类型,并深入讲解了属性注入的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、IOC底层原理

1.什么是IOC

2.IOC底层原理

二、IOC接口(BeanFanctory)

三、IOC操作Bean管理(基于XML)

1.创建对象

2.注入属性

(1)DI:依赖注入

(2)p名称空间注入(了解)

(3)注入其他类型属性

(4)注入外部bean 

(5)注入内部bean和级联赋值

(6)注入集合类型

 4.属性注入细节


一、IOC底层原理

1.什么是IOC

        通过控制反转(Inversion of Control,缩写为IOC),对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

        1)控制反转,把对象的创建和对象之间调用的过程,交给Spring进行管理

        2)使用IOC目的:降低耦合度

        3)入门案例即为IOC的实现(入门案例:【网址】)

2.IOC底层原理

(1)XML解析 + 工厂设计模式 + 反射(令耦合度尽可能的降低到最小)

通过画图来讲解IOC底层原理:

二、IOC接口(BeanFanctory)

  1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
  2. Spring提供IOC容器实现的两种方式(两个接口,两个接口均可加载XML文件并且获取配置创建的对象):

        (1)BeanFactory:IOC容器基本实现方式,是Spring内部使用的接口,不提供开发人员使用。

                特点:加载配置文件时不会创建对象,而是在获取对象或者使用对象时才去创建对象。

        (2)ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般面向开发人员使用。

                特点:加载配置文件时,直接创建对象。

 ApplicationContext中有两个主要的实现类:

1. FileSystemXmlApplicationContext:括号中时配置文件的全路径。

2. ClassPathXmlApplicationContext:括号中直接配置文件名。

三、IOC操作Bean管理(基于XML)

Bean管理(两个操作):

        (1)Spring创建对象

        (2)Spring属性注入

1.创建对象

                

                a. 在Spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象创建。

                b. 在bean标签中有很多属性,常见的有:

                * id 属性:设置类的唯一标识(并不是直接指对象名称);

                * Class属性 :类全路径(包类路径);

                c. 创建对象的时候,默认执行无参构造器方法(若使用有参构造器,且未构成无参构造器,此时无参构造器被覆盖,所以常规获取对象方式报错)

2.注入属性

(1)DI:依赖注入

是AOC的一种具体实现,必须在创建对象的基础之上完成。

 * 第一种注入方式:set方法注入

i.

/**
 * 演示使用set方法进行属性注入
 */
public class Book {
    //创建属性
    private String bname;
    private String bauthor;

    //创建属性对应的set方式注入

    public void setBname(String bname) {
        this.bname = bname;
    }

    public void setBauthor(String bauthor) {
        this.bauthor = bauthor;
    }
    
    public void TestBook(){
        System.out.println(bname + " :: "+bauthor);
    }
}

 ii.在Spring配置文件中配置对象创建,配置属性注入

<bean id="Book" class="com.xx.Spring.Book">
    <!--使用property完成属性注入
        name:类里面的属性名称
        value:向属性注入的值
    -->
    <property name="bname" value="数据结构"></property>
    <property name="bauthor" value="xxx"></property>
</bean>

iii. 测试代码 

public void TestBook(){
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean1.xml");
    Book book = context.getBean("Book", Book.class);
    book.TestBook();
}

输出结果:

第二种注入方式:使用有参构造器注入

package com.xx.Spring.testDome;

/**
 * 演示使用有参构造来进行属性注入
 */
public class Orders {
    private String oname;
    private String address;

    public Orders(String oname, String address) {
        this.oname = oname;
        this.address = address;
    }
    public void Test(){
        System.out.println(oname + ":" + address);
    }
}
<bean id="Oders" class="com.xx.Spring.testDome.Orders">
    <constructor-arg name="oname" value="abc"></constructor-arg>
    <constructor-arg name="address" value="123"></constructor-arg>
</bean>
@Test
public void TestOrder(){
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean1.xml");
    Orders oders = context.getBean("Oders", Orders.class);
    oders.Test();
}

(2)p名称空间注入(了解)

        a. 使用p名称空间注入,可以简化基于xml配置方式

第一步:添加 p 名称空间在配置文件中

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

<bean id="Book" class="com.xx.Spring.Book" p:bname="高频电子线路" p:bauthor="周珩">
    
</bean>

测试:

@Test
public void TestBook() {
    ApplicationContext context =
            new ClassPathXmlApplicationContext("bean1.xml");
    Book book = context.getBean("Book", Book.class);
    book.TestBook();
}

 结果:

(3)注入其他类型属性

1.字面量

    (1)null

<bean id="Book" class="com.xx.Spring.Book">
    <!--设置一个空值-->
    <property name="bname" value="数据结构"></property>
    <property name="bauthor">
        <null/>
    </property>
</bean>

    (2)特殊符号

<bean id="Book" class="com.xx.Spring.Book">
    <!--属性值包含特殊符号
        1.把<>进行转义 &lt;&gt;
        <property name="bauthor" value="&lt;&gt;<<南京>>"></property>
        2.把带特殊符号的内容写道CDATA
    -->
    <property name="bauthor" >
        <value><![CDATA[<<南京>>]]></value>
    </property>
</bean>

(4)注入外部bean 

a.创建Service类和Dao类

b.在Service类中调用Dao类中的方法

c.在Spring配置文件中进行配置

//测试
@Test
public void TestBean1(){
    ApplicationContext context =
            new ClassPathXmlApplicationContext("Bean1.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.add();
}

/*结果:
Service.add运行.....
Dao.updata运行....
*/

(5)注入内部bean和级联赋值

 a.一对多关系:部门和员工(一个部门有多个员工,一个员工属于一个部门);

b.在实体类之间标识一对多(员工表示所属部门),使用对象类型属性进行表示;

c.在Spring配置文件中进行配置

public void TestBean_(){
    ApplicationContext context =
            new ClassPathXmlApplicationContext("Bean_.xml");
    Emp emp = context.getBean("emp", Emp.class);
    emp.add();
}

/*结果:
id = 1 ename = xx dept = Dept{dname='研发部'}
*/

注:配置文件也可以使用级联赋值的方式

也可以这样写: 但注意此时的结果: 

已经将bean标签中的value值覆盖!!!

(6)注入集合类型

集合类型(List、Map、Set)

1.注入数组类型属性

2.注入list集合属性

3.注入Map集合类型的属性

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

package com.xx.Spring2.collectionType;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Stu {
    //1.数组类型属性
    private String[] course;
    //2.List集合类型属性
    private List<String> list;
    //3.Map集合类型的属性
    private Map<String, String> mapS;
    //4.Set集合类型属性
    private Set<String> sets;

    public void setCourse(String[] course) {
        this.course = course;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMapS(Map<String, String> mapS) {
        this.mapS = mapS;
    }

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }

    public void test(){
    System.out.println(Arrays.toString(course));
    System.out.println("===============");
    System.out.println(list);
    System.out.println("===============");
    System.out.println(maps);
    System.out.println("===============");
    System.out.println(sets);
    }
}

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="stu" class="com.xx.Spring2.collectionType.Stu">
    <!--1.数组类型注入-->
    <property name="course" >
        <array>
            <value>英语</value>
            <value>语文</value>
            <value>数学</value>
            <value>历史</value>
        </array>
    </property>
    
    <!--list集合类型注入-->
    <property name="list" >
        <array>
            <value>张三</value>
            <value>三三</value>
        </array>
    </property>

    <!--map集合类型注入-->
    <property name="maps">
        <map>
            <entry key="1" value="数学"></entry>
            <entry key="2" value="语文"></entry>
        </map>
    </property>

    <!--set集合类型注入-->
    <property name="sets">
        <set>
            <value>mysql</value>
            <value>java</value>
        </set>
    </property>
</bean>

</beans>
//测试
package com.xx.Spring2.TestArray;

import com.xx.Spring2.collectionType.Stu;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    @org.junit.Test
    public void TestBeanArray(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beanArray.xml");

        Stu stu = context.getBean("stu", Stu.class);

        stu.test();
    }
}
/*结果:
[英语, 语文, 数学, 历史]
===============
[张三, 三三]
===============
{1=数学, 2=语文}
===============
[mysql, java]
*/

 4.属性注入细节

1.若集合中储存对象类型的值

(比如每个学生可以学多门课程,创建Course类,并且在Stu类中创建list集合来储存多个course对象,再构建其对应的set方法)

package com.xx.Spring2.collectionType;

public class Course {
    private String cname;

    public void setCname(String cname) {
        this.cname = cname;
    }
}

在配置文件中配制时,首先创建多个course对象

<!--创建多个course对象-->
<bean id="course1" class="com.xx.Spring2.collectionType.Course">
    <property name="cname" value="数据结构"></property>
</bean>
<bean id="course2" class="com.xx.Spring2.collectionType.Course">
    <property name="cname" value="Spring5"></property>
</bean>

再使用外部标签注入

<!--list对象类型值注入-->
<property name="courseList">
    <list>
        <ref bean="course1"></ref>
        <ref bean="course2"></ref>
    </list>
</property>

2.不同类中,用到相同的集合类型(目前的配置文件的说明只注入一个类中,解决方法:将list结合的配置提取,做成公共部分,让所有的类均能引入该集合)

(1)在Spring配置中先引入一个名称空间 (util)

(2)使用Util标签完成list集合注入提取

【关于IOC操作Bean管理(基于beanFactory)笔记可转:】

<think>我们正在讨论IOC(控制反转)的底层实现原理。根据引用内容,IOC的核心是依赖注入(DI),它改变了对象获取依赖的方式——由IOC容器在运行时动态注入,而不是对象自己创建。Spring框架是IOC的典型实现,它通过XML配置或注解来管理对象及其依赖关系。 关键点: 1. **控制反转(IoC)**:将对象的创建和依赖关系的管理从代码内部转移到外部容器(如Spring容器)中。这样,对象不再自己创建依赖对象,而是由容器注入。 2. **依赖注入(DI)**:IoC实现方式,主要有两种:setter注入(通过set方法)和构造器注入(通过构造方法)。引用[3]和[5]提到了这两种方式。 3. **Spring中的实现**:Spring容器负责管理Bean(即对象)的生命周期和依赖关系。我们可以通过XML配置(如<bean>标签和<property>标签)或注解(如@Component, @Autowired)来实现底层原理可以概括为: - **反射机制**:Spring容器在运行时通过反射来创建对象,并注入依赖。例如,通过Class.forName()加载类,然后调用newInstance()创建实例,再通过反射调用setter方法或构造器设置依赖。 - **工厂模式**:Spring使用工厂模式(BeanFactory)来创建和管理Bean。ApplicationContext是其更高级的接口。 - **容器管理**:容器维护一个Bean的定义(BeanDefinition)注册表,根据配置(XML或注解)来实例化Bean,并解决Bean之间的依赖关系(通过反射注入)。 具体步骤(以XML配置为例): 1. 读取配置文件(如applicationContext.xml),解析<bean>标签,将每个Bean的定义(包括类名、属性值、依赖关系)注册到BeanDefinition注册表中。 2. 当需要获取一个Bean时,容器会检查该Bean所依赖的其他Bean,并递归创建这些依赖Bean(如果尚未创建)。 3. 创建Bean实例:通过反射调用无参构造器(或指定构造器)创建对象。 4. 依赖注入:通过反射调用setter方法(或构造器)将依赖的Bean注入到目标Bean中。 5. 如果配置了初始化方法(如init-method),则调用它。 6. 容器将创建好的Bean放入缓存(通常是ConcurrentHashMap)中,以便后续使用。 注解方式(如@ComponentScan和@Autowired)的底层原理类似,只是配置方式不同。容器会扫描指定包下的类,识别带有@Component等注解的类,并自动注册为Bean。然后通过@Autowired注解自动注入依赖,同样是通过反射实现。 总结:IOC底层实现依赖于反射和工厂模式,容器在启动时读取配置信息,然后通过反射机制创建对象并注入依赖,从而实现了对象之间的解耦。</think>### IOC(控制反转)底层实现原理详解 IOC(控制反转)的底层实现原理主要基于**反射机制**、**工厂模式**和**容器管理**,核心是通过**依赖注入(DI)** 反转对象创建与依赖关系的控制权。以下是关键实现原理: --- #### 1. **核心机制:反射(Reflection)** - **动态创建对象**:IOC容器通过反射(如Java的`Class.forName()`和`newInstance()`)动态加载类并实例化对象[^3][^4]。 - **依赖注入实现**:容器扫描类中的字段或方法(如`@Autowired`注解),通过反射将依赖对象注入到目标对象中[^4]。 - *示例*: ```java Class<?> clazz = Class.forName("com.example.UserService"); Object instance = clazz.newInstance(); // 反射创建对象 ``` --- #### 2. **容器与工厂模式** - **BeanFactory**:Spring的核心接口,管理Bean的生命周期(创建、配置、依赖注入)[^3][^4]。 - **容器工作流程**: 1. **加载配置**:解析XML或注解(如`@Component`),生成Bean定义(`BeanDefinition`)[^4]。 2. **实例化Bean**:根据Bean定义,通过反射创建对象。 3. **依赖注入**:递归解析依赖关系,注入其他Bean(通过Setter或构造器)[^3][^5]。 4. **缓存管理**:将初始化后的Bean存入缓存(如`ConcurrentHashMap`),避免重复创建。 --- #### 3. **依赖注入(DI)的实现方式** - **Setter注入**:通过Setter方法注入依赖(XML配置`<property>`或注解`@Autowired`)[^5]。 ```xml <bean id="userService" class="com.example.UserService"> <property name="userDao" ref="userDao"/> <!-- 注入userDao --> </bean> ``` - **构造器注入**:通过构造参数注入依赖(更推荐,避免循环依赖)[^3]。 ```java @Component public class UserService { private final UserDao userDao; @Autowired public UserService(UserDao userDao) { // 构造器注入 this.userDao = userDao; } } ``` --- #### 4. **注解驱动的实现(如Spring)** - **组件扫描**:`@ComponentScan`扫描包路径,识别`@Component`、`@Service`等注解类[^4]。 - **依赖注入**:`@Autowired`自动按类型匹配Bean,并注入字段/方法中。 - **代理模式**:对需增强的Bean(如事务管理`@Transactional`),生成动态代理对象(JDK/CGLib)[^3]。 --- #### 5. **循环依赖的解决** - **三级缓存**(Spring特有机制): 1. **一级缓存**:存放完整初始化的Bean。 2. **二级缓存**:存放半成品Bean(已实例化但未注入依赖)。 3. **三级缓存**:存放Bean的工厂对象(用于生成代理对象)[^3]。 - *流程*:当Bean A依赖Bean B时,容器先创建A的半成品→创建B→B注入A的半成品→B完成→A注入B的完整实例。 --- #### 总结 IOC底层通过**反射动态创建对象**、**容器管理依赖关系**、**工厂模式统一生命周期**,实现控制权的反转。依赖注入(DI)是核心手段,解决了对象间的紧耦合问题,使系统更灵活、可测试[^1][^2][^3]。 --- ### 相关问题 1. **Spring如何解决循环依赖问题?三级缓存的具体作用是什么?** 2. **反射机制在IOC中可能引发哪些性能问题?如何优化?** 3. **除Setter和构造器注入外,还有哪些依赖注入方式?各自适用场景是什么?** 4. **IOC容器与传统工厂模式(如SimpleFactory)的核心区别是什么?** 5. **动态代理(JDK/CGLib)在Spring IOC中扮演什么角色?** [^1]: 依赖注入是 IoC 的核心实现方式。通过 DI,IoC 容器将依赖关系从对象自身转移到容器中管理,从而实现了控制反转。 [^2]: DI(依赖注入)其实就是IOC的另外一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。 [^3]: Spring 中依赖注入有两种实现方式:set 方式(传值方式)和构造器方式(引用方式)。 [^4]: Spring IOC:控制反转与依赖注入。通过注解如`@Component`和`@Autowired`实现组件管理和依赖注入。 [^5]: Spring 的依赖注入在XML中通过`<property>`配置属性值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值