Spring之IoC理论

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云解析DNS-重点域名监控,免费拨测 20万次(价值200元)
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Spring之IoC理论

概述


上一篇spring概述我们搭建完基于 Spring 框架的环境, 这篇我们开始真正的阅读 Spring 的源码,分析 Spring 的源码之前我们先来简单回顾下 Spring 核心功能的简单使用。


为什么需要 IoC


假如有这么一个业务场景:dao 层从不同的地方获取用户数据,service 层用来调用获取用户的方法,如何控制从想要的地方获取用户数据?


1、先写一个 User 类


public class User {
    private String name;
    public User() {
    }
    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
复制代码


2、写一个 UserDao 接口


public interface UserDao {
    public void getUser();
}
复制代码


3、再去写 Dao 的实现类


public class UserDaoImpl implements UserDao {
    public void getUser() {
        User user = new User("hresh");
        System.out.println("从bean中获取到的用户数据为"+user);
    }
}
复制代码


4、写 UserService 的接口


public interface UserService {
    public void getUser();
}
复制代码


5、最后写 UserService 的实现类


public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();
    public void getUser() {
        userDao.getUser();
    }
}
复制代码


6、测试一下


public class UserGetTest {
    @Test
    public void getUser(){
        UserService userService = new UserServiceImpl();
        userService.getUser();
    }
}
复制代码


这样就实现了一种读取用户信息的方式,接下来我们再增加一种从 Mysql 数据库中读取用户信息的方法。


再增加 UserDao 的实现类


public class UserDaoMysqlImpl implements UserDao {
    public void getUser() {
        User user = new User("acorn");
        System.out.println("从MySQL数据库中获取到的用户数据为"+user);
    }
}
复制代码


紧接着我们要去使用 MySql 的话 , 我们就需要去 service 实现类里面修改对应的实现。


public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoMySqlImpl();
    @Override
    public void getUser() {
        userDao.getUser();
    }
}
复制代码


同样如果我们需要从 Oracle 数据库中读取数据,还需要构建一个 UserDao 的实现类,然后修改 UserServiceImpl 类。  假设我们的这种需求非常大 , 这种方式就根本不适用了,每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 。


那我们如何去解决?



我们可以在调用 UserDao 实现类的地方,不去实例化该对象,而是留出一个接口 ,利用 set 方法,代码如下:


public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // 利用set实现
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void getUser() {
        userDao.getUser();
    }
}
复制代码


现在在测试类里,进行测试:


public class UserGetTest {
    @Test
    public void getUser(){
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(new UserDaoImpl());
        userService.getUser();
        userService.setUserDao(new UserDaoMysqlImpl());
        userService.getUser();
    }
}
复制代码


执行结果为:


从bean中获取到的用户数据为User{name='hresh'}
从MySQL数据库中获取到的用户数据为User{name='acorn'}
复制代码


虽然只是 UserServiceImpl 类中的代码做了修改,看起来变动不大,甚至你可能会说测试类中还变复杂了。但是仔细想一下,之前所有的 Dao 实现类都是在 UserServiceImpl 中控制创建,而现在由更接近用户的测试类中控制创建对象,把主动权交给了调用者,程序不用去管怎么创建,怎么实现了,它只负责提供一个接口即可。


这种思想 ,从本质上解决了问题 , 我们程序员不再去管理对象的创建了,更多的去关注业务的实现 ,耦合性大大降低 。这也就是 IoC 的原型 !


IoC本质


IoC( Inverse of Control:控制反转 )是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由  Spring 框架来管理。IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体,IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

要了解控制反转,有必要先了解软件设计的一个重要思想:依赖倒置原则( Dependency Inversion Principle )。


  • 高层模块不应该依赖于底层模块,两者应该依赖于其抽象。
  • 抽象不应该依赖具体实现,具体实现应该依赖抽象。

上面2点是依赖倒置原则的概念,也是核心。主要是说模块之间不要依赖具体实现,依赖接口或抽象。

其实依赖倒置原则的核心思想是面向接口编程。


image.png


将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清楚这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。


