自己动手写一个spring之IOC_2

写在前面

本文来继续增加IOC部分的功能。

源码

1:增强

1.1:支持单例

spring源码的实现默认就是单例的,单例对象的好处是可以减少对象的创建以及存储成本,从而可以降低时间和空间的双重成本。为了支持单例,我们需要做如下的改造工作。既然是单例bean,肯定就增加了单例bean的管理职责,而根据单一职责原则,自然需要增加一个新的接口来承担这个职责,如下:

java 复制代码
/**
 * 单例bean的注册处,负责单例bean的维护功能
 * 可以形象的理解为专门针对单例bean的办事处
 */
public interface SingletonBeanRegistry {
    void registerSingleton(String beanName, Object singletonObject);

    Object getSingleton(String beanName);

    boolean containsSingleton(String beanName);

    String[] getSingletonNames();

}

按照惯例,自然要提供一个默认的实现类:

java 复制代码
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
    //容器中存放所有bean的名称的列表
    protected List<String> beanNames = new ArrayList<>();
    //容器中存放所有bean实例的map
    protected Map<String, Object> singletons = new ConcurrentHashMap<>(256);

    // 。。。
}

beandefinition中也要增加单例,原型相关信息:

java 复制代码
public class BeanDefinition {
    // 单例 or 原型,默认单例
	String SCOPE_SINGLETON = "singleton";
	String SCOPE_PROTOTYPE = "prototype";

    private String scope=SCOPE_SINGLETON;
    
}

BeanFactory中增加相关方法:

java 复制代码
/**
 * spring最基础的工厂类接口,提供bean的注册和获取功能
 */
public interface BeanFactory {
	// 是否单例
	boolean isSingleton(String name);
	// 是否原型
	boolean isPrototype(String name);
}

具体实现类怎么做的,看源码吧,其实具体实现没那么重要了,重要的是为什么要增加这些内容。

修改应用程序上下文支持单例:

java 复制代码
/**
 * 集大成者的类,暴漏给用户使用
 */
public class ClassPathXmlApplicationContext implements BeanFactory, ApplicationEventPublisher {
    SimpleBeanFactory beanFactory;

    @Override
    public boolean isSingleton(String name) {
        return false;
    }

    @Override
    public boolean isPrototype(String name) {
        return false;
    }
}

1.2:支持容器状态事件监听

定义事件类:

java 复制代码
/**
 * 定义应用事件
 */
public class ApplicationEvent extends EventObject {

	private static final long serialVersionUID = 1L;

	public ApplicationEvent(Object arg0) {
		super(arg0);
	}

}

这里继承了java.util.EventObject融入jdk的事件体系中。

定义事件发布类,即负责触发事件的类:

java 复制代码
/**
 * 发布应用事件
 */
public interface ApplicationEventPublisher {
	void publishEvent(ApplicationEvent event);
}

修改ClassPathXmlApplicationContext支持事件:

java 复制代码
/**
 * 集大成者的类,暴漏给用户使用
 */
public class ClassPathXmlApplicationContext implements BeanFactory, ApplicationEventPublisher {
    @Override
    public void publishEvent(ApplicationEvent event) {
    }
    // ...
}

1.3:支持注入

1.3.1:基于构造函数注入

如下:

xml 复制代码
<beans>
    <bean id="aservice" class="com.minis.test.AServiceImpl">
      <constructor-arg type="String" name="name" value="abc"/>
      <constructor-arg type="int" name="level" value="3"/>
    </bean>
</beans>

对应的类维护构造函数信息:

java 复制代码
public class ArgumentValues {
    private final List<ArgumentValue> argumentValueList = new ArrayList<ArgumentValue>();
    // ...
}

1.3.2:基于setter注入

如下:

xml 复制代码
<beans>
    <bean id="aservice" class="com.minis.test.AServiceImpl">
        <property type="String" name="property1" value="Hello World!"/>
    </bean>
</beans>

对应的类维护属性信息:

java 复制代码
/**
 * PropertyValue们
 */
public class PropertyValues {
    private final List<PropertyValue> propertyValueList;
    // ...
}

2:具体编码

在前面针对需要增强的内容已经定义了相关类和接口,并修改了已有的相关类,这部分看下bean解析相关的代码实现。

2.1:值的注入

当前要支持的值注入方式有两种,基于构造函数和属性的方式,如下可能的配置:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="aservice" class="com.minis.test.AServiceImpl">
        <constructor-arg type="String" name="name" value="abc"/>
        <constructor-arg type="int" name="level" value="3"/>
        <property type="String" name="property1" value="Someone says"/>
        <property type="String" name="property2" value="Hello World!"/>
    </bean>
</beans>

既然表示bean的配置变了,如何解析呢,在前面的设计中,beandefinitionreader负责解析resource为beanfinition,所以我们需要修改beanfinitionreader:

java 复制代码
package com.hc.minispring.ioc.two.beans;

