带你手写spring:IOC与DI


前言

依稀记得在2016年刚毕业的时候,在京面试某公司的时候技术总监和我聊到了spring,我比较欣赏一个音乐人Rod Johnson以个人之力承担了spring的主开发工程。当时的个人水平仅仅是知道spring,会简单使用。当面试官问到我对源码的阅读时,问我大概多久的时间能吃懂spring源码,我给了一个答案是1-2年吧。然后这个问题没有下文了,估计他(是她,是个女技术总监)当时想spring这么简单的东西你都要2年???

工作已经2年+,是时候兑现当年自己说的话了,日常我们在spring最主要的使用地方只是集成第三方产品(如orm框架等),像一种万能胶一样。以及自身业务系统中的bean的管理。以及一些aop的使用。在本篇文章中,主要是通过自身对于源码的解读后,抓住核心思想,实现一个mini版的spring。帮助大家以及我自身的理解,毕竟分享与帮助他人也是提高自身的一种手段。在本节中,我们主要讲解IOC与DI,后几篇文章中再继续探讨aop与springMVC以及springBoot的问题(当然,mvc与boot以及cloud是由spring衍生出来的产品)。

本文不是概念型讲解(只做简单的概念说明),如果你是新手概念也是模糊的,那么不太适合阅读此文章。对于内容中有任何的错误之处以及不恰当之处,希望您能指出。我会及时改正。

IOC

Inversion of Control中文译名:控制反转。

对于这个词,我的个人理解是:没有spring的时候,B类需要对象A,我们需要自己去new A(),可能有10个地方都需要A,那我们需要new10次,加重了GC的压力。

这是,我们想,如果有一个大的池子来帮我们创建好所有的对象(比如B和A类的对象都创建好),对象直接的依赖关系,也由这个池子来帮我们管理,如在创建B类时,发现需要引用A,则注入(DI)一个A对象。

原本对象的控制权在自己手里,而把这个控制权转变为给这个池子。更像是一种设计模式,如果这种思想被开发的早,可能就列入GoF的23种设计模式中了。。。实现这种模式的主要方式,则是我们后文的DI(Dependency Injection)

ioc实现

对于spring源码的阅读,个人阅读过相关书籍,第一遍看的时候,全脸懵逼,光是源码中的调用链,就有20几层+(不过源码中严格遵循了方法单一职责,这点很值得我学习,且设计模式运用的惟妙惟肖)。给大家的学习意见是:先看一些知名的博客,梳理一个整体的逻辑图,再细微的看里面的实现策略。否则是很难读懂的。当然,时至今日(个人也是略知一二),只是对大致逻辑做了总结后写出一个mini版,只是讲一个思路,代码的健壮性还请大家不要太较真。可能有的地方实现的并不是最好的实现方式。

bean:是spring中很重要的一个概念,其实就是对象的实例

首先,我们定义一个BeanFactory,对象实例的工厂,这个工厂提供一个统一的方法,getBean

1 public interface HkBeanFactory {
2     
3     Object getBean(String name) throws Exception;
4 
5 }

 

有了工厂后,我们还不行,还需要一个定义bean(BeanDefinition)的接口,用来描述这个bean是如何被定义出来的,在我所写的代码中,bean定义只通过类构造器来创建bean,事实上spring源码中还可通过静态工厂与成员工厂来创建bean,但因实际中少用这两中,所以我没定义。

定义bean,我们需要知道这个bean是哪个类的,是要单例的还是非单例(spring中叫:原型prototype),初始化bean时需要做的工作,以及bean销毁时的操作(销毁我的代码中未实现)

 1 public interface HkBeanDefinition {
 2     
 3     final static String SINGLETION = "singleton";
 4 
 5     final static String PROTOTYPE = "prototype";
 6     
 7     Class<?> getBeanClass();
 8     
 9     String getScope();
10     
11     boolean isSingleton();
12     
13     boolean isPrototype();
14     
15     String getInitMethodName();
16     
17 }

 

有了bean定义的接口,我们还是不能放入工厂中,还得将bean注册到工厂中,我们再写一个bean定义注册(BeanDefinitionRegistry)的接口,此接口要提供注册的功能,获取BeanDefinition的功能,另外,防止bean重复定义,需要一个检测是否已有bean的功能,如下:

1 public interface HkBeanDefinitionRegistry {
2     
3     void registerBeanDefinition (String beanName, HkBeanDefinition hkBeanDefinition) throws Exception;
4     
5     HkBeanDefinition getBeanDefinition(String beanName);
6     
7     boolean containsBeanDefinition(String beanName);
8 
9 }

 

