五分钟,手撸一个Spring容器!

Posted 三分恶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五分钟,手撸一个Spring容器!相关的知识,希望对你有一定的参考价值。

大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌。

这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开Spring神秘的面纱!

从什么是IOC开始?

Spring——春天,Java编程世界的春天是由一位音乐家——Rod Johnson带来的。

Rod Johnson先后编写了两本巨著《Expert One-on-One J2EE Design and Development》、《Expert One-on-One J2EE Development without EJB》,拉起了挑战正统Java EE框架EJB的大旗。

Rod Johnson两大著作-来自百度百科

Rod Johnson不仅是一名旗手,更是开发了Spring这一轻量级框架,像一名勇敢的龙骑兵一样,对EJB发动了冲锋,并最终战胜了EJB,让Spring成为Java EE事实上的标准。

Spring Logo

Spring的两大内核分别是IOC和AOP,其中最最核心的是IOC。

所谓的IOC(控制反转):就是由容器来负责控制对象的生命周期和对象间的关系。以前是我们想要什么,就自己创建什么,现在是我们需要什么,容器就给我们送来什么。

引入IOC之前和引入IOC之后

也就是说,控制对象生命周期的不再是引用它的对象,而是容器。对具体对象,以前是它控制其它对象,现在所有对象都被容器控制,所以这就叫控制反转

控制反转示意图

也许你还听到另外一个概念DI(依赖注入),它指的是容器在实例化对象的时候把它依赖的类注入给它,我们也可以认为,DI是IOC的补充和实现。

工厂和Spring容器

Spring是一个成熟的框架,为了满足扩展性、实现各种功能,所以它的实现如同枝节交错的大树一样,现在让我们把视线从Spring本身移开,来看看一个萌芽版的Spring容器怎么实现。

Spring的IOC本质就是一个大工厂,我们想想一个工厂是怎么运行的呢?