public void loadBeanDefinitions(Resource resource) {
        while (resource.hasNext()) {
            // ...
            // 处理属性 如 <property type="String" name="property1" value="Someone says"/>
            List<Element> propertyElements = element.elements("property");
            PropertyValues PVS = new PropertyValues();
            for (Element e : propertyElements) {
                String pType = e.attributeValue("type");
                String pName = e.attributeValue("name");
                String pValue = e.attributeValue("value");
                PVS.addPropertyValue(new PropertyValue(pType, pName, pValue));
            }
            beanDefinition.setPropertyValues(PVS);
            
            // 处理构造器参数,如<constructor-arg type="int" name="level" value="3"/>
            List<Element> constructorElements = element.elements("constructor-
arg");
            ArgumentValues AVS = new ArgumentValues();
            for (Element e : constructorElements) {
                String aType = e.attributeValue("type");
                String aName = e.attributeValue("name");
                String aValue = e.attributeValue("value");
                AVS.addArgumentValue(new ArgumentValue(aType, aName, aValue));
            }
            beanDefinition.setConstructorArgumentValues(AVS);
            // ...
        }
    }
}

接着还要通过修改创建bean的逻辑,完成注入的工作,此时需要修改负责bean创建的beanfactory类,修改如下:

java 复制代码
private Object createBean(BeanDefinition beanDefinition) {
    // ...
    try {
        clz = Class.forName(beanDefinition.getClassName());
        // 处理构造器参数
        ArgumentValues argumentValues = 
beanDefinition.getConstructorArgumentValues();
        //如果有参数
        if (!argumentValues.isEmpty()) {
            // ...
            //对每一个参数,分数据类型分别处理
            for (int i = 0; i < argumentValues.getArgumentCount(); i++) {
               // ...
            }
            try {
                //按照特定构造器创建实例
                con = clz.getConstructor(paramTypes);
                obj = con.newInstance(paramValues);
            } 
        } else { //如果没有参数,直接创建实例
            obj = clz.newInstance();
        }
    } catch (Exception e) {
    }
    // 处理属性
    PropertyValues propertyValues = beanDefinition.getPropertyValues();
    if (!propertyValues.isEmpty()) {
        for (int i = 0; i < propertyValues.size(); i++) {
            //对每一个属性,分数据类型分别处理
            PropertyValue propertyValue = 
propertyValues.getPropertyValueList().get(i);
            // ...
            //按照setXxxx规范查找setter方法,调用setter方法设置属性              
            String methodName = "set" + pName.substring(0, 1).toUpperCase() + pName.substring(1);            
            Method method = null;
            try {
                method = clz.getMethod(methodName, paramTypes);
            } 
            try {
                method.invoke(obj, paramValues);
            } 
        }
    }
    return obj;
}

代码con = clz.getConstructor(paramTypes);String methodName = "set" + pName.substring(0, 1).toUpperCase() + pName.substring(1); method = clz.getMethod(methodName, paramTypes);就分别完成了基于构造函数和属性方式的值注入。

到此,基础数据类型我们已经支持注入了,如果是对象类型呢?为了区分基础数据类型,增加一个ref来表示要引用的bean,这样语义表达的更加明确,编码也会更加简单清晰,如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="basebaseservice" class="com.minis.test.BaseBaseService">
        <property type="com.minis.test.AServiceImpl" name="as" ref="aservice" />
    </bean>
    <bean id="aservice" class="com.minis.test.AServiceImpl">
        <constructor-arg type="int" name="level"  ref="baseservice"/>
        <property type="com.minis.test.BaseService" name="ref1" ref="baseservice"/>
    </bean>
    <bean id="baseservice" class="com.minis.test.BaseService">
        <property type="com.minis.test.BaseBaseService" name="bbs" ref="basebaseservice" />
    </bean>
</beans>

因为增加ref属性,所以我们需要修改argumentvalue和propertyvalue,如下:

java 复制代码
@Data
public class PropertyValue {
    private final String type;
    private final String name;
    private final Object value;
    private final boolean isRef;

    public PropertyValue(String type, String name, Object value, boolean isRef) {
        this.type = type;
        this.name = name;
        this.value = value;
        this.isRef = isRef;
    }

}

以及:

java 复制代码
@Data
public class ArgumentValue {
    private Object value;
    private String type;
    private String name;
    //    private final boolean isRef;
    private boolean isRef;
    
    // ...
}

修改beandefinition增加dependOn设置依赖的bean们:

java 复制代码
package com.hc.minispring.ioc.two.beans;

public class BeanDefinition {
    // 依赖的bean信息
    private String[] dependsOn;
}

这里我们只考虑基于属性方式注入对象的场景,修改beandefinitionreader:

java 复制代码
// com.hc.minispring.ioc.two.beans.XmlBeanDefinitionReader#loadBeanDefinitions
public void loadBeanDefinitions(Resource res) {
    while (res.hasNext()) {
        // ...
        for (Element e : propertyElements) {
            // ...
            String pRef = e.attributeValue("ref");
            String pV = "";
            boolean isRef = false;
            if (pValue != null && !pValue.equals("")) {
                isRef = false;
                pV = pValue;
            } else if (pRef != null && !pRef.equals("")) {
                isRef = true;
                pV = pRef;
                refs.add(pRef);
            }
            PVS.addPropertyValue(new PropertyValue(pType, pName, pV, isRef));
        }
        // ...
        String[] refArray = refs.toArray(new String[0]);  
        // 设置依赖的bean们
        beanDefinition.setDependsOn(refArray);
    }
}

接着改造beanfactory中的createBean方法,为了更加清晰,将对于属性注入的处理单独抽出一个方法:

java 复制代码
// com.hc.minispring.ioc.two.beans.SimpleBeanFactory#createBean
private Object createBean(BeanDefinition bd) {
    // ...
    handleProperties(bd, clz, obj);
    return obj;
}
java 复制代码
private void handleProperties(BeanDefinition bd, Class<?> clz, Object obj) {
    //handle properties
    System.out.println("handle properties for bean : " + bd.getId());
    PropertyValues propertyValues = bd.getPropertyValues();
    if (!propertyValues.isEmpty()) {
        for (int i = 0; i < propertyValues.size(); i++) {
            // ...
            if (!isRef) {
                // ...
            } else { //is ref, create the dependent beans
                try {
                    paramTypes[0] = Class.forName(pType);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                try {
                    // 如果是ref,就要递归获取需要注入的bean了
                    paramValues[0] = getBean((String) pValue);
                } catch (BeansException e) {
                    e.printStackTrace();
                }
            }
            // ...
        }
    }
}

2.3:refresh方法作为容器刷新的入口

我们来增加一个refresh方法,负责容器的刷新,修改beanfactory:

java 复制代码
// com.hc.minispring.ioc.two.beans.SimpleBeanFactory#refresh
public void refresh() {
    for (String beanName : beanDefinitionNames) {
        try {
            getBean(beanName);
        } catch (BeansException e) {
            e.printStackTrace();
        }
    }
}

修改applicationcontext:

java 复制代码
// com.hc.minispring.ioc.two.context.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(java.lang.String, boolean)
public ClassPathXmlApplicationContext(String fileName, boolean isRefresh) {
    Resource res = new ClassPathXmlResource(fileName);
    SimpleBeanFactory bf = new SimpleBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
    reader.loadBeanDefinitions(res);
    this.beanFactory = bf;

    if (isRefresh) {
        this.beanFactory.refresh();
    }
}

2.2:测试

xml:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="basebaseservice" class="com.hc.minispring.ioc.two.test.BaseBaseService">
        <property type="com.hc.minispring.ioc.two.test.AServiceImpl" name="as" ref="aservice"/>
    </bean>
    <bean id="aservice" class="com.hc.minispring.ioc.two.test.AServiceImpl">
        <constructor-arg type="String" name="name" value="abc"/>
        <constructor-arg type="int" name="level" value="3"/>
        <property type="String" name="property1" value="Someone says"/>
        <property type="String" name="property2" value="Hello World!"/>
        <property type="com.hc.minispring.ioc.two.test.BaseService" name="ref1" ref="baseservice"/>
    </bean>
    <bean id="baseservice" class="com.hc.minispring.ioc.two.test.BaseService">
        <property type="com.hc.minispring.ioc.two.test.BaseBaseService" name="bbs" ref="basebaseservice"/>
    </bean>


</beans>

测试类:

java 复制代码
package com.hc.minispring.ioc.two.test;

import com.hc.minispring.ioc.two.beans.BeansException;
import com.hc.minispring.ioc.two.context.ClassPathXmlApplicationContext;

public class Test1 {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("two/beanTwo.xml");
        AService aService;
        try {
            aService = (AService) ctx.getBean("aservice");
            aService.sayHello();
        } catch (BeansException e) {
            e.printStackTrace();
        }
    }

}

运行:

复制代码
Someone says,Hello World!
--by ref Base Service says helloBase Base Service says hello

Process finished with exit code 0

写在后面

参考文章列表

手把手带你写一个 MiniSpring

相关推荐
来杯@Java1 小时前
学生选课管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·maven·mybatis
ltl1 小时前
推理退化:为什么大模型会输出乱码、死循环和无意义文本
后端
ltl2 小时前
架构视图与文档:C4 模型从入门到实战
后端
豆瓣鸡2 小时前
Spring Cloud笔记
spring·spring cloud
不知名的老吴2 小时前
线程的生命周期之线程“插队“
java·开发语言·python
ANnianStriver2 小时前
PetLumina-02-后端开发与前后端联调
java·ai·sa-token
云烟成雨TD3 小时前
Spring AI 1.x 系列【56】用大模型评判大模型:递归顾问实现自动化评估方案
人工智能·spring·自动化
杨了个杨89823 小时前
Keepalived + Nginx + HAProxy 高可用架构部署实战案例
java·nginx·架构
陈鋆4 小时前
Spring AI Framework(二:模块分析)
spring·ai