一、两个核心问题
问题一: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;
}
}
-
图书馆抽象类
/**
* @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;
}
}
-
南京图书馆类
```//南京图书馆
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);
}
}
-
学生类
/**
* @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服务提供者之间的关系。
4.2 IOC容器种类
Spring IOC容器一共有两种,分别是BeanFactory和ApplicationContext.那么这两种容器有什么区别呢?
特点 | BeanFactory | ApplicationContext |
---|---|---|
加载策略 | 延迟加载 | 启动时就完成所有初始化 |
启动速度 | 快 | 慢 |
系统资源要求 | 较低 | 较高 |
4.3 BeanFactory和ApplicationContext的关系
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
....
}
public interface ListableBeanFactory extends BeanFactory {
...
}
看到上面这两个接口没?是不是下面这种关系?可见,ApplicationContext是间接继承自BeanFactory,它是构建在BeanFactory基础上的IOC容器。
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;
}
外部配置文件方式
目前配置文件有两种方式,一种是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;
}
全文结束,感谢您的阅读!