Spring Ioc 源码分析之Bean的加载和构造

我们都知道,Spring Ioc和Aop是Spring的核心的功能,因此花一点时间去研究还是很有意义的,如果仅仅是知其所以然,也就体会不到大师设计Spring的精华,还记得那句话,Spring为JavaEE开发带来了春天。

IOC就是Inversion of control 也就是控制反转的意思,另一种称呼叫做依赖注入,这个可能更直观一点,拿个例子来说吧:

@Component
public class UserService {
    @Autowired
    private UserMapper mapper;
}

比如在UserService可能要调用一个Mapper,这个Mapper去做DAO的操作,在这里我们直接通过@Autowired注解去注入这个Mapper,这个就叫做依赖注入,你想要什么就注入什么,不过前提它是一个Bean。至于是怎么注入的,那是Spring容器做的事情,也是我们今天去探索的。

在进行分析之前,我先声明一下,下面的这些代码并不是从spring 源码中直接拿过来,而是通过一步步简化,抽取spring源码的精华,如果直接贴源码,我觉得可能很多人都会被吓跑,而且还不一定能够学到真正的东西。

Spring要去管理Bean首先要把Bean放到容器里,那么Spring是如何获得Bean的呢?

  • 首先,Spring有一个数据结构,BeanDefinition,这里存放的是Bean的内容和元数据,保存在BeanFactory当中,包装Bean的实体:
public class BeanDefinition {
    //真正的Bean实例
    private Object bean;
    //Bean的类型信息
    private Class beanClass;
    //Bean类型信息的名字
    private String beanClassName;
    //用于bean的属性注入 因为Bean可能有很多熟属性
    //所以这里用列表来进行管理
    private PropertyValues propertyValues = new PropertyValues();
}
  • PerpertyValues存放Bean的所有属性
public class PropertyValues {
    private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
}
  • PropertyValue存放的是每个属性,可以看到两个字段,name和valu。name存放的就是属性名称,value是object类型,可以是任何类型
public class PropertyValue {
    private final String name;
    private final Object value;
}

定义好这些数据结构了,把Bean装进容器的过程,其实就是其BeanDefinition构造的过程,那么怎么把一些类装入的Spring容器呢?

  • Spring有个接口就是获取某个资源的输入流,获取这个输入流后就可以进一步处理了:
public interface Resource {
    InputStream getInputStream() throws IOException;
}
  • UrlResource 是对Resource功能的进一步扩展,通过拿到一个URL获取输入流。
public class UrlResource implements Resource {
    private final URL url;
    public UrlResource(URL url) {
        this.url = url;
    }
    @Override
    public InputStream getInputStream() throws IOException{
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }
  • ResourceLoader是资源加载的主要方法,通过location定位Resource,
    然后通过上面的UrlResource获取输入流:
public class ResourceLoader {
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}
  • 大家可能会对上面的这段代码产生疑问:
     URL resource = this.getClass().getClassLoader().getResource(location);

为什么通过得到一个类的类类型,然后得到对应的类加载器,然后调用类加载器的Reource怎么就得到了URL这种类型呢?
我们来看一下类加载器的这个方法:

 public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

从这个方法中我们可以看出,类加载器去加载资源的时候,先会去让父类加载器去加载,如果父类加载器没有的话,会让根加载器去加载,如果这两个都没有加载成功,那就自己尝试去加载,这个一方面为了java程序的安全性,不可能你用户自己随便写一个加载器,就用你用户的。

  • 接下来我们看一下重要角色,这个是加载BeanDefinition用的。
public interface BeanDefinitionReader {
    void loadBeanDefinitions(String location) throws Exception;
}
  • 这个接口是用来从配置中读取BeanDefinition:
    其中registry key是bean的id,value存放资源中所有的BeanDefinition,
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    private Map<String,BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }

    public Map<String, BeanDefinition> getRegistry() {
        return registry;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}
  • 最后我们来看一个通过读取Xml文件的BeanDefinitionReader:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    /**
     * 构造函数 传入我们之前分析过的ResourceLoader 这个通过
     * location 可以 加载到Resource
     */
    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    /**
     * 这个方法其实是BeanDefinitionReader这个接口中的方法
     * 作用就是通过location来构造BeanDefinition
     */
    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        //把location传给ResourceLoader拿到Resource,然后获取输入流
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        //接下来进行输入流的处理
        doLoadBeanDefinitions(inputStream);
    }

    protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
        //因为xml是文档对象,所以下面进行一些处理文档工具的构造
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        //把输入流解析成一个文档,java可以处理的文档
        Document doc = docBuilder.parse(inputStream);
        // 处理这个文档对象 也就是解析bean
        registerBeanDefinitions(doc);
        inputStream.close();
    }

    public void registerBeanDefinitions(Document doc) {
        //得到文档的根节点,知道根节点后获取子节点就是通过层级关系处理就行了
        Element root = doc.getDocumentElement();
        //解析根节点 xml的根节点
        parseBeanDefinitions(root);
    }

    protected void parseBeanDefinitions(Element root) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //element有属性的包装
            if (node instanceof Element) {
                Element ele = (Element) node;
                processBeanDefinition(ele);
            }
        }
    }

    protected void processBeanDefinition(Element ele) {
        /**
         *  <bean id="object***" class="com.***.***"/>
         */
        //获取element的id
        String name = ele.getAttribute("id");
        //获取element的class
        String className = ele.getAttribute("class");
        BeanDefinition beanDefinition = new BeanDefinition();
        //处理这个bean的属性
        processProperty(ele, beanDefinition);
        //设置BeanDefinition的类名称
        beanDefinition.setBeanClassName(className);
        //registry是一个map,存放所有的beanDefinition
        getRegistry().put(name, beanDefinition);
    }



    private void processProperty(Element ele, BeanDefinition beanDefinition) {
        /**
         *类似这种:
         <bean id="userServiceImpl" class="com.serviceImpl.UserServiceImpl">
         <property name="userDao" ref="userDaoImpl"> </property>
         </bean>
         */
        NodeList propertyNode = ele.getElementsByTagName("property");
        for (int i = 0; i < propertyNode.getLength(); i++) {
            Node node = propertyNode.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                //获得属性的名称
                String name = propertyEle.getAttribute("name");
                //获取属性的值
                String value = propertyEle.getAttribute("value");
                if (value != null && value.length() > 0) {
                    //设置这个bean对应definition里的属性值
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
                } else {
                    //value是Reference的话  就会进入到这里处理
                    String ref = propertyEle.getAttribute("ref");
                    if (ref == null || ref.length() == 0) {
                        throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                                + name + "' must specify a ref or value");
                    }
                    //构造一个BeanReference 然后把这个引用方到属性list里
                    BeanReference beanReference = new BeanReference(ref);
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
                }
            }
        }
    }
}

  • 上面用到了BeanReference(如下),其实这个和PropertyValue类似,用不同的类型是为了更好的区分:
public class BeanReference {
     private String name;
     private Object bean;
}

好了,到现在我们已经分析完了Spring是如何找到Bean并加载进入Spring容器的,这里面最主要的数据结构就是BeanDefinition,ReourceLoader来完成资源的定位,读入,然后获取输入流,进一步的处理,这个过程中有对xml文档的解析和对属性的填充。
 由于自己能力有限,有些地方表述的不准确,还请大家谅解~~,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容