你真的了解Spring IOC容器吗?

363 阅读7分钟

一、两个核心问题

问题一:Spring为什么要提供这样一个IOC容器呢?

问题二:即使提供一个这样的容器,为什么要称之为IOC呢?

二、理解IOC和DI

2.1 概念

IOC是控制反转的意思,而DI是依赖注入的意思。 这两个概念对于刚刚接触Spring框架的人来说并不那么容易理解。控制反转实际上就是把自己的活交给别人来干,而依赖注入实际上是控制反转的一种实现方式。我知道这么说也还是很抽象,不妨举例子:

假如有这样的业务场景:一个学生要从南京图书馆借书,涉及到学生、图书馆、书籍三个实体类,具体业务逻辑见如下代码:

1. 书籍类
/**
 * @Description :
 * @Author : limq7
 * @Create in : 2021/4/14 15:23
 **/
public abstract class Book {
    private String strAuthor;//作者
    private Date dPublishTime;//出版时间
    public Book(String strAuthor, Date dPublishTime) {
        this.strAuthor = strAuthor;
        this.dPublishTime = dPublishTime;
    }

    public String getStrAuthor() {
        return strAuthor;
    }

    public void setStrAuthor(String strAuthor) {
        this.strAuthor = strAuthor;
    }

    public Date getdPublishTime() {
        return dPublishTime;
    }

    public void setdPublishTime(Date dPublishTime) {
        this.dPublishTime = dPublishTime;
    }
}
  1. 图书馆抽象类
/**
 * @Description :
 * @Author : limq7
 * @Create in : 2021/4/14 15:28
 **/
public class Library {
    //图书馆名称
    private String strLibraryName;
    //书籍列表
    private ArrayList<Book> bookList;
    //获取某一类书籍
    public ArrayList<Book> getBooksByType(String bookType){
        //假装是从数据库中读取的
        ArrayList<Book> myBookList = new ArrayList<>();
        return myBookList;
    }
    //获取某一本书籍
    public Book getBookById(String bookId){
        //假装是从数据库中读取的
        Book book = new Book("雨果",new Date());
        return  book;
    }

}
  1. 南京图书馆类
