写在前面
本文来继续增加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