自己实现Spring IoC容器(四)IoC容器的Bug

Bug的发现

之前我自己写了一个类似Spring中的IoC容器自己实现Spring IoC容器(三)完成IoC容器,然后最近想在这个项目基础上把Spring的AOP也实现一下,然后就悲剧的发现了一句错误代码……

这个错误代码就在edu.jyu.core.ClassPathXmlApplicationContext类的Object createBeanByConfig(Bean bean)方法中,下面是这个方法

/**
 * 根据bean的配置信息创建bean对象
 *
 * @param bean
 * @return
 */
private Object createBeanByConfig(Bean bean) {
    // 根据bean信息创建对象
    Class clazz = null;
    Object beanObj = null;
    try {
        clazz = Class.forName(bean.getClassName());
        // 创建bean对象
        beanObj = clazz.newInstance();
        // 获取bean对象中的property配置
        List<Property> properties = bean.getProperties();
        // 遍历bean对象中的property配置,并将对应的value或者ref注入到bean对象中
        for (Property prop : properties) {
            Map<String, Object> params = new HashMap<>();
            if (prop.getValue() != null) {
                params.put(prop.getName(), prop.getValue());
                // 将value值注入到bean对象中
                BeanUtils.populate(beanObj, params);
            } else if (prop.getRef() != null) {
                Object ref = context.get(prop.getRef());
                // 如果依赖对象还未被加载则递归创建依赖的对象
                if (ref == null) {
                    ref = createBeanByConfig(bean);
                }
                params.put(prop.getName(), ref);
                // 将ref对象注入bean对象中
                BeanUtils.populate(beanObj, params);
            }
        }
    } catch (Exception e1) {
        e1.printStackTrace();
        throw new RuntimeException("创建" + bean.getClassName() + "对象失败");
    }
    return beanObj;
}

错误就在如果依赖对象还未被加载条件成立后,ref = createBeanByConfig(bean); 这句代码的问题是什么了,很明显我一不小心又把当前要创建的bean对象的配置信息传入createBeanByConfig方法中了,所以就会无限递归下去,最后发生StackOverflowError错误。

至于为什么我的测试代码能通过也是比较凑巧,我的测试bean是一个A类,一个B类,其中B类依赖A类对象,所以我们要把A类对象注入到B类中,但是就是这么巧,读取配置文件的时候先读到了A类,所以在要创建B类对象时,A类对象已经创建好了,ref == null就为false,也就是说没执行到那句错误代码,所以就没发现……

要是我改为A类依赖B类,那就可以发现问题了,因为要创建A类对象时,B类对象还没创建。

A类

package edu.jyu.bean;

public class A {
    private String name;
    private B b;

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

B类

package edu.jyu.bean;

public class B {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

此时配置文件applicationContext.xml也需要修改一下

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="A" class="edu.jyu.bean.A">
        <property name="name" value="Jason"></property>
        <property name="b" ref="B"></property>
    </bean>

    <bean name="B" class="edu.jyu.bean.B" scope="prototype">
        <property name="age" value="13"></property>
    </bean>
</beans>

测试类TestApplicationContext也改一下

package edu.jyu.core;

import org.junit.Test;

import edu.jyu.bean.A;
import edu.jyu.bean.B;

public class TestApplicationContext {

