实现BeanFactory

首先,IOC是个容器,可以帮我们管理对象的整个声明周期。

要实现IOC(控制反转),其实就是要实现DI(注入依赖)

DI其实就是对spring管理的对象进行赋值

那么首先要能够管理,其次要能够赋值

想要获取IOC管理的对象,首先要获得IOC。

  1. BeanFactory ,这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用

  2. ApplicationContext这是BeanFactory的子接口,,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用ApplicationContext 而不是底层的 BeanFactory。注意,ApplicationContextBeanFactory的子接口,在接口中只有声明,没有实现。

    ClassPathXmlApplicationContext是ApplicationContext的实现类,通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 。

因此我们要先实现BeanFactoryClassPathXmlApplicationContext

实现简单版本的bean

在spring中,bean的配置和使用过程为 :

先配置bean

1
<bean id="helloworld" class="com.atguigu.spring.bean.HelloWorld"></bean>

再获取和使用bean

1
2
3
4
5
6
7
8
9
//获取IOC容器 (通过类路径获取) 
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取IOC容器中的bean
//1. 可以根据name(xml中的id)获取。由于根据name获取的不知道类型,因此做一个 (HelloWorld)强转
HelloWorld helloworld = (HelloWorld) ac.getBean("helloworld");
//2. 根据类型获取。这里会报错,因为有两个Hellworld类型的bean
HelloWorld helloworld = ac.getBean(HelloWorld.class);
//3. 根据id和类型获取
HelloWorld helloworld = ac.getBean("helloworld", HelloWorld.class);

要做到这种效果,在我们的minispring中,先要实现一个BeanDefinition类和一个 ClassPathXmlApplicationContext类

1
2
3
4
5
6
7
8
9
10
11
public class BeanDefinition {
private String id;
private String className;

public BeanDefinition(String id,String className){
this.id = id;
this.className = className;
}
//省略setter,getter
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.minis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* 在实际中的spring框架作用中,ClassPathXmlApplicationContext是一个接口的实现类,通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
* 原用法 :
* 1. 获取IOC容器 (通过类路径获取),ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
* 2. 获取IOC容器中的bean,HelloWorld helloworld = ac.getBean("helloworld"); (通过名字获取)
* 现在来初步实现这个类。
* */
public class ClassPathXmlApplicationContext {
/**
* 两个属性 ,beanDefinitions和 singletons
* beanDefinitions :是一个泛型列表,里面装了所有的bean
* singletons :是一个哈希表,可以通过id找bean
* */
private List<BeanDefinition> beanDefinitions = new ArrayList<>();
private Map<String, Object> singletons = new HashMap<>();
public ClassPathXmlApplicationContext(String fileName){
this.redXml(fileName) ;
this.InstanceBeans() ;
}
/**
*通过传入的文件路径,也就是 XML 文件的全路径名,来获取 XML 内的信息
* */
private void redXml(String fileName) {
SAXReader saxReader = new SAXReader();
try {
//注意 : 类加载器获取资源时 此处的filepath 需要放在resource目录里面(手动创建并标识类型为Resource root)
URL xmlPath = this.getClass().getClassLoader().getResource(fileName);
Document document = saxReader.read(xmlPath);
Element rootElement = document.getRootElement();
//对配置文件中的每一个<bean>,进行处理
for (Element element : (List<Element>) rootElement.elements()) {
//获取Bean的基本信息
String beanID = element.attributeValue("id");
String beanClassName = element.attributeValue("class");
BeanDefinition beanDefinition = new BeanDefinition(beanID,beanClassName);
//将Bean的定义存放到beanDefinitions
beanDefinitions.add(beanDefinition);
}
}
}
/**
* 通过反射创建bean实例,并且存在singletons里面。
* */
private void InstanceBeans() {
for (BeanDefinition beanDefinition : beanDefinitions) {
try {
singletons.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).newInstance());
}
}
}

//这是对外的一个方法,让外部程序从容器中获取Bean实例,会逐步演化成核心方法
public Object getBean(String beanName) {
return singletons.get(beanName);
}
}

以上,在填充完毕 redXmlInstanceBeans两个方法后,就初步实现了IOC

注意,SAXReader需要导入以下jar包

1
2
3
4
5
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

目前的 ClassPathXmlApplicationContext 兼具了 BeanFactory 的功能,它通过 singletonsbeanDefinitions 初步实现了 Bean 的管理,其实这也是 Spring 本身的做法。

对上述代码解耦

上面的 ClassPathXmlApplicationContext承担了太多责任,需要解耦。

将这个类解耦,其分解为两个部分 :

  1. 一是提出一个最基础的核心容器
  2. 把 XML 这些外部配置信息的访问单独剥离出去

主要是一个类只做一件事的思想。

现在我们只有 XML 这一种方式,但是之后还有可能配置到 Web 或数据库文件里,拆解出去之后也便于扩展。

以spring目录结构为范本,定义项目的代码结构 :

