微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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,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。另外由于时间比较仓促,代码设计上有不合理的地方还请包涵,后面会抽时间对代码进行重构。

原文地址:https://www.jb51.cc/xml/295215.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。