OK,有了以上3个接口,我们可以开始实现具体细节了,首先,我们来实现一个通用的bean定义(GenericBeanDefinition),spring中,默认所有的bean都是单例的。所以我们也给定默认单例

 1 public class GenericBeanDefinition implements HkBeanDefinition {
 2     
 3     private Class<?> beanClass;
 4     
 5     private String scope = HkBeanDefinition.SINGLETION;
 6     
 7     private String initMethodName;
 8 
 9     
10     
11 
12     public void setBeanClass(Class<?> beanClass) {
13         this.beanClass = beanClass;
14     }
15 
16     public void setScope(String scope) {
17         this.scope = scope;
18     }
19 
20     public void setInitMethodName(String initMethodName) {
21         this.initMethodName = initMethodName;
22     }
23 
24 
25     @Override
26     public Class<?> getBeanClass() {
27         return beanClass;
28     }
29 
30     @Override
31     public String getScope() {
32         return scope;
33     }
34 
35     @Override
36     public boolean isSingleton() {
37         return Objects.equals(scope, HkBeanDefinition.SINGLETION);
38     }
39 
40     @Override
41     public boolean isPrototype() {
42         return Objects.equals(scope, HkBeanDefinition.PROTOTYPE);
43     }
44 
45     @Override
46     public String getInitMethodName() {
47         return initMethodName;
48     }
49 
50 
51 }
bean定义实现类,点击展开

 

在这个类中,我们实现接口中的方法并生成对应的set方法即可!

OK,以上是我们所需要的零件,现在,我们用这些零件来组装一个bean工厂的实现类,bean工厂中,我们要有注册bean定义的功能(类似官方spring中,我们在配置文件中组装bean那一步,指定类,属性等等。这里我们通过硬编码实现),

还要有获取bean的功能getBean(),为了简化步骤与理解,我们全部用java代码来实现,更好的帮助大家理解。(即我们这会省去xml解析,省去解析注解诸如@Component@Repository、@Service等)也省掉spring的applicationcontext的门面模式,我们来直接操作工厂。

所以这个类需要实现HkBeanFactory与HkBeanDefinitionRegistry这2个接口。

 

首先,定义俩个集合,存放bean实例与beanDefinition。如下:因可能存在同时注册,所以采用与官方版本一致的ConcurrentHashMap

public class DefaultBeanFactory implements HkBeanFactory, HkBeanDefinitionRegistry {

    private Map<String, Object> beanMap = new ConcurrentHashMap<>();
    private Map<String, HkBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    }

 

接下来,写注册bean的功能,如上文所说,我们此处用代码来注册,不再解析xml,方法如下:

    /* 
     * 注册bean定义,需要给定唯一bean的名称和bean的定义,放到bean定义集合中
     */
    @Override
    public void registerBeanDefinition(String beanName, HkBeanDefinition hkBeanDefinition) throws Exception {
        Objects.requireNonNull(beanName, "beanName不能为空");
        Objects.requireNonNull(hkBeanDefinition, "beanDefinition不能为空");
        if (beanDefinitionMap.containsKey(beanName)){
            throw new Exception("已存在【"+beanName+ "】的bean定义"+getBeanDefinition(beanName));
        }
        beanDefinitionMap.put(beanName, hkBeanDefinition);

    }

 

OK,bean已经再工厂定义好,接下来我们写关键的获取bean方法getBean(),在spring的官方版本中,创建bean对象有3种方式,通过调用构造器,还可以通过类的静态工厂方法与成员工厂方法来创建对象。鉴于后2种我们平常使用较少,此处我们采用构造器来创建对象。代码如下:

 1     /* 
 2      * 获得bean的门面方法
 3      */
 4     @Override
 5     public Object getBean(String name) throws Exception {
 6         return doGetBean(name);
 7     }
 8     
 9     /**
10      * getBean的具体逻辑
11      * 
12      * 事实上,在spring的bean定义中,还可以静态工厂方法和成员工厂方法来创建实例,但在开发中这2种用的较少,所以此处只使用构造器来创建bean
13      */
14     private Object doGetBean(String beanName) throws Exception{
15         Objects.requireNonNull(beanName, "beanName不能为空");
16         Object instance = beanMap.get(beanName);
17         //如果bean已存在,则直接返回
18         if(instance != null){
19             return instance;
20         }
21         HkBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
22         Objects.requireNonNull(beanDefinition, "beanDefinition不能为空");
23         Class<?> class1 = beanDefinition.getBeanClass();
24         Objects.requireNonNull(class1, "bean定义中class类型不能为空");
25         instance = class1.newInstance();
26         
27         //实例已创建好,通过反射执行bean的init方法
28         String initMethodName = beanDefinition.getInitMethodName();
29         if(null!=initMethodName){
30             Method method = class1.getMethod(initMethodName, null);
31             method.invoke(instance, null);
32         }
33         
34         //将单例bean放到map中,下次可直接拿到
35         if(beanDefinition.isSingleton()){
36             beanMap.put(beanName, instance);
37         }
38         return instance;
39     }

 

对以上代码关键部分做出解释,因实例化bean会在之后多次用到,所以单独抽取出来,事实上官方版本也是这么做的。,所以我们重点看一下doGetBean方法

16~20行,尝试从beanMap种获取,如果是单例bean,已创建过对象,则直接返回对象

21~22行,从beanDefinitionMap种获取bean定义,如果未定义,则抛出异常。

