一、前言
采用 xml 的配置方式,仿照 spring 的 ioc 源码实现,手写一个非常简易的 ioc 容器,便于入门 spring 的源码。
二、整体流程介绍
spring ioc 的整体流程,粗略的分为以下几步:
在进行手写之前,需要先明确以下几个功能组件:
三、具体步骤
3.1 搭建项目
-
搭建一个 maven 项目,引入 dom4j 的依赖,pom 文件如下
<dependencies> <!-- https://mvnrepository.com/artifact/dom4j/dom4j --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> </dependencies>
dom4j 的依赖主要是为了解析 xml 配置文件使用
-
编写一个 UserDao 接口和实现类 UserDaoImpl ,代码如下:
public interface UserDao { void sayHello(); } public class UserDaoImpl implements UserDao { @Override public void sayHello() { System.out.println("hello world"); } }
-
编写一个 UserService 接口和实现类 UserServiceImpl,代码如下:
public interface UserService { void sayHello(); } public class UserServiceImpl implements UserService { // 这里依赖了 UserDao private UserDao userDao; // 这个 set 方法必须写,后面需要通过这个方法给 userDao 赋值 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void sayHello() { userDao.sayHello(); } }
-
在resources 目录下新建一个 application.xml 文件,内容如下
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="userService" class="com.liuqiuyi.spring.business.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean id="userDao" class="com.liuqiuyi.spring.business.impl.UserDaoImpl"></bean> </beans>
该 xml 中,我们往 ioc 容器中放入了两个 bean,分别是 userService 和 userDao。其中,userService 依赖 userDao,和上面的 java 代码对应
这个的写法和 spring 的 xml 配置一样
3.2 定义 xml 属性接收对象
需要将 xml 文件中的属性封装为 java bean,这里需要使用三个类来封装:
- PropertyValue 类:用于封装 xml 文件中的 property 标签中的内容
- MutablePropertyValues 类:一个 bean 标签中可以包含多个 property 标签,使用 MutablePropertyValues 来封装多个 property 标签
- BeanDefinition 类:用于封装 bean 标签的内容
三个类的代码如下:
- PropertyValue 类
/**
* 用于封装配置文件中, property 标签
* @author liuqiuyi
* @date 2021/3/10 22:27
*/
public class PropertyValue {
private String name;
private String ref;
public PropertyValue() {
}
public PropertyValue(String name, String ref) {
this.name = name;
this.ref = ref;
}
// get/set 方法省略
}
- MutablePropertyValues 类
/**
* 用于封装多个 property 对象,需要实现迭代器接口
*
* @author liuqiuyi
* @date 2021/3/10 22:30
*/
public class MutablePropertyValues implements Iterable<PropertyValue>{
private final List<PropertyValue> propertyValueList;
public MutablePropertyValues() {
propertyValueList = new ArrayList<>();
}
public MutablePropertyValues(List<PropertyValue> propertyValueList) {
this.propertyValueList = propertyValueList != null ? propertyValueList : new ArrayList<>();
}
/**
* 添加 PropertyValue
*
* @author liuqiuyi
* @date 2021/3/10 22:44
*/
public MutablePropertyValues addPropertyValue(PropertyValue propertyValue) {
if (null == propertyValue) {
return this;
}
// 需要先判断有没有添加过,如果之前添加过,进行替换,如果没有添加过,则直接添加
for (int i = 0; i < propertyValueList.size(); i++) {
PropertyValue value = propertyValueList.get(i);
if (value.getName().equals(propertyValue.getName())) {
// 如果存在,替换
propertyValueList.set(i, propertyValue);
return this;
}
}
propertyValueList.add(propertyValue);
return this;
}
/**
* 这里直接返回 list 的迭代器
*/
@Override
public Iterator<PropertyValue> iterator() {
return propertyValueList.iterator();
}
}
- BeanDefinition 类
/**
* 用于封装 xml 中 bean 标签的信息
*
* @author liuqiuyi
* @date 2021/3/10 22:45
*/
public class BeanDefinition {
private String id;
private String className;
private MutablePropertyValues mutablePropertyValues;
// get/set 方法省略
}
这三个类的关系如下:
3.3 创建接收 BeanDefinition 的注册表类
在读取 xml 文件之前,我们需要考虑一个问题,xml 文件读取出来后,我们封装到了一个个 BeanDefinition 对象中,那么这些对象存储在哪些地方?总不能我每次调用时都读取一次吧,所以这里就需要介绍 BeanDefinitionRegistry 了。
- BeanDefinitionRegistry 是一个接口,定义了一些操作 BeanDefinition 的抽象方法
- SimpleBeanDefinitionRegistry 是具体的实现类,里面实现了具体的操作 BeanDefinition 的方法,这个类可以理解为一个注册表,存储封装好的 BeanDefinition 对象
代码实现如下:
- BeanDefinitionRegistry 接口
/**
* Bean 注册表,用于存储解析出来的 BeanDefinition
*
* @author liuqiuyi
* @date 2021/3/10 22:52
*/
public interface BeanDefinitionRegistry {
/**
* 从注册表中删除指定名称的BeanDefinition对象
*
* @param beanName bean 名称
* @param beanDefinition bean 注册对象
* @author liuqiuyi
* @date 2021/3/10 22:53
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
/**
* 从注册表中删除指定名称的BeanDefinition对象
*
* @param beanName bean 名称
* @throws Exception 找不到 BeanDefinition 对象
* @author liuqiuyi
* @date 2021/3/10 22:54
*/
void removeBeanDefinition(String beanName) throws Exception;
/**
* 根据名称从注册表中获取BeanDefinition对象
*
*
* @param beanName bean 名称
* @return BeanDefinition 对象
* @throws Exception 找不到 BeanDefinition 对象
* @author liuqiuyi
* @date 2021/3/10 22:54
*/
BeanDefinition getBeanDefinition(String beanName) throws Exception;
/**
* 判断 BeanDefinition 是否存在
*
* @param beanName bean 名称
* @return 是否存在, true-存在,false-不存在
* @author liuqiuyi
* @date 2021/3/10 22:55
*/
boolean containsBeanDefinition(String beanName);
/**
* 获取 BeanDefinition 的个数
*
* @return BeanDefinition 的个数
* @author liuqiuyi
* @date 2021/3/10 22:58
*/
int getBeanDefinitionCount();
/**
* 获取 BeanDefinition 的所有名称
*
* @return BeanDefinition 数组
* @author liuqiuyi
* @date 2021/3/10 22:59
*/
String[] getBeanDefinitionNames();
}
- SimpleBeanDefinitionRegistry 类
/**
* 单例 BeanDefinition 注册类
*
* @author liuqiuyi
* @date 2021/3/10 23:08
*/
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
/**
* 用来存储 beanDefinition 的Map容器,这里不考虑并发问题
*/
Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
/**
* 从注册表中删除指定名称的BeanDefinition对象
*
* @param beanName bean 名称
* @param beanDefinition bean 注册对象
* @author liuqiuyi
* @date 2021/3/10 22:53
*/
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}
/**
* 从注册表中删除指定名称的BeanDefinition对象
*
* @param beanName bean 名称
* @throws Exception 找不到 BeanDefinition 对象
* @author liuqiuyi
* @date 2021/3/10 22:54
*/
@Override
public void removeBeanDefinition(String beanName) throws Exception {
beanDefinitionMap.remove(beanName);
}
/**
* 根据名称从注册表中获取BeanDefinition对象
*
* @param beanName bean 名称
* @return BeanDefinition 对象
* @throws Exception 找不到 BeanDefinition 对象
* @author liuqiuyi
* @date 2021/3/10 22:54
*/
@Override
public BeanDefinition getBeanDefinition(String beanName) throws Exception {
return beanDefinitionMap.get(beanName);
}
/**
* 判断 BeanDefinition 是否存在
*
* @param beanName bean 名称
* @return 是否存在, true-存在,false-不存在
* @author liuqiuyi
* @date 2021/3/10 22:55
*/
@Override
public boolean containsBeanDefinition(String beanName) {
return beanDefinitionMap.containsKey(beanName);
}
/**
* 获取 BeanDefinition 的个数
*
* @return BeanDefinition 的个数
* @author liuqiuyi
* @date 2021/3/10 22:58
*/
@Override
public int getBeanDefinitionCount() {
return beanDefinitionMap.size();
}
/**
* 获取 BeanDefinition 的所有名称
*
* @return BeanDefinition 数组
* @author liuqiuyi
* @date 2021/3/10 22:59
*/
@Override
public String[] getBeanDefinitionNames() {
return beanDefinitionMap.keySet().toArray(new String[0]);
}
}
3.4 读取 xml 文件
这一步就开始读取 xml 文件中定义的标签内容,并封装到 BeanDefinition 对象中。主要涉及到 xml 解析的方法:
- BeanDefinitionReader 接口:定义加载 BeanDefinition 的抽象方法
- XmlBeanDefinitionReader 类:从 xml 中读取内容并解析为具体的 BeanDefinition 对象
具体代码如下:
- BeanDefinitionReader 接口
/**
* 解析 xml 配置文件,并封装到 BeanDefinition 中
*
* @author liuqiuyi
* @date 2021/3/11 22:55
*/
public interface BeanDefinitionReader {
/**
* 获取注册表对象
*
* @return 注册表对象
* @author liuqiuyi
* @date 2021/3/11 22:58
*/
BeanDefinitionRegistry getRegistry();
/**
* 加载配置文件并在注册表中进行注册
* @param configLocation 读取的文件地址
* @throws Exception
* @author liuqiuyi
* @date 2021/3/11 22:58
*/
void loadBeanDefinitions(String configLocation) throws Exception;
}
- XmlBeanDefinitionReader 类
/**
* 从 xml 中读取 BeanDefinition
* @author liuqiuyi
* @date 2021/3/11 22:59
*/
public class XmlBeanDefinitionReader implements BeanDefinitionReader{
// 依赖了 BeanDefinitionRegistry 对象,将解析出来的 BeanDefinition 对象放入 beanDefinitionRegistry 中缓存
private BeanDefinitionRegistry beanDefinitionRegistry;
public XmlBeanDefinitionReader() {
this.beanDefinitionRegistry = new SimpleBeanDefinitionRegistry();
}
/**
* 获取注册表对象
*
* @return 注册表对象
* @author liuqiuyi
* @date 2021/3/11 22:58
*/
@Override
public BeanDefinitionRegistry getRegistry() {
return beanDefinitionRegistry;
}
/**
* 加载配置文件并在注册表中进行注册
*
* @param configLocation 读取的文件地址
* @throws Exception
* @author liuqiuyi
* @date 2021/3/11 22:58
*/
@Override
public void loadBeanDefinitions(String configLocation) throws Exception {
if (null == configLocation || "".equals(configLocation.trim())) {
return;
}
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
// 读取 xml 文件的内容
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
// 获取根标签
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements();
// 循环所有的标签
for (Element element : elements) {
// 取出 id 属性和 class 属性
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setId(id);
beanDefinition.setClassName(clazz);
// 取出 property 标签
List<Element> propertyElements = element.elements("property");
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
for (Element propertyElement : propertyElements) {
// 取出 property 标签中的 name 属性和 ref 属性
String name = propertyElement.attributeValue("name");
String ref = propertyElement.attributeValue("ref");
mutablePropertyValues.addPropertyValue(new PropertyValue(name, ref));
}
beanDefinition.setMutablePropertyValues(mutablePropertyValues);
// 将 beanDefinition 对象放到 beanDefinitionRegistry 注册表对象中
beanDefinitionRegistry.registerBeanDefinition(id, beanDefinition);
}
}
}
3.5 创建 bean 对象,并进行依赖注入
这里仿照 spring 中的结构来进行实现
- BeanFactory 接口:spring 中 ioc 的顶层接口,定义了操作 ioc 容器的抽象方法
- ApplicationContext 接口:继承了 BeanFactory 接口,定义了一个容器初始化方法 refresh()
- AbstractApplicationContext 抽象类:实现了 ApplicationContext 接口,重写了 refresh() 方法,并定义了初始化容器的步骤
- ClassPathXmlApplicationContext 类:继承了 AbstractApplicationContext 抽象类,重写了操作容器的方法,并对外提供获取 bean 的方法
具体依赖关系如下:
具体代码实现:
-
BeanFactory 接口
/** * ioc 容器的顶层接口 * * @author liuqiuyi * @date 2021/3/11 23:37 */ public interface BeanFactory { /** * 根据bean对象的名称获取bean对象 * @param name bean 对象的名称 * @return bean 对象 * @throws Exception 找不到 bean 对象 * @author liuqiuyi * @date 2021/3/11 23:38 */ Object getBean(String name) throws Exception; /** * 根据bean对象的名称获取bean对象,并进行类型转换 * * @param name bean 对象的名称 * @param clazz 对象的类型 * @return T 指定的 bean * @throws Exception 找不到 bean 异常 * @author liuqiuyi * @date 2021/3/11 23:38 */ <T> T getBean(String name, Class<? extends T> clazz) throws Exception; }
-
ApplicationContext 接口
/** * ApplicationContext 容器,定义了 refresh() 方法 * @author liuqiuyi * @date 2021/3/11 23:41 */ public interface ApplicationContext extends BeanFactory{ /** * 进行配置文件加载并进行对象创建 * * @throw Exception * @author liuqiuyi * @date 2021/3/11 23:42 */ void refresh() throws Exception; }
-
AbstractApplicationContext 抽象类
/** * 抽象的 ApplicationContext ,定义了初始化的步骤 * @author liuqiuyi * @date 2021/3/13 15:34 */ public abstract class AbstractApplicationContext implements ApplicationContext{ // 用来存放创建出来的 bean 对象,这里不考虑并发问题 protected final Map<String, Object> singletonObjectMap = new HashMap<>(); // 依赖 BeanDefinitionReader 对象,因为需要先将 xml 对象解析为 BeanDefinition 对象 protected BeanDefinitionReader beanDefinitionReader; /** * xml 文件的路径 */ protected String configLocation; /** * 进行配置文件加载并进行对象创建 * * @throw IllegalStateException * @author liuqiuyi * @date 2021/3/11 23:42 */ @Override public void refresh() throws Exception { // 加载 beanDefinition 对象 beanDefinitionReader.loadBeanDefinitions(configLocation); // 执行 bean 的初始化 finishBeanInitialization(); } private void finishBeanInitialization() throws Exception { // 获取 BeanDefinitionRegistry 对象 BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); // 获取所有的 beanDefinitionName String[] beanDefinitionNames = registry.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { // 这里的 getBean 方法用到了模板方法模式,是在子实现类中完成的 getBean(beanDefinitionName); } } }
-
ClassPathXmlApplicationContext 类
public class ClassPathXmlApplicationContext extends AbstractApplicationContext { /** * 在构造方法中完成对 xml 路径的赋值,并执行 refresh() 方法 */ public ClassPathXmlApplicationContext(String configLocation) throws Exception { super.configLocation = configLocation; beanDefinitionReader = new XmlBeanDefinitionReader(); super.refresh(); } /** * 根据bean对象的名称获取bean对象 * * @param name bean 对象的名称 * @return bean 对象 * @throws Exception 找不到 bean 对象 * @author liuqiuyi * @date 2021/3/11 23:38 */ @Override public Object getBean(String name) throws Exception { // 先判断容器中是否存在该 bean 对象,存在就直接返回 Object obj = singletonObjectMap.get(name); if (null != obj) { return obj; } BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry(); // 从 BeanDefinitionRegistry 中根据名称取出 BeanDefinition BeanDefinition beanDefinition = registry.getBeanDefinition(name); if (null == beanDefinition) { return null; } // 通过反射创建bean Class<?> clazz = Class.forName(beanDefinition.getClassName()); Object beanObj = clazz.newInstance(); // 获取依赖信息 MutablePropertyValues mutablePropertyValues = beanDefinition.getMutablePropertyValues(); for (PropertyValue mutablePropertyValue : mutablePropertyValues) { String propertyValueName = mutablePropertyValue.getName(); String ref = mutablePropertyValue.getRef(); if (null != ref && !"".equals(ref.trim())) { // 获取需要注入的 bean,这里类似于递归 Object bean = getBean(ref); // 组装 beanObj 中 setBean 的方法名称 String setMethodName = buildSetMethodName(propertyValueName); // 获取 beanObj 中 setBean 的方法,并进行设值 Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.getName().equals(setMethodName)) { method.invoke(beanObj, bean); } } } } // 将创建出来的对象放入 ioc 容器中 singletonObjectMap.put(name, beanObj); return beanObj; } /** * 根据bean对象的名称获取bean对象,并进行类型转换 * * @param name bean 对象的名称 * @param clazz 对象的类型 * @return T 指定的 bean * @throws Exception 找不到 bean 异常 * @author liuqiuyi * @date 2021/3/11 23:38 */ @Override public <T> T getBean(String name, Class<? extends T> clazz) throws Exception { Object bean = getBean(name); if (null == bean) { return null; } return clazz.cast(bean); } /** * 将方法名称首字母大写,并加上 set 字符串 * @author liuqiuyi * @date 2021/3/13 16:06 */ private String buildSetMethodName(String name) { if (null == name || "".equals(name.trim())) { return ""; } String upperString = name.substring(0, 1).toUpperCase() + name.substring(1); return "set" + upperString; } }
到这里整个 ioc 容器就已经全部实现完成了,下面进行测试
四、测试自定义的 IOC 容器
编写测试方法,从 IOC 容器中获取 UserServiceImpl 类,并调用类中的方法
/**
* 自定义的 ioc 容器测试
*
* @author liuqiuyi
* @date 2021/3/13 16:12
*/
public class SpringIocTest {
public static void main(String[] args) throws Exception {
// 创建 IOC 容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
// 从容器中获取 UserServiceImpl 类
UserServiceImpl userService = applicationContext.getBean("userService", UserServiceImpl.class);
// 调用类中的方法
userService.sayHello();
}
}
得到结果如下:
至此,自定义的简易 IOC 容器已经实现完成了。