模拟Spring手撕一个简单的IOC容器

在Spring中, IOC是一个容器, 主要负责对托管至Spring的Bean进行创建及保存. Spring IOC创建Bean可分为单例和原型两种. 由于篇幅所限, 本篇中的简易版IOC只实现对单例Bean的管理.

图片

计思路

定位Bean

项目中的代码成千上万, Spring并不能准确的知道哪些Bean是需要由IOC容器创建并管理. 因此需要通过配置的方式将需要被管理的Bean告知Spring.

XML配置

早期的Spring, 被管理的Bean需要XML文件中进行声明.

<bean id="userService" class="com.demo.UserService"></bean>

注解配置

由于XML配置过于繁琐, 可读性较差. 为简化配置Spring推出了基于注解的配置. 在代码中对需要被管理的Bean添加指定注解即可.

@Component
public class UserService {
}

为了提升性能, 需要告知Spring哪些目录下有需要被加载的Bean, Spring会扫描这些目录并将含有注解的Bean进行管理

<component-scan package="com.demo" />

解析Bean

确定需要被管理的Bean后, 就要对Bean进行解析. 由于有有XML和注解两种配置方式, 因此IOC容器需要分别解析XML配置及注解配置的Bean. 主要针对以下几项进行解析:

  • 类型: 后续通过反射创建Bean时使用

  • 名称: 保存时作为Bean的别名使用

  • 属性: 依赖注入时使用

注册Bean

将解析得到的Bean描述信息注册到指定容器中.

创建Bean

将已注册到容器中的Bean依次实例化, 并统一保存. 根据Bean描述信息中的类型(Class)通过反射创建Bean的实例.

获取Bean

对外提供获取Bean的接口, 如果Bean不存在, 自动创建保存后返回.

接口与组件

BeanDefinition

BEAN描述类, 用来保存BEAN的基本信息, 包括名称, 类型, 属性等.

// BEAN描述信息
public class BeanDefinition {

   // 名称
   private String name;

   // CLASS
   private Class<?> clazz;

   // 通过名称和CLASS实例化, 默认使用CLASS名作为BEAN的名称
   public BeanDefinition(String name, Class<?> clazz) {
       this.clazz clazz;
       this.name BeanUtil.isEmpty(name) BeanUtil.getName(clazz) : name;
  }

   // Getter & Setter
   // ...

}

BeanFactory

BEAN工厂, IOC容器的核心类. 负责统一创建及管理BEAN(包括描述信息和实例), 对外提供获取BEAN的接口. 由IOC容器管理的BEAN的所有操作都由BeanFactory完成.

// BEAN工厂, 提供BEAN的创建及获取
public class BeanFactory {

   // 保存所有BEAN的信息. K: BEAN名称, V: BEAN描述信息
   private final Map<String, BeanDefinitionbeanDefinitionMap new ConcurrentHashMap<String, BeanDefinition>();

   // 保存所有BEAN的实例化对象. K: BEAN名称, V: BEAN实例化对象
   private final Map<String, ObjectbeanObjectMap new ConcurrentHashMap<String, Object>();

   // 注册BEAN
   public void registerBean(BeanDefinition bean) {
       beanDefinitionMap.put(bean.getName(), bean);
  }

   // 获取所有已注册BEAN的名称
   public Set<StringgetBeanNames() {
       return this.beanDefinitionMap.keySet();
  }

   // 根据名称获取BEAN的类型
   public Class<?> getBeanType(String name) {
       return this.beanDefinitionMap.get(name).getClazz();
  }

   // 根据名称获取BEAN的实例
   @SuppressWarnings("unchecked")
   public <TgetBean(String name) throws Exception {
       return null;
  }

   // 实例化BEAN
   public void instanceBean() throws Exception {
  }

}

ElementParser

配置文件节点解析器接口

// 节点解析器接口
public interface ElementParser {

   // 解析节点
   public void parse(Element ele, BeanFactory factory) throws Exception;

}

BeanElementParser

解析XML配置文件中的节点, 将解析到的BEAN信息封装成BeanDefinition, 注册至BeanFactory中.

// Bean节点解析器,解析XML配置文件中的<bean>节点
public class BeanElementParser implements ElementParser {

   // 解析<bean>节点
   @SuppressWarnings("unchecked")
   @Override
   public void parse(Element ele, BeanFactory factory) throws Exception {
       // 解析<bean>节点, 将Bean描述信息封装
       BeanDefinition bd null;
       // 向BEAN工厂注册Bean
       factory.registerBean(bd);
  }

}

ComponentScanElementParser

解析XML配置文件中的节点, 获取package属性中的包目录, 扫描目录下的类并解析, 将需要被管理的BEAN信息封装成BeanDefinition, 注册至BeanFactory中.

// <component-scan>节点解析器
public class ComponentScanElementParser implements ElementParser {

   // 解析<component-scan>节点
   @Override
   public void parse(Element ele, BeanFactory factory) throws Exception {
       // 扫描package属性中定义的目录
       String basePackage ele.getAttributeValue("package");
       // 解析目录下的Bean并注册至BEAN工厂
       BeanDefinition bd null;
       factory.registerBean(bd);
  }

}

Component

如果BEAN需要被Spring管理, 在类中添加该注解. 含有该注解的类在被ComponentScanElementParser扫描后会交由IOC容器管理.

// 托管Bean声明注解
@Documented
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
   String value() default "";
}

ApplicationContext

应用程序上下文, 提供IOC容器初始化入口及统一获取BEAN的接口.

// 应用程序上下文
public class ApplicationContext {

   // 配置文件路径
   private String configLocation;

   // BEAN工厂
   private BeanFactory beanFactory;

   // 节点解析器容器
   private Map<String, ElementParserparserMap new HashMap<String, ElementParser>();

   // 无参构造
   public ApplicationContext() {
  }

   // 根据配置文件路径实例化上下文
   public ApplicationContext(String configLocation) {
       this.setConfigLocation(configLocation);
  }

   // 设置配置文件路径
   public void setConfigLocation(String configLocation) {
       this.configLocation configLocation;
  }

   // 初始化
   public void init() throws Exception {
       this.init(null);
  }

   // 根据Servlet上下文初始化
   public void init(ServletContext context) throws Exception {
       // 创建BEAN工厂
       // 初始化配置文件节点解析器
       // 解析配置文件中定义的BEAN
       // 通知BEAN工厂实例化已注册的BEAN
  }

   // 获取名称获取BEAN实例
   public <TgetBean(String beanName) throws Exception {
       return this.beanFactory.getBean(beanName);
  }

   // 获取BEAN工厂
   public BeanFactory getBeanFactory() {
       return beanFactory;
  }

}

流程

  1. 实例化ApplicationContext并设置配置文件路径

  2. 调用init方法初始化IOC容器

  3. 创建BeanFactory

  4. 初始化, 节点解析器

  5. 读取配置文件并解析配置文件中定义的BEAN

  6. 各节点解析器解析配置文件并向BeanFactory中注册Bean

  7. 调用BeanFactory的instanceBean方法实例化已注册的Bean

  8. IOC容器初始化完成

具体实现

IOC容器初始化

ApplicationContext作为IOC容器的初始化入口, init方法中需要初始化基础组件(节点解析器, BEAN工厂)并完成BEAN的注册及实例化.

  • 初始化操作基础流程

// 根据Servlet上下文初始化
public void init(ServletContext context) throws Exception {
   
   // 创建BEAN工厂
   createBeanFactory();
   // 初始化配置文件节点解析器
   initElementParser();
   // 解析配置文件中定义的BEAN
   parseBean();
   // 通知BEAN工厂实例化已注册的BEAN
   this.beanFactory.instanceBean();

}
  • 创建BEAN工厂

// 创建BEAN工厂
private void createBeanFactory() {
   this.beanFactory new BeanFactory();
}
  • 初始化配置文件节点解析器

// 初始化配置文件节点解析器, KEY为节点的名称
// 解析文件时根据节点的名称就可以找到对应的解析器
private void initElementParser() {
   parserMap.put("bean", new BeanElementParser());
   parserMap.put("component-scan", new ComponentScanElementParser());
}
  • 解析配置文件中定义的BEAN

// 解析配置文件中定义的BEAN
@SuppressWarnings("unchecked")
private void parseBean() throws Exception {

   // 开始加载配置文件(JDom解析XML)
   String classpath getClass().getClassLoader().getResource("").getPath();
   Document doc new SAXBuilder().build(new File(classpath, this.configLocation));

   // 获取根节点(<beans>)下所有子节点并依次解析
   List<ElementelementList doc.getRootElement().getChildren();
   for (Element ele : elementList) {

       // 节点名称
       String eleName ele.getName();
       // 无对应的节点解析器
       if (!this.parserMap.containsKey(eleName)) {
           throw new RuntimeException("节点[" eleName "]配置错误,无法解析");
      }

       // 根据节点名称找到对应的节点解析器解析节点
       this.parserMap.get(eleName).parse(ele, this.beanFactory);

  }

}

节点解析器解析节点

  • 节点解析器

// Bean节点解析器,解析XML配置文件中的<bean>节点
public class BeanElementParser implements ElementParser {

   // 解析<bean>节点
   @SuppressWarnings("unchecked")
   @Override
   public void parse(Element ele, BeanFactory factory) throws Exception {
       
       // <bean>节点中的id和class属性
       String cls ele.getAttributeValue("class");
       String id ele.getAttributeValue("id");
       
       // 类型
       Class<?> clazz Class.forName(cls);
       
       // 封装成类描述信息
       BeanDefinition bd new BeanDefinition(id, clazz);

       // 向BEAN工厂注册Bean
       factory.registerBean(bd);

  }

}
  • 节点解析器

// <component-scan>节点解析器
public class ComponentScanElementParser implements ElementParser {

   // 解析<component-scan>节点
   @Override
   public void parse(Element ele, BeanFactory factory) throws Exception {

       // package属性(扫描目录)
       String basePackage ele.getAttributeValue("package");
       if (basePackage == null) {
           throw new RuntimeException("<component-scan>必须配置package属性");
      }

       // 获取扫描目录绝对路径
       String baseDir getClass().getClassLoader().getResource(basePackage.replace('.', '/')).getPath();

       // 扫描目录,获取目录下的所有类文件
       for (File file : new File(baseDir).listFiles()) {

           // 获取CLASS的路径(包目录+类名)并加载CLASS
           String classPath basePackage "." file.getName().replaceAll("\.class", "");
           Class<?> clazz Class.forName(classPath);

           // 只处理含有@Component的BEAN
           if (!clazz.isAnnotationPresent(Component.class)) {
               continue;
          }

           // 获取类的@Component注解
           Component clazz.getAnnotation(Component.class);
           // 封装成类描述信息
           BeanDefinition bd new BeanDefinition(c.value(), clazz);

           // 向BEAN工厂注册Bean
           factory.registerBean(bd);

      }

  }

}

Bean工厂注册Bean并实例化

// BEAN工厂, 提供BEAN的创建及获取
public class BeanFactory {

   // BEAN描述信息容器, 保存所有BEAN的信息. K: BEAN名称, V: BEAN描述信息
   private final Map<String, BeanDefinitionbeanDefinitionMap new ConcurrentHashMap<String, BeanDefinition>();

   // BEAN实例容器, 保存所有BEAN的实例化对象. K: BEAN名称, V: BEAN实例化对象
   private final Map<String, ObjectbeanObjectMap new ConcurrentHashMap<String, Object>();

   // 注册BEAN
   public void registerBean(BeanDefinition bean) {
       beanDefinitionMap.put(bean.getName(), bean);
  }

   // 获取所有已注册BEAN的名称
   public Set<StringgetBeanNames() {
       return this.beanDefinitionMap.keySet();
  }

   // 根据名称获取BEAN的类型
   public Class<?> getBeanType(String name) {
       return this.beanDefinitionMap.get(name).getClazz();
  }

   // 根据名称获取BEAN的实例
   @SuppressWarnings("unchecked")
   public <TgetBean(String name) throws Exception {

       // 根据名称从容器获取BEAN
       Object bean this.beanObjectMap.get(name);

       // 容器中存在直接返回
       if (bean != null) {
           return (T) bean;
      }

       // 未获取到时自动创建
       // 查看缓存中是否有BEAN描述
       if (!this.beanDefinitionMap.containsKey(name)) {
           throw new RuntimeException("未定义BEAN[" name "]");
      }

       // 存在BEAN描述时根据描述信息实例化BEAN
       BeanDefinition beanDef this.beanDefinitionMap.get(name);
       bean beanDef.getClazz().newInstance();

       // 将BEAN实例化保存至容器
       this.beanObjectMap.put(name, bean);

       // 返回新创建BEAN
       return (T) bean;

  }

   // 实例化BEAN
   public void instanceBean() throws Exception {

       // 根据缓存的BEAN描述信息依次创建BEAN
       for (String beanName : this.beanDefinitionMap.keySet()) {
           getBean(beanName);
      }

  }

}

测试

  • 创建BEAN

// 通过注解声明BEAN
@Component
public class ServiceX {

   public void test() {
       System.out.println("ServiceX.test start...");
  }

}

// 通过配置文件配置BEAN
public class ManagerX {

   public void test() {
       System.out.println("ManagerX.test start...");
  }

}
  • 创建XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans>

   <!-- 配置BEAN所在目录, IOC容器会扫描该目录, 加载含有@Component注解的Bean -->
<component-scan package="com.demo.service" />

   <!-- 配置BEAN -->
<bean id="managerX" class="com.demo.ManagerX"></bean>

</beans>
  • 创建测试类

// IOC测试类
public class Test {

   // 测试IOC容器
   public static void main(String[] args) throws Exception {

       // 实例化应用上下文并设置配置文件路径
       ApplicationContext context new ApplicationContext("context.xml");
       // 初始化上下文(IOC容器)
       context.init();

       // 从IOC容器中获取BEAN并执行
       ServiceX serviceX context.getBean("serviceX");
       ManagerX managerx context.getBean("managerX");
       serviceX.test();
       managerx.test();

  }

}
  • 运行

从IOC容器中获取BEAN并执行后输出如下结果: BEAN的实例对象已经保存在IOC容器中.

ServiceX.test start...
ManagerX.test start...

IOC容器未找到对应的BEAN(未配置或配置错误)时会抛出异常: BEAN的实例对象没有在IOC容器中.

Exception in thread "main" java.lang.RuntimeException: 未定义BEAN[managerX1]

总结

Spring IOC实现对BEAN控制权的反转, 将Bean统一将由IOC容器创建及管理. 只有IOC容器统一管理Bean后才能完成对各BEAN依赖属性的自动注入.

Spring的IOC容器通过配置文件获取到需要被管理的Bean后, 将Bean的信息解析封装并注册至Bean工厂(Bean工厂缓存Bean描述信息). 所有Bean注册完成后依次对Bean进行实例化并保存在Bean工厂的容器中, 以此来实现对Bean的统一管理. 当需要获取Bean时, 统一从Bean工厂容器中获取.

原文地址:https://www.cnblogs.com/lanblogs/p/15161930.html