前言
依稀记得在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 }
在这个类中,我们实现接口中的方法并生成对应的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; } }
测试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中文译名:依赖注入。
由于时间关系,会在之后的周末时间整理 好后更新此处内容(手动捂脸)相信有了上边的基础后,此处大家也不难实现了!!!