IoC 在 Spring 中有多种实现方式,可以使用 XML 配置,也可以使用注解,新版本的 Spring 也可以零配置实现 IoC。


Spring 容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从 IoC 容器中取出需要的对象。


image.png


采用 XML 方式配置 Bean 的时候,Bean 的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。


实战分析


编写代码

定义一个 bean 类:

public class User {
    private String name;
    public User() {
    }
    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
复制代码


源码很简单,bean 没有特别之处,Spring 的目的就是让我们的 bean 成为一个纯粹的 POJO,这就是 Spring 追求的,接下来就是在配置文件中定义这个 bean,配置文件如下:


<?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="user" class="com.msdn.bean.User">
        <property name="name" value="hresh" />
    </bean>
</beans>
复制代码


在上面的配置中我们可以看到bean的声明方式,在spring中的bean定义有N种属性,但是我们只要像上面这样简单的声明就可以使用了。


具体测试代码如下:


public class MyBeanTest {
    @Test
    public void MyBean(){
        //解析application_context.xml文件 , 生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
        //getBean : 参数即为spring配置文件中bean的id .
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
复制代码


执行结果为:


User{name='hresh'}
复制代码


思考


  • User 对象是谁创建的?【user 对象是由 Spring 创建的】
  • User 对象的属性是怎么设置的?【user 对象的属性是由 Spring 容器设置的】
    这个过程就叫做控制反转:
  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用 Spring 后,对象是由 Spring 来创建的。
  • 反转:程序本身不创建对象,而变成被动地接收对象。
    依赖注入:利用 set 方法来进行注入的。
    IOC是一种编程思想,由主动的编程变成被动的接收
    关于  ClassPathXmlApplicationContext  的学习后续会单独介绍,有兴趣的朋友可以去看一下。


按照上述的方式我们对之前提到的业务场景进行修改。首先新增 一个 Spring 配置文件 application_context.xml


<?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="mysqlImpl" class="com.msdn.dao.UserDaoMysqlImpl" />
    <bean id="oracleImpl" class="com.msdn.dao.UserDaoOracleImpl" />
    <bean id="serviceImpl" class="com.msdn.service.UserServiceImpl">
         <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
        <!--引用另外一个bean , 不是用value 而是用 ref-->
        <property name="userDao" ref="oracleImpl" />
    </bean>
</beans>
复制代码


测试代码如下:


@Test
public void MyBean(){
    ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
    UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl");
    serviceImpl.getUser();
}
复制代码


之后我们不需要再去程序中改动了,要实现不同的操作,只需要在 XML 配置文件中进行修改。所谓的 IoC 就是对象由 Spring 来创建、管理和装配。


IoC涉及到的组件


在上文测试代码中我们用到的是 ApplicationContext,具体实现是 ClassPathXmlApplicationContext。所以接下来我们简单分析一下在此过程中涉及到的组件。


首先是  ClassPathXmlApplicationContext  类的继承关系图。


image.png


基本上包含了  IOC 体系中大部分的核心类和接口。 下面我们就针对这个图进行简单的拆分和补充说明。


Resource 主要负责对资源的抽象,它的每一个实现类都代表了一种资源的访问策略,如  ClasspathResource 、 URLResource ,FileSystemResource 等。


image.png


有了资源,就需要有资源加载模块,Spring 利用 ResourceLoader 来进行统一资源加载,关系图如下:


image.png


资源加载完毕之后就需要 BeanFactory 来进行加载解析,它是一个 bean 容器,其中  BeanDefinition 是它的基本结构,它内部维护着一 个 BeanDefinition map ,并可根据 BeanDefinition 的描述进行 bean 的创建和管理。


image.png


BeanFacoty 有三个直接子类 ListableBeanFactoryHierarchicalBeanFactoryAutowireCapableBeanFactoryDefaultListableBeanFactory 为最终默认实现,它实现了所有接口。


BeanDefinition 用来描述 Spring 中的 Bean 对象。


image.png


BeanDefinitionReader 的作用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。


image.png


ApplicationContext 是个 Spring 容器,也叫做应用上下文。它继承  BeanFactory,同时也是  BeanFactory 的扩展升级版。由于 ApplicationContext 的结构就决定了它与 BeanFactory 的不同,其主要区别有:

  1. 继承 MessageSource ,提供国际化的标准访问策略;
  2. 继承 ApplicationEventPublisher,提供强大的事件机制;
  3. 扩展 ResourceLoader,可以用来加载多个 Resource,可以灵活访问不同的资源;
  4. 对 Web 应用的支持。


image.pngimage.png


上述提到的六个重要知识点是 Spring IoC 中最核心的部分,后续的学习也是针对这些内容进行详细解读。


IoC创建对象


无参构造器


当对象由无参构造器创建时,属性是由该类的 set 方法写入的。


User 类

public class User {
    private String name;
    public User() {
        System.out.println("user无参构造方法");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
复制代码


application_context.xml


<?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="user" class="com.msdn.bean.User">
        <property name="name" value="hresh" />
    </bean>
</beans>
复制代码


测试代码:


public class MyBeanTest {
    @Test
    public void MyBean(){
        //解析application_context.xml文件 , 生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
        //在执行getBean的时候, user已经创建好了,属性是通过set方法写入的
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
复制代码


执行结果为:


user无参构造方法
User{name='hresh'}
复制代码


如果将 User 类中的 set 方法注释掉,再次调用测试代码,会报错,说明对象是由无参构造器创建成功后,会调用 set 方法完成实例的初始化。


有参构造器


User 类


public class User {
    private String name;
    public User() {
        System.out.println("user无参构造方法");
    }
    public User(String name) {
        this.name = name;
        System.out.println("user有参构造方法");
    }
    public String getName() {
        return name;
    }
//    public void setName(String name) {
//        this.name = name;
//    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
复制代码


application_context.xml


<?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="user" class="com.msdn.bean.User">
        <constructor-arg name="name" value="hresh" />
    </bean>
</beans>
复制代码


测试代码:


public class MyBeanTest {
    @Test
    public void MyBean(){
        //解析application_context.xml文件 , 生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
复制代码


执行结果为:


user有参构造方法
User{name='hresh'}
复制代码


结论:Spring 容器根据 XML 文件中的配置,调用 bean 类的有参构造器来创建对象。


Spring中XML配置


别名

alias 设置别名 , 为bean设置别名 , 可以设置多个别名 。

<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userT" alias="userNew"/>
复制代码


Bean的配置


<!--bean就是java对象,由Spring创建和管理-->
<!--
    id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
    如果配置id,又配置了name,那么name是别名
    name可以设置多个别名,可以用逗号,分号,空格隔开
    如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
    class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.msdn.bean.Hello">
    <property name="name" value="Spring"/>
</bean>
复制代码


import

团队的合作通过import来实现 ,当有多个关于 bean 定义的文件,最后可以集中在一个文件中。


<import resource="{path}/beans.xml"/>
复制代码


参考文献

https://blog.kuangstudy.com/index.php/archives/518/



相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
2月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
10月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
694 26
|
6月前
|
XML 人工智能 Java
Spring IOC 到底是什么?
IOC(控制反转)是一种设计思想,主要用于解耦代码,简化依赖管理。其核心是将对象的创建和管理交给容器处理,而非由程序直接硬编码实现。通过IOC,开发者无需手动new对象,而是由框架负责实例化、装配和管理依赖对象。常见应用如Spring框架中的BeanFactory和ApplicationContext,它们实现了依赖注入和动态管理功能,提升了代码的灵活性与可维护性。
195 1
|
XML Java 数据格式
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
|
7月前
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。
|
12月前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
260 69
|
9月前
|
Java 容器 Spring
什么是Spring IOC 和DI ?
IOC : 控制翻转 , 它把传统上由程序代码直接操控的对象的调用权交给容 器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转 移,从程序代码本身转移到了外部容器。 DI : 依赖注入,在我们创建对象的过程中,把对象依赖的属性注入到我们的类中。
|
12月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
12月前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
187 21
|
12月前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
467 12