    @Test
    public void test() {
        BeanFactory ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
        A a = (A) ac.getBean("A");
        A a1 = (A) ac.getBean("A");
        B b = (B) ac.getBean("B");
        B b1 = (B) ac.getBean("B");
        System.out.println(a.getB());
        System.out.println("a==a1 : "+(a==a1));
        System.out.println("b==b1 : "+(b==b1));
    }
}

运行这个测试,你就会惊喜地发现爆栈了

Bug的解决

解决上面的那个Bug并不难,只需要把那句错误代码ref = createBeanByConfig(bean);换成ref = createBeanByConfig(config.get(prop.getRef()));

完整方法

/**
 * 根据bean的配置信息创建bean对象
 *
 * @param bean
 * @return
 */
private Object createBeanByConfig(Bean bean) {
    // 根据bean信息创建对象
    Class clazz = null;
    Object beanObj = null;
    try {
        clazz = Class.forName(bean.getClassName());
        // 创建bean对象
        beanObj = clazz.newInstance();
        // 获取bean对象中的property配置
        List<Property> properties = bean.getProperties();
        // 遍历bean对象中的property配置,并将对应的value或者ref注入到bean对象中
        for (Property prop : properties) {
            Map<String, Object> params = new HashMap<>();
            if (prop.getValue() != null) {
                params.put(prop.getName(), prop.getValue());
                // 将value值注入到bean对象中
                BeanUtils.populate(beanObj, params);
            } else if (prop.getRef() != null) {
                Object ref = context.get(prop.getRef());
                // 如果依赖对象还未被加载则递归创建依赖的对象
                if (ref == null) {
                    //下面这句的错误在于传入了当前bean配置信息,这会导致不断递归最终发生StackOverflowError
                    //解决办法是传入依赖对象的bean配置信息
                    //ref = createBeanByConfig(bean);
                    ref = createBeanByConfig(config.get(prop.getRef()));
                }
                params.put(prop.getName(), ref);
                // 将ref对象注入bean对象中
                BeanUtils.populate(beanObj, params);
            }
        }
    } catch (Exception e1) {
        e1.printStackTrace();
        throw new RuntimeException("创建" + bean.getClassName() + "对象失败");
    }
    return beanObj;
}

现在运行测试类TestApplicationContext的测试方法就没问题了

时间: 2024-11-07 15:00:36

自己实现Spring IoC容器(四)IoC容器的Bug的相关文章

Spring学习总结四——SpringIOC容器四

一:spring容器给bean对象注入属性值 1:注入基本属性值 a. 创建MessageBean类: 1 /** 2 * 3 */ 4 package com.hlcui.dao; 5 6 /** 7 * @author Administrator 8 * 9 */ 10 public class MessageBean { 11 private String name; 12 13 private int age; 14 15 private double salary; 16 17 pub

Spring.net(二)----初探IOC容器

我在上一篇关于Spring.net的文章“Spring.NET框架简介及模块说明 ”中很详细的介绍了,本文就不旧话从提.我门就直奔主题吧. 1.首先了解两个接口.  IObjectFactory接口和IApplicationContext接口:他两个称为“容器”或“IOC容器”. Spring.net框架的核心原则是非侵入性.  IObjectFactory接口是初始化.配置及管理对象的实际容器.  IObjectFactory全限定名为Spring.Objects.Factory.IObjec

JAVAWEB开发之Spring详解之——Spring的入门以及IOC容器装配Bean(xml和注解的方式)、Spring整合web开发、整合Junit4测试

Spring框架学习路线 Spring的IOC Spring的AOP,AspectJ Spring的事务管理,三大框架的整合 Spring框架概述 什么是Spring? Spring是分层的JavaSE/EE full-stack(一站式)轻量级开源框架. 所谓分层: SUN提供的EE的三层结构:web层.业务层.数据访问层(也称持久层,集成层). Struts2是web层基于MVC设计模式框架. Hibernate是持久的一个ORM的框架. 所谓一站式:Spring框架有对三层的每层解决方案.

《Spring揭秘》——IOC梳理2(容器启动)

IoC容器背后的秘密 主要分为两个阶段:容器启动阶段.Bean实例化阶段. 容器启动阶段: 容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData( 通常也就是XML格式的配置信息).进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了. Bean实例化阶段: 该

spring源码研究之IoC容器在web容器中初始化过程

前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spring的IoC容器在web容器如何启动和起作用的并不清楚.所以就抽时间看一下spring的源代码,借此了解它的原理. 我们知道,对于使用Spring的web应用,无须手动创建Spring容器,而是通过配置文件,声明式的创建Spring容器.因此在Web应用中创建Spring容器有如下两种方式: 1. 直接在web.xml文件中配置创建Spring容器. 2. 利用第三方MVC

Spring:笔记整理(2)——IOC容器

IOC容器 什么是IOC IOC ,全称Inversion of control,即,控制反转,是一种设计思想. 控制: 在Java中,IOC意味着:你将设计好的对象交给容器控制,而不是传统的在你的对象内容直接控制. 在传统程序设计中,我们直接在对象内容通过new创建对象.而IOC是有一个专门的容器来创建这些对象,即由IOC来控制对象的创建. 反转: 容器帮助我们查找及注入依赖对象,对象只是被动的接受依赖对象. 图示: 当有了IOC/DI容器后,我们就不用在客户端中主动创建这些对象了:

容器的IOC应用

1.IOC概念 IOC全称Inversion of Control,被译为控制反转: IOC是指程序中的对象获取方式发生反转,有最初的new方式创建,转换为由第三框架创建.注入.第三框架一般是通过配置方式指定注入哪一个具体实现,从而降低了对象之间的耦合度 IOC按实现方法不同,可以分为依赖注入DI和依赖查找两种 Spring容器是采用DI方式实现了IOC控制,IOC是Spring框架的基础和核心: DI全称是Dependency Injection,被译为依赖注入: DI的基本原理就是将一起工作

IOC容器在web容器中初始化——(一)两种配置方式

参考文章http://blog.csdn.net/liuganggao/article/details/44083817,http://blog.csdn.net/u013185616/article/details/52186184. 最近在研究IOC容器在web容器中初始化的过程.阅读了源码,参照了很多文章,在这里记录一下. 使用的web容器为tomcat7.spring的jar包为4.3.7.RELEASE版本. 我们可以通过web.xml配置文件web容器中声明spring容器.有以下两

spring(3)------控制反转(IOC)/依赖注入(DI)

一,spring核心概念理解 控制反转: 控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理. 所谓的"控制反转"概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器. 没有控制反转这种模式前,你创建一个对象,在什么地方用,你得单独通过关键字new出来用, 但现在可以不用这样,你把new对象的权利交给spring配置文件,通过配置文件来'new', 就是权利的反转,你以前干的事

spring源码分析---IOC(1)

我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象间的关系,将对象之间的关系抽象出来,通过spring容器控制对象生成时机,减少对象之间的耦合度. 更通俗一点的说就是,JAVA程序中,当你在代码中需要使用某个类提供的功能时,你首先需要new一个对象,给它传递必要的参数,然后才能使用它提供的功能:有了IOC之后,IOC的容器类似一个中介,所有的对象都