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,配置文件如下:

                复制代码

在上面的配置中我们可以看到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

                                         复制代码

测试代码如下:

@Testpublic 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.png
image.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

                

测试代码:

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

                

测试代码:

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的配置

    

import

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


参考:

Spring之IoC理论

你可能感兴趣的:(Spring之IoC理论)