25行,调用bena定义种的类的构造方法来创建实例。

28~32行,某些bean有初始化方法init,如资源加载等,此处我们通过反射执行一次init方法。

35~37行,将单例bean放到集合种,下次获取此对象即可直接返回。

下面,附上完整代码:

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 默认的bean工厂实现类,需实现bean定义注册
 * @author HK
 *
 */
public class DefaultBeanFactory implements HkBeanFactory, HkBeanDefinitionRegistry {

    private Map<String, Object> beanMap = new ConcurrentHashMap<>();
    private Map<String, HkBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    
    
    /* 
     * 注册bean定义,需要给定唯一bean的名称和bean的定义,放到bean定义集合中
     */
    @Override
    public void registerBeanDefinition(String beanName, HkBeanDefinition hkBeanDefinition) throws Exception {
        Objects.requireNonNull(beanName, "beanName不能为空");
        Objects.requireNonNull(hkBeanDefinition, "beanDefinition不能为空");
        if (beanDefinitionMap.containsKey(beanName)){
            throw new Exception("已存在【"+beanName+ "】的bean定义"+getBeanDefinition(beanName));
        }
        beanDefinitionMap.put(beanName, hkBeanDefinition);

    }

    /* 
     * 获得bean定义
     */
    @Override
    public HkBeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    /* 
     * 是否存在bean定义
     */
    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    /* 
     * 获得bean的门面方法
     */
    @Override
    public Object getBean(String name) throws Exception {
        return doGetBean(name);
    }
    
    /**
     * getBean的具体逻辑
     * 
     * 事实上,在spring的bean定义中,还可以静态工厂方法和成员工厂方法来创建实例,但在开发中这2种用的较少,所以此处只使用构造器来创建bean
     */
    private Object doGetBean(String beanName) throws Exception{
        Objects.requireNonNull(beanName, "beanName不能为空");
        Object instance = beanMap.get(beanName);
        //如果bean已存在,则直接返回
        if(instance != null){
            return instance;
        }
        HkBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        Objects.requireNonNull(beanDefinition, "beanDefinition不能为空");
        Class<?> class1 = beanDefinition.getBeanClass();
        Objects.requireNonNull(class1, "bean定义中class类型不能为空");
        instance = class1.newInstance();
        
        //实例已创建好,通过反射执行bean的init方法
        String initMethodName = beanDefinition.getInitMethodName();
        if(null!=initMethodName){
            Method method = class1.getMethod(initMethodName, null);
            method.invoke(instance, null);
        }
        
        //将单例bean放到map中,下次可直接拿到
        if(beanDefinition.isSingleton()){
            beanMap.put(beanName, instance);
        }
        return instance;
    }

}
DefaultBeanFactory点击展开

 

 

测试IOC容器

接下来,我们来测试这个IOC容器的运行:如下先定义一个简单bean对象TeacherBean,一个是初始化方法init( ),一个是上课方法teach( )。

/**
 * @author HK
 *
 */
public class TeacherBean {
    
    public void teach(){
        System.out.println(this+"执行了teach方法,老师要开始上课了!");
    }
    
    public void init(){
        System.out.println("Teacher类的初始化init方法被执行了");
    }

}

 

 好,我们就将这个teacherBean放到IOC工厂。如下,

 1 import org.junit.AfterClass;
 2 import org.junit.Test;
 3 
 4 import spring.beans.DefaultBeanFactory;
 5 import spring.beans.GenericBeanDefinition;
 6 
 7 /**
 8  * @author HK
 9  *
10  */
11 public class TestIOC {
12     static DefaultBeanFactory factory = new DefaultBeanFactory();
13     
14     @Test
15     public void testRegist() throws Exception {
16         GenericBeanDefinition bd = new GenericBeanDefinition(); 
17         bd.setBeanClass(TeacherBean.class);
18 //        bd.setScope(HkBeanDefinition.PROTOTYPE);
19         bd.setInitMethodName("init");
20         factory.registerBeanDefinition("teacher", bd);
21         
22     }
23     
24     @AfterClass
25     public static void testGetBean() throws Exception{
26         TeacherBean t =(TeacherBean) factory.getBean("teacher");
27         TeacherBean t1 =(TeacherBean) factory.getBean("teacher");
28         t.teach();
29         t1.teach();
30         System.out.println(t==t1);
31     }
32 
33 }

 

解释:16~20行,我们主要做了注册bean定义。而在26~30行,是从IOC中获取bean,由于默认是单例,所以2次获取bean后是同1个对象。执行结果如下图

至此,一个简单的IOC容器就完成了。但我们发现了一些其他问题,比如Teacher类中有很多属性,如String name;  int age;  Student student;等等

此时,学生类Student也是一个bean的时候,如何给Teacher类赋予这些值呢,接下来,就是我们DI依赖注入要做的事情了。

 

DI

Dependency Injection中文译名:依赖注入。

由于时间关系,会在之后的周末时间整理 好后更新此处内容(手动捂脸)相信有了上边的基础后,此处大家也不难实现了!!!

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM