• 模拟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工厂容器中获取.

  • 相关阅读:
    360浏览器通过.favdb文件恢复前一个登录账号的收藏夹到新账号
    react跨域问题Django配置允许跨域No 'Access-Control-Allow-Origin' header is present on the requested resource',及其解决办法
    Scratch3.0后台开发记录(一)创建Django 后台服务器
    Scratch3.0开发记录(三)添加登录功能之使用fetch配置登录端口
    谈谈绩效考核
    前端面试中该问些什么?
    用cocos2d-html5做的消除类游戏《英雄爱消除》(3)——游戏主界面
    用cocos2d-html5做的消除类游戏《英雄爱消除》(2)——Block设计实现
    用cocos2d-html5做的消除类游戏《英雄爱消除》(1)——系统主菜单
    用cocos2d-html5做的消除类游戏《英雄爱消除》(4)——游戏结束
  • 原文地址:https://www.cnblogs.com/lanblogs/p/15161930.html
Copyright © 2020-2023  润新知