Simple IOC 容器实现-基于XML方式

概述

IOC(Inversion of Control)“控制反转”,不过更流行的叫法是“依赖注入”(DI - Dependency Injection)。

什么是“控制反转”呢?其实就是将控制权(创建对象和对象之间的依赖关系的权利)交给Spring容器。以前我们写代码的需要某个对象的时候直接使用 new XXXImpl();,有了Spring IOC容器之后,它负责对象的创建和依赖注入,当我们需要某个对象时直接跟Spring IOC容器要就好了。

IOC 听起来很高大上,其实实现起来并不复杂。本文主要介绍基于XML配置的方式来实现一个IOC容器,后面会有单独的一章介绍如何通过注解的方式来实现IOC容器。

用法

具体用法与Spring IOC类似,如下:

1、beans.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">  

    <bean id="userDao" class="com.ricky.ioc.sample.dao.UserDaoImpl" scope="singleton" init-method="init" ></bean>

    <bean id="userService" class="com.ricky.ioc.sample.service.UserServiceImpl">  
        <property name="userDao" ref="userDao"></property>  
    </bean>

    <bean id="userController" class="com.ricky.ioc.sample.controller.UserController">  
        <property name="userService" ref="userService"></property>  
    </bean>
</beans>  

2、添加maven依赖

<dependency>
    <groupId>com.ricky.framework</groupId>
    <artifactId>ioc</artifactId>
    <version>1.0.0</version>
</dependency>

3、加载bean配置文件

ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

//通过id获取Bean
UserController userController =(UserController)ctx.getBean("userController");
userController.login("ricky", "123");

//通过Class获取Bean
UserService userService = ctx.getBean(UserService.class);
System.out.println(userService);
userService.login("ricky", "abc");

ctx.close();

运行结果如下:

UserController login name->ricky,password->123
UserServiceImpl login name->ricky,password->123
UserDaoImpl find name->ricky
com.ricky.ioc.sample.service.UserServiceImpl@214c265e
UserServiceImpl login name->ricky,password->abc
UserDaoImpl find name->ricky
container close…

具体实现

思路:
解析beans.xml获取Bean列表以及相互之间的依赖关系,然后通过反射技术构造出Bean实例,并根据Bean之间的依赖关系进行Bean装配。

首先,看看ApplicationContext类,代码如下:

package com.ricky.framework.ioc;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;

import com.ricky.framework.ioc.model.BeanDefinition;
import com.ricky.framework.ioc.model.PropertyDefinition;
import com.ricky.framework.ioc.util.ReflectionUtils;

public abstract class ApplicationContext {

    public abstract Object getBean(String id);

    public abstract <T> T getBean(Class<T> clazz);

    public abstract void close();

    protected abstract BeanDefinition getBeanDefinition(String id);

    protected Object createBean(BeanDefinition bd) {

        try {
            Object bean = ReflectionUtils.newInstance(bd.getClassName());
            if(StringUtils.isNotEmpty(bd.getInitMethodName())){
                ReflectionUtils.invokeMethod(bean, bd.getInitMethodName());
            }

            return bean;
        } catch (ClassNotFoundException | InstantiationException
                | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException("create bean error, class->"+bd.getClassName(), e);
        }
    }

    protected void injectBeanProperties(Object bean, BeanDefinition beanDefinition){

        try {
            PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();  

            for(PropertyDefinition propertyDefinition : beanDefinition.getProperties()){  

                for(PropertyDescriptor propertyDescriptor:ps){

                    if(propertyDescriptor.getName().equals(propertyDefinition.getName())){  

                        Method setter = propertyDescriptor.getWriteMethod(); 
                        setter.setAccessible(true); 

                        setter.invoke(bean, getBean(propertyDefinition.getRef()));  
                    }
                }
            }
        } catch (SecurityException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException
                | IntrospectionException e) {
            throw new RuntimeException("inject bean properties error", e);
        }
    }
}

在这涉及到两个关键类:BeanDefinition 和 PropertyDefinition,它们分别是用来描述 javabean的定义和javabean 属性的定义,一个BeanDefinition 可以有1个或多个PropertyDefinition,它们之间是1:N的关系。代码如下:

BeanDefinition.java

package com.ricky.framework.ioc.model;

import java.util.List;

public class BeanDefinition {
    private String id;
    private String className;
    private String scope;   //singleton|prototype
    private String initMethodName;
    private List<PropertyDefinition> properties;

    public BeanDefinition(String id, String className) {  
        this.id = id;  
        this.className = className;  
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getClassName() {
        return className;
    }
    public void setClassName(String className) {
        this.className = className;
    }
    public String getScope() {
        return scope;
    }
    public void setScope(String scope) {
        this.scope = scope;
    }
    public String getInitMethodName() {
        return initMethodName;
    }
    public void setInitMethodName(String initMethodName) {
        this.initMethodName = initMethodName;
    }
    public List<PropertyDefinition> getProperties() {
        return properties;
    }
    public void setProperties(List<PropertyDefinition> properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "BeanDefinition [id=" + id + ", className=" + className
                + ", scope=" + scope + ", initMethodName=" + initMethodName
                + ", properties=" + properties + "]";
    }

}

PropertyDefinition.java

package com.ricky.framework.ioc.model;

public class PropertyDefinition {  
    private String name;
    private String ref;

