手写实现一个简单的IOC实现

1. 前言

简单记录一次Spring框架IOC功能的实现。IOC和AOP功能都是基于java反射实现的
源码地址:https://gitee.com/ericecc/simple_-spring.git

2. IOC

2.1 项目架构

image.png

2.2 主要步骤

  1. 创建ioc.xml
<beans xmlns:context=" ">
    <!-- user bean-->
    <bean id="user" class="com.example.spring.ioc.beans.User">
        <property name="name" value="zhangsan"/>
        <property name="age" value="20"/>
    </bean>
    <!-- table bean -->
    <bean id="table" class="com.example.spring.ioc.beans.Table">
        <property name="tableName" value="销售"/>
        <property name="id" value="000001"/>
        <property name="description" value="Spring IOC testing"/>
    </bean>
    <!-- 配置注解扫描 -->
    <context:component-scan base-package="com.example.spring.ioc.beans"></context:component-scan>
</beans>
  1. 创建相应的Bean
public class Table {
    String id;
    String tableName;
    String description;

    @Override
    public String toString() {
        return "Table{" +
                "id='" + id + '\'' +
                ", tableName='" + tableName + '\'' +
                ", description='" + description + '\'' +
                '}';
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
public class User {
    private String name;
    private String age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

package com.example.spring.ioc.beans;

import com.example.spring.ioc.annonation.AutoWired;
import com.example.spring.ioc.annonation.Beans;

/**
 * 该类是用注解方式实现依赖注入的类
 * 如果只是xml方式实现注入可以不写
 */
@Beans
public class DIBean {
    @AutoWired // 自动注入的bean是通过ioc配置文件方式生成的,没有改成注解实现
    public User user;
    @AutoWired
    public Table table;

    @Override
    public String toString() {
        return "DIBean{" +
                "user=" + user +
                ", table=" + table +
                '}';
    }
}
  1. IOC两种方式实现
package com.example.spring.ioc.utils;

import com.example.spring.ioc.annonation.AutoWired;
import com.example.spring.ioc.annonation.Beans;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 *  IOC 功能主要实现
 *  1. 包含注解方式和配置文件两种方式
 */
public class MyIoc {
    Map<String, Object> beansMap = new HashMap<>(); // bean 容器

    private static final Logger LOGGER = LoggerFactory.getLogger(MyIoc.class);

    public MyIoc(String location) throws Exception {
        LOGGER.info("location:{}", location);
        loadBeans(location); // 通过配置文件加载bean
        initAttrs(); // 通过注解自动注入
    }

    /**
     * 注解方式实现依赖自动注入
     * 1. 遍历容器中存储的对象
     * 2. 获取对象的Fields,
     *      1. 检查Fields是否有AutoWire注解
     *      2. 如果有AutoWire注解
     *      3. 通过setAccessible(true) 和 set方法为Field注入对象
     *
     */
    private void initAttrs() throws IllegalAccessException {
        Set<String> strings = beansMap.keySet();
        for (String s : strings){
            // 依赖注入
            attrsAssign(beansMap.get(s));
        }
    }

    /** 注解方式具体实现 **/
    private void attrsAssign(Object o) throws IllegalAccessException {
        Field[] declaredFields = o.getClass().getDeclaredFields();
        for(Field f: declaredFields){
            AutoWired annotation = f.getAnnotation(AutoWired.class);
            if (annotation != null){
                String name = f.getName();
                Object bean = getBean(name);
                if (o != null){
                    f.setAccessible(true);
                    f.set(o, bean);
                }
            }
        }
    }

    /**
     * 通过xml实现bean加载
     *  1. MySpringApplicationContext(String url) 传递配置文件的地址
     *  2. 解析xml配置文件
     *      1. 解析出 id 和 类名
     *          1. 通过Class.forName(类名) 加载对象
     *          2、 通过.newInstance()实例化
     *      2. 解析properties 用于生成具体对象
     *          1. 解析出name 和 value
     *          2. 通过 bean.getDeclaredField(name) 获取相应的Field
     *          3. 通过 Field.set(value) 为Field注入信息
     *      3. 将实例化的bean存入容器中
     * @param location
     * @throws Exception
     */
    private void loadBeans(String location) throws Exception {
        // 1. 读取xml配置文件
        FileInputStream fileInputStream = new FileInputStream(location);
        System.out.println(location);
        // 2. 获取xml文件中的配置信息
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(fileInputStream);
        Element root = doc.getDocumentElement();
        LOGGER.info("Element:{}", root.toString());
        NodeList nodes = root.getChildNodes();
        for (int i = 0; i < nodes.getLength(); i++){
            Node node = nodes.item(i);
            /** xml 配置方式主逻辑**/
            if (node instanceof Element && node.getNodeName().equals("bean")){
                Element ele = (Element) node;
                String id = ele.getAttribute("id");
                String className = ele.getAttribute("class");
                LOGGER.info("id:{}, className:{}", id, className);

                // 加载类
                Class beanClass = null;
                try {
                    beanClass = Class.forName(className);
                } catch (ClassNotFoundException e){
                    e.printStackTrace();
                    return;
                }
                // 实例化
                Object bean = beanClass.newInstance();
                registerBean(id, bean);

                // 获取属性
                NodeList property = ele.getElementsByTagName("property");
                for (int j = 0; j < property.getLength(); j++){
                    Element pro = (Element)property.item(j);
                    String name = pro.getAttribute("name");
                    String value = pro.getAttribute("value");
                    LOGGER.info("name:{}, value:{}", name, value);
                    // 获取相应字段
                    Field field = bean.getClass().getDeclaredField(name);
                    field.setAccessible(true);
                    if (value != null && value.length() > 0){
                        // 将bean对象上的field 设置为value
                        field.set(bean, value);
                    } else {
                        String ref = pro.getAttribute("ref");
                        LOGGER.info("ref:{}", ref);
                        if (ref == null || ref.length() == 0){
                            throw new IllegalArgumentException("ref config error");
                        }
                        field.set(bean,getBean(ref));
                    }
                }
            }
            /** 包扫描 **/
            if (node instanceof Element && node.getNodeName().equals("context:component-scan")){
                Element el = (Element) node;
                String attribute = el.getAttribute("base-package"); // com.example.spring.ioc.beans
                // 替换.
                String path = attribute.replaceAll("\\.", "\\/");
                // com/example/spring/ioc/beans
                URL url = Thread.currentThread().getContextClassLoader().getResource(path);
                if (url != null){
                    String protocol = url.getProtocol();
                    // 遍历包下所有类
                    if (protocol.equalsIgnoreCase("file")){
                        File file = new File(url.getFile());
                        ArrayList<String> beansName = new ArrayList<>();
                        File[] files = file.listFiles();
                        for (File f : files){
                            // 读取包名
                            String name = f.getName().substring(0, f.getName().length() - 6);
                            String packageName = path.replaceAll("\\/", "\\.") + "." + name;
                            // 获取类内部内容
                            Class<?> aClass = Class.forName(packageName);
                            Beans annotation = aClass.getAnnotation(Beans.class);
                            if (annotation != null){
                                String aName = aClass.getName();
                                String[] splits = aName.split("\\.");
                                String pack = splits[splits.length - 1].toLowerCase();
                                registerBean(pack, aClass.newInstance()); // 实例化并存入容器
                            }
                        }
                    }
                }
            }
        }
        Set<String> strings = beansMap.keySet();
        LOGGER.info("------------------Beans容器内容-------------------");
        for (String s : strings){
            LOGGER.info("key:{}, value:{}]", s, beansMap.get(s));
        }
        LOGGER.info("-------------------------------------------------");
    }

    private void registerBean(String id, Object bean) {
        beansMap.put(id, bean);
    }

    public Object getBean(String ref) {
        return beansMap.get(ref);
    }
}

  1. SpringContext实现
import com.example.spring.ioc.utils.MyIoc;

public class MySpringApplicationContext {
    private String configName;
    
    public MySpringApplicationContext(String configName){
        this.configName = configName;
    }

    /**
     * 通过该方法将bean转换成指定类型
     * @param name bean name
     * @param c bean 的 class
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> T getBean(String name, Class<T> c) throws Exception {
        String file = MySpringApplicationContext.class.getClassLoader().getResource(configName).getFile();
        System.out.println(file);
        MyIoc myIoc = new MyIoc(file);
        Object bean = myIoc.getBean(name);
        return (T) bean;
    }
}
  1. 测试
import com.example.spring.ioc.beans.DIBean;
import com.example.spring.ioc.beans.Table;
import com.example.spring.ioc.beans.User;
import org.junit.Test;

public class MyIocTest {
    @Test
    public void test() throws Exception {
        // 创建上下文
        MySpringApplicationContext ctx = new MySpringApplicationContext("ioc.xml");
        // 获取对象
        User user = ctx.getBean("user", User.class);
        Table table = ctx.getBean("table", Table.class);
        DIBean dibean = ctx.getBean("dibean", DIBean.class);
        // xml配置实现
        System.out.println(user);
        System.out.println(table);
        // Beans实现
        System.out.println(dibean);
    }
}
  1. 结果


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