工厂运行
  • 生产产品:一个工厂最核心的功能就是生产产品。在Spring里,不用Bean自己来实例化,而是交给Spring,应该怎么实现呢?——答案毫无疑问,反射

    那么这个厂子的生产管理是怎么做的?你应该也知道——工厂模式

  • 库存产品:工厂一般都是有库房的,用来库存产品,毕竟生产的产品不能立马就拉走。Spring我们都知道是一个容器,这个容器里存的就是对象,不能每次来取对象,都得现场来反射创建对象,得把创建出的对象存起来。

  • 订单处理:还有最重要的一点,工厂根据什么来提供产品呢?订单。这些订单可能五花八门,有线上签签的、有到工厂签的、还有工厂销售上门签的……最后经过处理,指导工厂的出货。

    在Spring里,也有这样的订单,它就是我们bean的定义和依赖关系,可以是xml形式,也可以是我们最熟悉的注解形式。

  • 那对应我们的萌芽版的Spring容器是什么样的呢?

    mini版本Spring IOC
    订单:Bean定义

    Bean可以通过一个配置文件定义,我们会把它解析成一个类型。

    Bean定义
  • beans.properties

    为了偷懒,这里直接用了最方便解析的properties,用一个<key,value>类型的配置来代表Bean的定义,其中key是beanName,value是class

    userDao:cn.fighter3.bean.UserDao
  • BeanDefinition.java

    bean定义类,配置文件中bean定义对应的实体

    public class BeanDefinition 

        private String beanName;

        private Class beanClass;
         //省略getter、setter  
        
  • 获取订单:资源加载

    接下订单之后,就要由销售向生产部门交接,让生产部门知道商品的规格、数量之类。

    资源加载器,就是来完成这个工作的,由它来完成配置文件中配置的加载。

    public class ResourceLoader 

        public static Map<String, BeanDefinition> getResource() 
            Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(16);
            Properties properties = new Properties();
            try 
                InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties");
                properties.load(inputStream);
                Iterator<String> it = properties.stringPropertyNames().iterator();
                while (it.hasNext()) 
                    String key = it.next();
                    String className = properties.getProperty(key);
                    BeanDefinition beanDefinition = new BeanDefinition();
                    beanDefinition.setBeanName(key);
                    Class clazz = Class.forName(className);
                    beanDefinition.setBeanClass(clazz);
                    beanDefinitionMap.put(key, beanDefinition);
                
                inputStream.close();
             catch (IOException | ClassNotFoundException e) 
                e.printStackTrace();
            
            return beanDefinitionMap;
        


    订单分配:Bean注册

    对象注册器,这里用于单例bean的缓存,我们大幅简化,默认所有bean都是单例的。可以看到所谓单例注册,也很简单,不过是往HashMap里存对象。

    public class BeanRegister 

        //单例Bean缓存
        private Map<String, Object> singletonMap = new HashMap<>(32);

        /**
         * 获取单例Bean
         *
         * @param beanName bean名称
         * @return
         */

        public Object getSingletonBean(String beanName) 
            return singletonMap.get(beanName);
        

        /**
         * 注册单例bean
         *
         * @param beanName
         * @param bean
         */

        public void registerSingletonBean(String beanName, Object bean) 
            if (singletonMap.containsKey(beanName)) 
                return;
            
            singletonMap.put(beanName, bean);
        


    生产车间:对象工厂

    好了,到了我们最关键的生产部门了,在工厂里,生产产品的是车间,在IOC容器里,生产对象的是BeanFactory。

    BeanFactory
  • 对象工厂,我们最核心的一个类,在它初始化的时候,创建了bean注册器,完成了资源的加载。

  • 获取bean的时候,先从单例缓存中取,如果没有取到,就创建并注册一个bean

    public class BeanFactory 

        private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

        private BeanRegister beanRegister;

        public BeanFactory() 
            //创建bean注册器
            beanRegister = new BeanRegister();
            //加载资源
            this.beanDefinitionMap = new ResourceLoader().getResource();
        

        /**
         * 获取bean
         *
         * @param beanName bean名称
         * @return
         */

        public Object getBean(String beanName) 
            //从bean缓存中取
            Object bean = beanRegister.getSingletonBean(beanName);
            if (bean != null
                return bean;
            
            //根据bean定义,创建bean
            return createBean(beanDefinitionMap.get(beanName));
        

        /**
         * 创建Bean
         *
         * @param beanDefinition bean定义
         * @return
         */

        private Object createBean(BeanDefinition beanDefinition) 
            try 
                Object bean = beanDefinition.getBeanClass().newInstance();
                //缓存bean
                beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean);
                return bean;
             catch (InstantiationException | IllegalAccessException e) 
                e.printStackTrace();
            
            return null;
        

  • 生产销售:测试
  • UserDao.java

    我们的Bean类,很简单

    public class UserDao 

        public void queryUserInfo()
            System.out.println("A good man.");
        

  • 单元测试

    public class ApiTest 
        @Test
        public void test_BeanFactory() 
            //1.创建bean工厂(同时完成了加载资源、创建注册单例bean注册器的操作)
            BeanFactory beanFactory = new BeanFactory();

            //2.第一次获取bean(通过反射创建bean,缓存bean)
            UserDao userDao1 = (UserDao) beanFactory.getBean("userDao");
            userDao1.queryUserInfo();

            //3.第二次获取bean(从缓存中获取bean)
            UserDao userDao2 = (UserDao) beanFactory.getBean("userDao");
            userDao2.queryUserInfo();
        

  • 运行结果

    A good man.
    A good man.
  • 至此,我们一个萌芽版的Spring容器就完成了。

    考虑一下,它有哪些不足呢?是否还可以抽象、扩展、解耦……

    细细想想这些东西,你是不是对真正的Spring IOC容器为何如此复杂,有所理解了呢?




    参考:

  • [1]. 《Spring揭秘》

  • [2].小傅哥 《手撸Spring》

  • [3].《精通Spring4.X企业应用开发实战》

  • 建了一个技术交流群,里面大厂大佬云集,技术、面试、生活聊的热火朝天。金三银四面试季来临,我会不定期分享自己面试中问到的题目,带你深度剖析面试官心理。添加微信ThirdFighter,拉你入群。

    往期推荐

    面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!

    面渣逆袭:计算机网络六十二问,三万字图文详解!速收藏!

    面渣逆袭:相亲十五问,蚌埠住了!

    面渣逆袭:Java基础五十三问,快来看看有没有你不会的!

    面渣逆袭:Java并发六十问,图文详解,快来看看你会多少道!

    面渣逆袭:JVM经典五十问,这下面试稳了!

    面渣逆袭:Java集合连环三十问

    面试字节,被操作系统问挂了

    老板,记得点赞在看转发三连啊!

    五分钟,手撸一个Spring容器

    大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌。

    这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开Spring神秘的面纱!

    从什么是IOC开始?

    Spring——春天,Java编程世界的春天是由一位音乐家——Rod Johnson带来的。

    Rod Johnson先后编写了两本巨著《Expert One-on-One J2EE Design and Development》、《Expert One-on-One J2EE Development without EJB》,拉起了挑战正统Java EE框架EJB的大旗。

    Rod Johnson不仅是一名旗手,更是开发了Spring这一轻量级框架,像一名勇敢的龙骑兵一样,对EJB发动了冲锋,并最终战胜了EJB,让Spring成为Java EE事实上的标准。

    Spring的两大内核分别是IOC和AOP,其中最最核心的是IOC。

    所谓的IOC(控制反转):就是由容器来负责控制对象的生命周期和对象间的关系。以前是我们想要什么,就自己创建什么,现在是我们需要什么,容器就给我们送来什么。

    也就是说,控制对象生命周期的不再是引用它的对象,而是容器。对具体对象,以前是它控制其它对象,现在所有对象都被容器控制,所以这就叫控制反转

    也许你还听到另外一个概念DI(依赖注入),它指的是容器在实例化对象的时候把它依赖的类注入给它,我们也可以认为,DI是IOC的补充和实现。

    工厂和Spring容器

    Spring是一个成熟的框架,为了满足扩展性、实现各种功能,所以它的实现如同枝节交错的大树一样,现在让我们把视线从Spring本身移开,来看看一个萌芽版的Spring容器怎么实现。

    Spring的IOC本质就是一个大工厂,我们想想一个工厂是怎么运行的呢?

    • 生产产品:一个工厂最核心的功能就是生产产品。在Spring里,不用Bean自己来实例化,而是交给Spring,应该怎么实现呢?——答案毫无疑问,反射

      那么这个厂子的生产管理是怎么做的?你应该也知道——工厂模式

    • 库存产品:工厂一般都是有库房的,用来库存产品,毕竟生产的产品不能立马就拉走。Spring我们都知道是一个容器,这个容器里存的就是对象,不能每次来取对象,都得现场来反射创建对象,得把创建出的对象存起来。

    • 订单处理:还有最重要的一点,工厂根据什么来提供产品呢?订单。这些订单可能五花八门,有线上签签的、有到工厂签的、还有工厂销售上门签的……最后经过处理,指导工厂的出货。

      在Spring里,也有这样的订单,它就是我们bean的定义和依赖关系,可以是xml形式,也可以是我们最熟悉的注解形式。

    那对应我们的萌芽版的Spring容器是什么样的呢?

    订单:Bean定义

    Bean可以通过一个配置文件定义,我们会把它解析成一个类型。

    • beans.properties

      为了偷懒,这里直接用了最方便解析的properties,用一个<key,value>类型的配置来代表Bean的定义,其中key是beanName,value是class

      userDao:cn.fighter3.bean.UserDao
      
    • BeanDefinition.java

      bean定义类,配置文件中bean定义对应的实体

      public class BeanDefinition 
      
          private String beanName;
      
          private Class beanClass;
           //省略getter、setter  
          
      

    获取订单:资源加载

    接下订单之后,就要由销售向生产部门交接,让生产部门知道商品的规格、数量之类。

    资源加载器,就是来完成这个工作的,由它来完成配置文件中配置的加载。

    public class ResourceLoader 
    
        public static Map<String, BeanDefinition> getResource() 
            Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(16);
            Properties properties = new Properties();
            try 
                InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties");
                properties.load(inputStream);
                Iterator<String> it = properties.stringPropertyNames().iterator();
                while (it.hasNext()) 
                    String key = it.next();
                    String className = properties.getProperty(key);
                    BeanDefinition beanDefinition = new BeanDefinition();
                    beanDefinition.setBeanName(key);
                    Class clazz = Class.forName(className);
                    beanDefinition.setBeanClass(clazz);
                    beanDefinitionMap.put(key, beanDefinition);
                
                inputStream.close();
             catch (IOException | ClassNotFoundException e) 
                e.printStackTrace();
            
            return beanDefinitionMap;
        
    
    
    

    订单分配:Bean注册

    对象注册器,这里用于单例bean的缓存,我们大幅简化,默认所有bean都是单例的。可以看到所谓单例注册,也很简单,不过是往HashMap里存对象。

    public class BeanRegister 
    
        //单例Bean缓存
        private Map<String, Object> singletonMap = new HashMap<>(32);
    
        /**
         * 获取单例Bean
         *
         * @param beanName bean名称
         * @return
         */
        public Object getSingletonBean(String beanName) 
            return singletonMap.get(beanName);
        
    
        /**
         * 注册单例bean
         *
         * @param beanName
         * @param bean
         */
        public void registerSingletonBean(String beanName, Object bean) 
            if (singletonMap.containsKey(beanName)) 
                return;
            
            singletonMap.put(beanName, bean);
        
    
    
    

    生产车间:对象工厂

    好了,到了我们最关键的生产部门了,在工厂里,生产产品的是车间,在IOC容器里,生产对象的是BeanFactory。

    • 对象工厂,我们最核心的一个类,在它初始化的时候,创建了bean注册器,完成了资源的加载。

    • 获取bean的时候,先从单例缓存中取,如果没有取到,就创建并注册一个bean

      public class BeanFactory 
      
          private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
      
          private BeanRegister beanRegister;
      
          public BeanFactory() 
              //创建bean注册器
              beanRegister = new BeanRegister();
              //加载资源
              this.beanDefinitionMap = new ResourceLoader().getResource();
          
      
          /**
           * 获取bean
           *
           * @param beanName bean名称
           * @return
           */
          public Object getBean(String beanName) 
              //从bean缓存中取
              Object bean = beanRegister.getSingletonBean(beanName);
              if (bean != null) 
                  return bean;
              
              //根据bean定义,创建bean
              return createBean(beanDefinitionMap.get(beanName));
          
      
          /**
           * 创建Bean
           *
           * @param beanDefinition bean定义
           * @return
           */
          private Object createBean(BeanDefinition beanDefinition) 
              try 
                  Object bean = beanDefinition.getBeanClass().newInstance();
                  //缓存bean
                  beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean);
                  return bean;
               catch (InstantiationException | IllegalAccessException e) 
                  e.printStackTrace();
              
              return null;
          
      
      

    生产销售:测试

    • UserDao.java

      我们的Bean类,很简单

      public class UserDao 
      
          public void queryUserInfo()
              System.out.println("A good man.");
          
      
      
    • 单元测试

      public class ApiTest 
          @Test
          public void test_BeanFactory() 
              //1.创建bean工厂(同时完成了加载资源、创建注册单例bean注册器的操作)
              BeanFactory beanFactory = new BeanFactory();
      
              //2.第一次获取bean(通过反射创建bean,缓存bean)
              UserDao userDao1 = (UserDao) beanFactory.getBean("userDao");
              userDao1.queryUserInfo();
      
              //3.第二次获取bean(从缓存中获取bean)
              UserDao userDao2 = (UserDao) beanFactory.getBean("userDao");
              userDao2.queryUserInfo();
          
      
      
    • 运行结果

      A good man.
      A good man.
      

    至此,我们一个萌芽版的Spring容器就完成了。

    考虑一下,它有哪些不足呢?是否还可以抽象、扩展、解耦……

    细细想想这些东西,你是不是对真正的Spring IOC容器为何如此复杂,有所理解了呢?




    参考:

    [1]. 《Spring揭秘》

    [2].小傅哥 《手撸Spring》

    [3].《精通Spring4.X企业应用开发实战》


    更多干货文章👇👇👇

    以上是关于五分钟,手撸一个Spring容器!的主要内容,如果未能解决你的问题,请参考以下文章

    五分钟,手撸一个Spring容器

    五分钟,手撸一个Spring容器

    五分钟,手撸一个Spring容器

    Bean容器生命周期,好像人的一生。。

    Spring Bean生命周期,好像人的一生。。

    Spring Bean生命周期,好像人的一生。。