    public PropertyDefinition(String name, String ref) {  
        this.name = name;  
        this.ref = ref;  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public String getRef() {  
        return ref;  
    }  
    public void setRef(String ref) {  
        this.ref = ref;  
    }

    @Override
    public String toString() {
        return "PropertyDefinition [name=" + name + ", ref=" + ref + "]";
    }

}  

2、接下来是ClassPathXmlApplicationContext类,代码如下:

package com.ricky.framework.ioc;

import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;

import com.ricky.framework.ioc.model.BeanDefinition;
import com.ricky.framework.ioc.parser.BeanXmlConfigParser;
import com.ricky.framework.ioc.util.BeanScope;
import com.ricky.framework.ioc.util.ReflectionUtils;

public class ClassPathXmlApplicationContext extends ApplicationContext {

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();
    protected Map<String, Object> beanInstanceMap = new HashMap<String, Object>();

    public ClassPathXmlApplicationContext(String xmlFilePath) {

        System.out.println("****************container init begin****************");

        readXml(xmlFilePath);
        initBeans();
        injectBeans();

        System.out.println("****************container init end****************");
    }

    private void readXml(String xmlFilePath) {

        BeanXmlConfigParser beanXmlConfigParser = new BeanXmlConfigParser();

        List<BeanDefinition> bean_def_list = null;
        try {
            bean_def_list = beanXmlConfigParser.parse(xmlFilePath);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("not found bean xml, file->"+xmlFilePath, e);
        } catch (DocumentException e) {
            throw new RuntimeException("bean xml format error, file->"+xmlFilePath, e);
        }

        for (BeanDefinition beanDefinition : bean_def_list) {

            if(StringUtils.isEmpty(beanDefinition.getId()) || StringUtils.isEmpty(beanDefinition.getClassName())){
                throw new IllegalArgumentException("bean definition is empty!");
            }

            if (beanDefinitionMap.containsKey(beanDefinition.getId())) {
                throw new IllegalArgumentException(
                        "duplicated bean id , id->"
                                + beanDefinition.getId());
            }

            beanDefinitionMap.put(beanDefinition.getId(), beanDefinition);
        }
    }

    private void initBeans() {

        for (Map.Entry<String, BeanDefinition> me : beanDefinitionMap.entrySet()) {

            BeanDefinition bd = me.getValue();
            if(StringUtils.isEmpty(bd.getScope()) || bd.getScope().equals(BeanScope.SINGLETON)){
                try {
                    Object bean = createBean(bd);
                    beanInstanceMap.put(bd.getId(), bean);
                } catch (Exception e) {
                    throw new IllegalArgumentException("create bean error,class->"+bd.getClassName(), e);
                }
            }
        }
    }

    private void injectBeans() {

        for (Map.Entry<String, BeanDefinition> me : beanDefinitionMap.entrySet()) {

            BeanDefinition beanDefinition = me.getValue();
            //判断有没有注入属性  
            if (beanDefinition.getProperties() != null && beanDefinition.getProperties().size()>0) {  

                Object bean = beanInstanceMap.get(beanDefinition.getId());  
                try {
                    injectBeanProperties(bean, beanDefinition);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }  
        }  
    }

    @Override
    public Object getBean(String id) {

//      System.out.println("get bean by id:"+id);

        if (StringUtils.isEmpty(id)) {
            return null;
        }

        if (beanDefinitionMap.containsKey(id)) {

            BeanDefinition bd = beanDefinitionMap.get(id);

            if(StringUtils.isEmpty(bd.getScope()) || bd.getScope().equals(BeanScope.SINGLETON)){

                return beanInstanceMap.get(id);
            }

            Object bean = null;
            try {
                bean = createBean(bd);
                injectBeanProperties(bean, bd);
                beanInstanceMap.put(bd.getId(), bean);
            } catch (Exception e) {
                e.printStackTrace();
            }

            return bean;
        }
        throw new IllegalArgumentException("unknown bean, id->" + id);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getBean(Class<T> clazz) {

//      System.out.println("get bean by type:"+clazz.getName());

        for(Map.Entry<String, BeanDefinition> me : beanDefinitionMap.entrySet()){

            BeanDefinition bd = me.getValue();
            Class<?> beanClass = null;
            try {
                beanClass = ReflectionUtils.loadClass(bd.getClassName());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

            if(beanClass!=null && clazz.isAssignableFrom(beanClass)){
//              System.out.println("find bean by type, class->"+clazz.getName());
                return (T) getBean(bd.getId());
            }
        }

        return null;
    }

    @Override
    protected BeanDefinition getBeanDefinition(String id) {

        return beanDefinitionMap.get(id);
    }

    @Override
    public void close() {

        System.out.println("container close...");

        // release resource
        beanDefinitionMap.clear();
        beanDefinitionMap = null;

        beanInstanceMap.clear();
        beanInstanceMap = null;
    }

}

在ClassPathXmlApplicationContext类中,主要负责三大功能:解析XML配置文件、通过反射构建Bean实例以及对Bean进行装配。


小结

以上所有代码均已上传到GitHub上,欢迎大家fork。另外由于时间比较仓促,代码设计上有不合理的地方还请包涵,后面会抽时间对代码进行重构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值