```//南京图书馆
public class NanjingLibrary extends Library {
    public NanjingLibrary(){
        
    }

    @Override
    public ArrayList<Book> getBooksByType(String bookType) {
        return super.getBooksByType(bookType);
    }

    @Override
    public Book getBookById(String bookId) {
        return super.getBookById(bookId);
    }
}

  1. 学生类
/**
 * @Description :
 * @Author : limq7
 * @Create in : 2021/4/14 15:38
 **/
public class Students {
    //图书馆
    private Library library;
    public Students(){
        //去南京图书馆看书
        library = new NanjingLibrary();
    }
    //借书
    public ArrayList<Book> getBookByBookType(String bookType){
        
        ArrayList<Book> books = library.getBooksByType(bookType);
        
        return books;
    }
}

我们先看学生类,这个类中有一个图书馆类,当每一个学生要借书的时候,首先要new一个学生类,在构造方法中同时也new了一个图书馆类,图书馆这个类每次都是自己创建的,Spring设计者在考虑能不能将这个依赖类(图书馆类)的创建交给别人来完成?

比如采用如下这种方式:

public class Students {
    private Library library;
    public Students(Library library){
        this.library = library;
    }
    //借书
    public ArrayList<Book> getBookByBookType(String bookType){

        ArrayList<Book> books = library.getBooksByType(bookType);

        return books;
    }
}

这种方式的好处在于,只是在Student的构造方法中传递了一个library参数,而这个对象的创建任务交给了别人去做。整个过程中你看不到new关键字的存在,也就是说它自己是不创建自己的依赖对象的。

2.2 依赖注入的三种方式

在上文中,我对Student类的构造方法做了一个改进,其实,这种改进后的方式就是依赖注入的其中一种方式,称之为“构造方法注入”。实际上,依赖注入包括三种方式,分别是构造方法注入、setter方法注入和接口注入

构造方法注入

上文中已经展示过,不再赘述。

setter方法注入
public class Students {
    private Library library;

    public Library getLibrary() {
        return library;
    }

    public void setLibrary(Library library) {
        this.library = library;
    }

    //借书
    public ArrayList<Book> getBookByBookType(String bookType){

        ArrayList<Book> books = library.getBooksByType(bookType);

        return books;
    }
}
接口注入

注:据《Spring揭秘》的作者王福强介绍,接口注入是现在不甚提倡的一种依赖注入方式,基本处于“退役”状态,因为它强制被注入对象实现不必要的接口,带有侵入性。具体实现代码如下

//借书的接口
public interface BorrowBook {
    public ArrayList<Book> getBooksByType(Library library);
}
public class Students implements BorrowBook {
    private Library library;
    //借书
    @Override
    public ArrayList<Book> getBooksByType(Library library) {
        String bookType = "computerScience";
        return library.getBooksByType(bookType);
    }
}

2.3 为什么要使用控制反转?(本文之重点)

还记得本文开头的业务场景吗?即实现某同学去南京图书馆借书的业务功能。如果有一天,业务扩展了,他还想去北京图书馆,那怎么办?很显然,下面这部分代码肯定要做修改,别无他法。

public Students(){`
        `//去南京图书馆看书`
        `library = new NanjingLibrary();`
    }`

那如果我用的是控制反转呢?我只需要再新建一个北京图书馆类,让这个类继承Library这个抽象类即可,根本不需要改动任何代码。这就是控制反转的好处之所在。

3. IOC Service Provider的作用

顾名思义,这是IOC的服务提供者。提供什么服务?IOC需要什么服务?当然是管理对象之间的依赖关系了。你只要记住,这样的服务提供者的目的就是将两个业务对象绑定到一起。这种绑定方式,既可以是简单的几行代码,也可以是我们熟知的xml配置文件。

3.1 一个简单的Service Provider

public static void main(String[] args) {
        Library nanjingLibrary = new NanjingLibrary();
        Students students = new Students();
        ArrayList<Book> books = students.getBooksByType(nanjingLibrary);
        
    }

正如上面的代码,成功地将Students和NanjingLibrary两个绑定在了一起。假如是从北京图书馆借书,直接将NanjingLibrary替换成BeijingLibrary即可,就特别方便。

3.2 常用的Service Provider

A.直接编码方式

IoContainer container = ...;
container.register(Library.class,new NanjingLibrary());
container.register(Students.class,new Students());
Students students = (Students)container.get(Students.class);
NanjingLibrary nanjingLibrary  = (NanjingLibrary)container.get(Library.class); ArrayList<Book> books = students.getBooksByType(nanjingLibrary);

通过为相应的类指定其具体的实例,可以告知IOC容器,当我们需要这种类型的对象实例时,请将容器中注册的、对应的那个具体实例返回给我们。

B.配置文件的方式: 一般就是我们常见的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-2.5.xsd">
    <bean id="Students" class="com.xiwei.xiangxu.test.Students">
        <constructor-arg index="0">
            <ref bean="NanjingLibrary"/>
        </constructor-arg>
    </bean>
    <bean id="NanjingLibrary" class="com.xiwei.xiangxu.test.NanjingLibrary">

    </bean>
</beans>

C.元数据方式:也就是现在的注解方式

4. Spring的IOC容器之BeanFactory

4.1 IOC容器是干嘛的?

Spring的IOC容器确实是一个IOC服务提供者,但它不仅仅是一个服务提供者,不然也不能称之为容器了。它还应该具备AOP支持、线程管理、企业服务继承等功能,当然这些功能都不是本节内容所关注的。下图即能说明Spring的IOC容器跟IOC服务提供者之间的关系。

image.png

4.2 IOC容器种类

Spring IOC容器一共有两种,分别是BeanFactory和ApplicationContext.那么这两种容器有什么区别呢?

特点BeanFactoryApplicationContext
加载策略延迟加载启动时就完成所有初始化
启动速度
系统资源要求较低较高

4.3 BeanFactory和ApplicationContext的关系

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    ....
}
public interface ListableBeanFactory extends BeanFactory {
    ...
}

看到上面这两个接口没?是不是下面这种关系?可见,ApplicationContext是间接继承自BeanFactory,它是构建在BeanFactory基础上的IOC容器

image.png

4.4 初始BeanFactory

//我的职责是:业务对象的注册和对象间依赖关系的绑定,但是我只负责提供抽象的方法,指导别人怎么去做,至于具体怎么做,我不管。
public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
	//给我传一个Class的ID,我给你返回一个这个类的对象实例
    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
	//看容器中是否定义了这个类
    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

4.5 BeanFactory或ApplicationContext创建对象过程

XmlBeanFactory方式
//方式一,创建BeanFactory容器
BeanFactory container =
    new XmlBeanFactory(new ClassPathResource("beans.xml"));
Students students = (Students) container.getBean("Students");
System.out.println(students.getBookByType());
ClassPathXmlApplicationContext方式
//方式二,创建ApplicationContext容器,子类是ClassPathXmlApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Students students = (Students) context.getBean("Students");
System.out.println(students.getBookByType());
FileSystemXmlApplicationContext方式
//方式三,创建ApplicationContext容器,子类是
ApplicationContext context =
    new FileSystemXmlApplicationContext(
    "D:\\MyStudy\\xiweiproject\\xiangxu01\\src\\main\\resources\\beans.xml");//直接路径
Students students = (Students) context.getBean("Students");
System.out.println(students.getBookByType());

beans.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-2.5.xsd">
    <bean id="Students" class="com.xiwei.xiangxu.test.Students">
        <constructor-arg index="0">
            <ref bean="NanjingLibrary"/>
        </constructor-arg>
    </bean>
    <bean id="NanjingLibrary" class="com.xiwei.xiangxu.test.NanjingLibrary">

    </bean>
</beans>

4.6 BeanFactory的对象注册和依赖绑定方式

如果说3.2节中给出的方式是抽象化的,那么下面是具体的实现方式。

直接编码方式
public static void main(String[] args) {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory beanFactory = (BeanFactory) getCompletedBeanFactory(beanRegistry);
        Students students = (Students) beanFactory.getBean("Students");
        System.out.println(students.getBookByType());
    }
    public static BeanFactory getCompletedBeanFactory(BeanDefinitionRegistry registry){
        //第一步,定义两个Bean
        AbstractBeanDefinition studentsDef = new RootBeanDefinition(Students.class);
        AbstractBeanDefinition nanjingLibraryDef = new 	      RootBeanDefinition(NanjingLibrary.class);
        //第二步,将定义注册到容器中
        registry.registerBeanDefinition("Students",studentsDef);
        registry.registerBeanDefinition("NanjingLibrary",nanjingLibraryDef);
        //第三步,完成依赖绑定(通过构造方法绑定)
        ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
        argumentValues.addIndexedArgumentValue(0,nanjingLibraryDef);
        studentsDef.setConstructorArgumentValues(argumentValues);
        return (BeanFactory) registry;
    }

image.png

外部配置文件方式

目前配置文件有两种方式,一种是Properties文件格式,另一种是Xml文件格式,本文仅列举Xml文件格式的方式,也是最常用的一种方式。

public static void main(String[] args) {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory beanFactory = getBeanFactoryByXmlReader(beanRegistry);
        Students students = (Students) beanFactory.getBean("Students");
        System.out.println(students.getBookByType());
    }
    public static BeanFactory getBeanFactoryByXmlReader(BeanDefinitionRegistry registry){
        //读取XML文件内容并将其放入到registry中
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
        reader.loadBeanDefinitions("beans.xml");
        return (BeanFactory) registry;

    }

image.png

全文结束,感谢您的阅读!