手写一个及其简易的 IOC 容器

一、前言

采用 xml 的配置方式,仿照 spring 的 ioc 源码实现,手写一个非常简易的 ioc 容器,便于入门 spring 的源码。

二、整体流程介绍

spring ioc 的整体流程,粗略的分为以下几步:

image-20210316192125552

在进行手写之前,需要先明确以下几个功能组件:

三、具体步骤

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 方法省略
}

这三个类的关系如下:

image-20210317193337324

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 的方法

具体依赖关系如下:

image-20210316215712194

具体代码实现:

  • 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();
	}
}

得到结果如下:

image-20210316220954808

至此,自定义的简易 IOC 容器已经实现完成了。

五、IOC 初始化时序图

image-20210317202451573
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值