面试官特别爱问SpringIOC底层实现,Spring源码晦涩难懂怎么办呢? 跟着老师手动实现一个mini ioc容器吧,实现后再回头看Spring源码事半功倍哦,就算直接和面试官讲也完全可以哦,类名完全按照源码设计,话不多说开干~!
手动实现IOC容器的设计
需要实现的IOC功能:
·可以通过xml配置bean信息
·可以通过容器getBean获取对象
·能够根据Bean的依赖属性实现依赖注入
·可以配置Bean的单例多例
实现简易IOC设计的类
类/接口 | 说明 |
BeanFactory | IOC容器的基础接口,提供IOC容器的基本功能 |
DefaultListableBeanFactory | IOC容器的核心实现类,提供多个map集合用来存储bean的定义对象,提供getBean方法的核心实现 |
XmlBeanFactory | IOC容器的实现类,基于xml构建bean信息 |
XmlBeanDefinitionReader | 用于解析xml信息,并提供解析Document文档的方法,并将解析到的BeanDefinition对象注册到核心容器中 |
BeanDefinition | 封装Bean的定义对象,如: bean的id class,scope ..等等 |
Property | 封装Bean所关联依赖的属性 |
类之间关系模型
前期准备
创建maven项目引入依赖
<dependencies><!-- 解析xml --><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.1</version></dependency><!-- BeanUtils --><dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.3</version></dependency></dependencies>
准备3个bean的实体类
/**
* 学生类
* 学生类依赖班级对象
* 并提供 sayHello() 方法
* @作者 itcast
* @创建日期 2020/3/7 19:46
**/publicclassStudent{privateString name;privateTClass tClass;publicvoidsayHello(){System.out.println("大家好,我是"+this.name+" 我的班级是==>"+tClass.getCname()+" 我的老师是"+tClass.getTeacher().getTname());}publicStringgetName(){return name;}publicvoidsetName(String name){this.name = name;}publicTClassgettClass(){return tClass;}publicvoidsettClass(TClass tClass){this.tClass = tClass;}}
/**
* 班级类
* 班级类依赖教师对象
* @作者 itcast
* @创建日期 2020/3/7 19:45
**/publicclassTClass{privateString cname;// 班级名称privateTeacher teacher;// 老师publicStringgetCname(){return cname;}publicvoidsetCname(String cname){this.cname = cname;}public com.itcast.ioc.bean.TeachergetTeacher(){return teacher;}publicvoidsetTeacher(com.itcast.ioc.bean.Teacher teacher){this.teacher = teacher;}}
/**
* 教师类
* @作者 itcast
* @创建日期 2020/3/7 19:44
**/publicclassTeacher{privateString tname;// 老师名称publicStringgetTname(){return tname;}publicvoidsetTname(String tname){this.tname = tname;}}
xml配置对象
配置学生对象: 小明
依赖班级对象: 3年2班
依赖教师对象: 陈老师
<?xml version="1.0" encoding="UTF-8"?><beans><!-- 配置IOC容器要管理的对象 bean作用域: 单例 原型 --><bean id="student"class="com.itcast.ioc.bean.Student" scope="singleton" lazy-init="true"><!-- 依赖注入: 属性注入 构造器注入 注解注入--><property name="name" value="小明"></property><property name="tClass" ref="tclass"></property></bean><bean id="tclass"class="com.itcast.ioc.bean.TClass"><property name="cname" value="3年2班"></property><property name="teacher" ref="teacher"></property></bean><bean id="teacher"class="com.itcast.ioc.bean.Teacher"><property name="tname" value="陈老师"></property></bean></beans>
mini-IOC容器-定义类
定义BeanFactory
/**
* 容器的基础接口
* 提供容器最基本的功能
*/publicinterfaceBeanFactory{// 核心方法 获取对象ObjectgetBean(String beanName);}
定义DefaultListableBeanFactory
packagecom.itcast.ioc.core;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;/**
* 基础容器的核心实现
* 提供 beanDefinitionMap 存储bean的定义
* 提供 singletonObjects 存储bean的对象实例
* @作者 itcast
* @创建日期 2020/7/8 15:37
**/publicclassDefaultListableBeanFactoryimplementsBeanFactory{// 提供 beanDefinitionMap 存储bean的定义privateMap<String,BeanDefinition> beanDefinitionMap =newConcurrentHashMap<>();// 提供 singletonObjects 存储bean的对象实例 (scope为singleton的)privateMap<String,Object> singletonObjects =newConcurrentHashMap<>();/**
* 实现getBean方法
* @parambeanName
* @return
*/@OverridepublicObjectgetBean(String beanName){returnnull;}/**
* 将bean注册到容器中
* @parambeanDefinition
*/publicvoidregisterBeanDefinition(BeanDefinition beanDefinition){
beanDefinitionMap.put(beanDefinition.getBeanName(),beanDefinition);}}
定义BeanDefnition
/**
* 用于描述Bean的定义
* @作者 itcast
* @创建日期 2020/7/8 15:41
**/publicclassBeanDefinition{privateString beanName;// bean标签的ID 作为bean的唯一标识privateString className;// bean的所属classprivateString scope ="singleton";// bean的scope作用域privateList<Property> propertyList =newArrayList<>();publicStringgetBeanName(){return beanName;}publicvoidsetBeanName(String beanName){this.beanName = beanName;}publicStringgetClassName(){return className;}publicvoidsetClassName(String className){this.className = className;}publicStringgetScope(){return scope;}publicvoidsetScope(String scope){this.scope = scope;}publicList<Property>getPropertyList(){return propertyList;}publicvoidsetPropertyList(List<Property> propertyList){this.propertyList = propertyList;}}
定义Property
/**
* 用于封装一个property标签
* 属性数据
* @作者 itcast
* @创建日期 2020/7/8 15:44
**/publicclassProperty{privateString name;// 属性名称privateString value;// 属性的值privateString ref;// 属性的引用publicStringgetName(){return name;}publicvoidsetName(String name){this.name = name;}publicStringgetValue(){return value;}publicvoidsetValue(String value){this.value = value;}publicStringgetRef(){return ref;}publicvoidsetRef(String ref){this.ref = ref;}}
定义XmlBeanFactory
/**
* 继承核心实现类
* 基于xml配置bean的实现类
* @作者 itcast
* @创建日期 2020/7/8 15:47
**/publicclassXmlBeanFactoryextendsDefaultListableBeanFactory{/**
* 将解析配置文件 注册bean的所有工作交给reader对象
*/finalXmlBeanDefinitionReader xmlBeanDefinitionReader =newXmlBeanDefinitionReader(this);/**
* 构造器需要传入xml配置文件
* @paramconfigPath
*/publicXmlBeanFactory(String configPath){// 使用reader对象 解析配置 注册Beanthis.xmlBeanDefinitionReader.loadBeanDefinitions(configPath);}}
定义XmlBeanDefinitionReader
/**
* 解析配置
* 注册到容器中
* @作者 itcast
* @创建日期 2020/7/8 15:51
**/publicclassXmlBeanDefinitionReader{// 核心beanfactory对象 用于将解析后的bean注册到beanfactory中finalDefaultListableBeanFactory beanFactory;publicXmlBeanDefinitionReader(DefaultListableBeanFactory beanFactory){this.beanFactory = beanFactory;}/**
* 根据传递的配置文件
* 解析配置
* 注册bean
* @paramconfigPath
*/voidloadBeanDefinitions(String configPath){}}
mini-IOC容器--解析注册
实现步骤
1. 通过dom4j解析xml得到Document文档
2. 遍历文档所有Bean标签
3. 解析每一个Bean标签 封装一个BeanDefinition对象
4. 解析每一个Bean标签下的所有Property标签 封装一个Property对象
5. 将BeanDefinition和Property对象注册到容器
实现xml解析及bean注册
/**
* 解析配置
* 注册到容器中
* @作者 itcast
* @创建日期 2020/7/8 15:51
**/publicclassXmlBeanDefinitionReader{// 核心beanfactory对象 用于将解析后的bean注册到beanfactory中finalDefaultListableBeanFactory beanFactory;publicXmlBeanDefinitionReader(DefaultListableBeanFactory beanFactory){this.beanFactory = beanFactory;}/**
* 根据传递的配置文件
* 解析配置
* 注册bean
* @paramconfigPath
*/voidloadBeanDefinitions(String configPath){// 1. 通过dom4j解析xml得到Document文档Document document =doLoadDocument(configPath);// 2. 遍历文档所有Bean标签Element rootElement = document.getRootElement();List<Element> list = rootElement.selectNodes("//bean");for(Element element : list){// 3. 解析每一个Bean标签 封装一个BeanDefinition对象BeanDefinition beanDefinition =parseBeanDefinition(element);// 5. 将BeanDefinition和Property对象注册到容器
beanFactory.registerBeanDefinition(beanDefinition);}}/**
* 3. 解析每一个Bean标签 封装一个BeanDefinition对象
* 4. 解析每一个Bean标签下的所有Property标签 封装一个Property对象
*/BeanDefinitionparseBeanDefinition(Element element){BeanDefinition beanDefinition =newBeanDefinition();String beanName = element.attributeValue("id");String className = element.attributeValue("class");String scope = element.attributeValue("scope");
beanDefinition.setBeanName(beanName);
beanDefinition.setClassName(className);if(scope!=null&&!"".equals(scope)){
beanDefinition.setScope(scope);}List<Element> propertyList = element.elements("property");for(Element propertyEle : propertyList){Property property =newProperty();
property.setName(propertyEle.attributeValue("name"));
property.setValue(propertyEle.attributeValue("value"));
property.setRef(propertyEle.attributeValue("ref"));
beanDefinition.getPropertyList().add(property);}return beanDefinition;}/**
* 解析Document文档
* @paramconfigPath
* @return
*/DocumentdoLoadDocument(String configPath){InputStream inputStream =this.getClass().getClassLoader().getResourceAsStream(configPath);SAXReader saxReader =newSAXReader();try{return saxReader.read(inputStream);}catch(DocumentException e){
e.printStackTrace();System.out.println("解析xml出现异常==>"+e.getMessage());thrownewRuntimeException(e.getMessage());}}}
准备测试类
/**
* 测试类
* @作者 itcast
* @创建日期 2020/7/8 16:19
**/publicclassIocTest{publicstaticvoidmain(String[] args){// 创建IOC容器BeanFactory beanFactory =newXmlBeanFactory("applicationContext.xml");// 通过容器获取对象Student student =(Student)beanFactory.getBean("student");// 调用对象sayHello方法
student.sayHello();}}
断点查看注册情况
可以看到我们配置的xml内容 已经解析成了BeanDefinition对象,注册到了核心容器的map中
mini-IOC容器-getBean
实现步骤
1. 先从单例的map集合中获取 是否有指定beanName的对象
·有直接返回
·没有下一步
2. 从注册集合中获取bean的定义对象
·有下一步
·没有抛异常NoSuchBeanDefinition
3. 判断bean的scope作用域
singleton单例
· createBean对象
·存入单例集合
·返回对象
prototype多例
·createBean对象
·返回对象
4. createBean方法
获取BeanDefinition中的className
通过反射API得到Class对象
通过反射API得到bean实例
获取BeanDefinition中依赖的属性列表
实现属性的依赖注入
实现getBean及createBean方法
/**
* 基础容器的核心实现
* 提供 beanDefinitionMap 存储bean的定义
* 提供 singletonObjects 存储bean的对象实例
* @作者 itcast
* @创建日期 2020/7/8 15:37
**/publicclassDefaultListableBeanFactoryimplementsBeanFactory{// 提供 beanDefinitionMap 存储bean的定义privateMap<String,BeanDefinition> beanDefinitionMap =newConcurrentHashMap<>();// 提供 singletonObjects 存储bean的对象实例 (scope为singleton的)privateMap<String,Object> singletonObjects =newConcurrentHashMap<>();/**
* 实现getBean方法
* @parambeanName
* @return
*/@OverridepublicObjectgetBean(String beanName){// 1. 先从单例的map集合中获取 是否有指定beanName的对象Object singletonObj = singletonObjects.get(beanName);// 有直接返回if(singletonObj!=null){return singletonObj;}// 没有下一步// 2. 从注册集合中获取bean的定义对象BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);// 有下一步// 没有抛异常NoSuchBeanDefinitionif(beanDefinition==null){thrownewRuntimeException("NoSuchBeanDefinition : 你找的 "+beanName+" 对象 不存在");}// 3. 判断bean的scope作用域String scope = beanDefinition.getScope();// singleton单例if("singleton".equals(scope)){// createBean对象Object obj =createBean(beanDefinition);// 存入单例集合
singletonObjects.put(beanName,obj);// 返回对象return obj;}else{// prototype多例// createBean对象returncreateBean(beanDefinition);// 返回对象}}/**
* //4. createBean方法
* //获取BeanDefinition中的className
* //通过反射API得到Class对象
* //通过反射API得到bean实例
* //获取BeanDefinition中依赖的属性列表
* //实现属性的依赖注入
* 创建对象
* @parambeanDefinition
* @return
*/ObjectcreateBean(BeanDefinition beanDefinition){String className = beanDefinition.getClassName();Class<?> aClass;try{
aClass =Class.forName(className);}catch(ClassNotFoundException e){
e.printStackTrace();thrownewRuntimeException("类未找到"+e.getMessage());}// 创建对象:Object obj;try{
obj = aClass.newInstance();}catch(InstantiationException e){
e.printStackTrace();thrownewRuntimeException("创建对象失败"+e.getMessage());}catch(IllegalAccessException e){
e.printStackTrace();thrownewRuntimeException("非法访问"+e.getMessage());}// 依赖注入List<Property> propertyList = beanDefinition.getPropertyList();for(Property property : propertyList){String name = property.getName();String value = property.getValue();String ref = property.getRef();// 属性名不为空 进行注入if(name!=null&&!"".equals(name)){// 如果配置的是value值 直接注入if(value!=null&&!"".equals(value)){Map<String,String> params =newHashMap<>();
params.put(name,value);try{BeanUtils.populate(obj,params);}catch(IllegalAccessException e){
e.printStackTrace();thrownewRuntimeException("非法访问"+e.getMessage());}catch(InvocationTargetException e){
e.printStackTrace();thrownewRuntimeException("调用目标对象失败"+e.getMessage());}}// 如果配置的是ref需要获取其它对象注入if(ref!=null&&!"".equals(ref)){try{BeanUtils.setProperty(obj,name,getBean(ref));}catch(IllegalAccessException e){
e.printStackTrace();thrownewRuntimeException("非法访问"+e.getMessage());}catch(InvocationTargetException e){
e.printStackTrace();thrownewRuntimeException("调用目标对象失败"+e.getMessage());}}}}return obj;}/**
* 将bean注册到容器中
* @parambeanDefinition
*/publicvoidregisterBeanDefinition(BeanDefinition beanDefinition){
beanDefinitionMap.put(beanDefinition.getBeanName(),beanDefinition);}}
mini-IOC容器-单例对象初始化
DefaultListableBeanFactory增加初始化方法
publicvoidpreInstaniceSingletons(){
beanDefinitionMap.forEach((beanName,beanDefinition)->{String scope = beanDefinition.getScope();// 判断单例 非抽象 不懒加载if("singleton".equals(scope)){this.getBean(beanName);}});}
XmlBeanFactory增加单例对象初始化
publicXmlBeanFactory(String configPath){// 使用reader对象 解析配置 注册Beanthis.xmlBeanDefinitionReader.loadBeanDefinitions(configPath);// 初始化单例对象this.preInstaniceSingletons();}
mini-IOC容器-测试和小结
测试对象能否获取
查看bean的注册及单例集合信息
可以通过变更scope的值查看对应的变化
IOC容器源码及其它面试细节
扩展: 容器如何创建对象
IOC容器在准备创建对象时, 会判断是否有配置 factory-method方法
如果有配置 会调用factory-method所指向的方法构建对象.
如果没配置,会检查是否有配置构造参数
无构造参数: 调用默认构造器创建对象
有构造参数: 根据参数情况匹配对应的构造器
扩展: bean的生命周期
spring 容器中的bean的完整生命周期一共分为十一步完成。
1.bean对象的实例化
2.封装属性,也就是设置properties中的属性值
3.如果bean实现了BeanNameAware,则执行setBeanName方法,也就是bean中的id值
4.如果实现BeanFactoryAware或者ApplicationContextAware ,需要设置setBeanFactory或者上下文对象setApplicationContext
5.如果存在类实现BeanPostProcessor后处理bean,执行postProcessBeforeInitialization,可以在初始化之前执行一些方法
6.如果bean实现了InitializingBean,则执行afterPropertiesSet,执行属性设置之后的操作
7.调用执行指定的初始化方法
8.如果存在类实现BeanPostProcessor则执行postProcessAfterInitialization,执行初始化之后的操作
9.执行自身的业务方法
10.如果bean实现了DisposableBean,则执行spring的的销毁方法
11.调用执行自定义的销毁方法。
扩展: bean的循环依赖问题
A 依赖 B B 依赖 A 产生闭环,称为循环依赖
·Spring 默认允许单例对象的属性注入 所产生的循环依赖
单例对象的循环依赖 Spring通过3级缓存来解决
比如一个类A中有一个属性是B类,B类中有一个属性是A类,这时看Spring是怎么解决他们的相互依赖的。Spring注入一个类的大体步骤分为两部分,一是先完成对类的构造工作,二是会对类的属性进行设置和填充。首先Spring构造A类,通过AbstractAutowireCapableBeanFactory的doCreateBean方法中调用addSingletonFactory方法将A类曝光到singletonFactories中。这时完成A的构造后,需要填充B属性,继续第二步,发现B还没有构造,于是开始B流程的构造过程,构造的时候发现需要填充A,从第三层缓存singletonFactories中找到A(此时的A还没有完全构造完成,但是可以拿到A的一个引用),B拿到A的引用后,完成B自己的填充属性工作,完成初始化工作,把自己放到第一层缓存singletonObjects中。这时回到A的这边,在拿到B对象后,完成自己的填充属性工作。
源码 | 级别 | 描述 |
singletonObjects | 一级缓存 | 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 |
earlySingletonObjects | 二级缓存 | 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 |
singletonFactories | 三级缓存 | 存放 bean 工厂对象,用于解决循环依赖 |
·如果是构造器依赖属性 会报循环依赖异常
·如果对象都是多例对象 会报循环依赖异常
·如果设置allowCircularReferences为false 会报循环依赖异常
protectedvoidcustomizeBeanFactory(DefaultListableBeanFactory beanFactory){if(this.allowBeanDefinitionOverriding !=null){
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if(this.allowCircularReferences !=null){
beanFactory.setAllowCircularReferences(this.allowCircularReferences);}}
扩展: bean的覆盖问题
默认情况:
同一个配置文件中出现id相同的bean会报错,不同的配置文件出现id相同的bean后加,载的bean会将先加载的bean覆盖掉称为bean的覆盖,bean的覆盖不会报错,但可能影响我们的项目,可以通过属性设置不允许bean的覆盖,allowBeanDefinitionOverriding设置为false。