1
2
3
4
com.minis.beans;
com.minis.context;
com.minis.core;
com.minis.test;

解耦后的代码目录:

定义BeansException

BeansException是一个异常处理类 :

1
2
3
4
5
6
public class BeansException extends Exception {
public BeansException(String msg) {
//直接调用父类(Exception)处理并抛出异常
super(msg);
}
}

定义BeanFactory

BeanFactory 这个接口是拆解出的一个基础容器,,先让这个接口拥有两个特性:一是获取一个 Bean(getBean),二是注册一个 BeanDefinition(registerBeanDefinition)。后面在XmlBeanDefinitionReader中实现这个接口

1
2
3
4
public interface BeanFactory {
Object getBean(String beanName) throws BeansException;
void registerBeanDefinition(BeanDefinition beanDefinition);
}

定义 Resource

把外部的配置信息都当成 Resource(资源)来进行抽象

1
2
public interface Resource extends Iterator<Object> {
}

定义 ClassPathXmlResource

操作 XML 文件格式都是 dom4j 帮我们做的,这里主要就是解析XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ClassPathXmlResource implements Resource{
Document document;
Element rootElement;
Iterator<Element> elementIterator;
public ClassPathXmlResource(String fileName) {
SAXReader saxReader = new SAXReader();
URL xmlPath = this.getClass().getClassLoader().getResource(fileName);
//将配置文件装载进来,生成一个迭代器,可以用于遍历
try {
this.document = saxReader.read(xmlPath);
this.rootElement = document.getRootElement();
this.elementIterator = this.rootElement.elementIterator();
}
}
public boolean hasNext() {
return this.elementIterator.hasNext();
}
public Object next() {
return this.elementIterator.next();
}
}

XmlBeanDefinitionReader

这里就是 ClassPathXmlApplicationContext 中有关 BeanDefinition 实例化以及加载到内存中的相关内容提取出来了。

即将解析好的XML转换成我们需要的BeanDefinition,加载到BeanFactory

这里会先尝试直接拿bean实例,拿不到再根据bean的定义注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class SimpleBeanFactory implements BeanFactory{
private List<BeanDefinition> beanDefinitions = new ArrayList<>();
private List<String> beanNames = new ArrayList<>();
private Map<String, Object> singletons = new HashMap<>();
public SimpleBeanFactory() {
}

//getBean,容器的核心方法
public Object getBean(String beanName) throws BeansException {
//先尝试直接拿Bean实例
Object singleton = singletons.get(beanName);
//如果此时还没有这个Bean的实例,则获取它的定义来创建实例
if (singleton == null) {
int i = beanNames.indexOf(beanName);
if (i == -1) {
throw new BeansException();
}
else {
//获取Bean的定义
BeanDefinition beanDefinition = beanDefinitions.get(i);
try {
singleton = Class.forName(beanDefinition.getClassName()).newInstance();
}
//注册Bean实例
singletons.put(beanDefinition.getId(), singleton);
}
}
return singleton;
}

public void registerBeanDefinition(BeanDefinition beanDefinition) {
this.beanDefinitions.add(beanDefinition);
this.beanNames.add(beanDefinition.getId());
}
}

ClassPathXmlApplicationContext

那么现在的ClassPathXmlApplicationContext一部分交给了 BeanFactory,一部分又交给了 Resource 和 Reader。

现在只需要做三件事 :

  1. 解析 XML 文件中的内容。
  2. 加载解析的内容,构建 BeanDefinition。
  3. 读取 BeanDefinition 的配置信息,实例化 Bean,然后把它注入到 BeanFactory 容器中。

接下来看看它的实现吧 !

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.minis.context;

import com.minis.beans.*;
import com.minis.core.ClassPathXmlResource;
import com.minis.core.Resource;

/**
* ClassPathXmlApplicationContext是根据name获取bean,实现了beanFactory接口
* 实例域为BeanFactory
* 工作流程为 : 先解析XML文件中的内容,再进行实例化及加载
* */
public class ClassPathXmlApplicationContext implements BeanFactory {
BeanFactory beanFactory;
//context负责整合容器的启动过程,读外部配置,解析Bean定义,创建BeanFactory
public ClassPathXmlApplicationContext(String fileName) {
//1. 解析 XML 文件中的内容。
Resource resource = new ClassPathXmlResource(fileName);
//2. 先定义一个BeanFactory变量
BeanFactory beanFactory = new SimpleBeanFactory();
//3.XmlBeanDefinitionReader会将XML 文件中的内容转化成我们需要的BeanDefinition并注册
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
//搞完咯
this.beanFactory = beanFactory;
}
//context再对外提供一个getBean,底下就是调用的BeanFactory对应的方法
public Object getBean(String beanName) throws BeansException {
return this.beanFactory.getBean(beanName);
}
//注入到 BeanFactory 容器
public void registerBeanDefinition(BeanDefinition beanDefinition) {
this.beanFactory.registerBeanDefinition(beanDefinition);
}
}

小结

整体流程图 :