一、前言
在上一篇手撸Spring之实现一个简易版IoC容器,我们先把最简单的IoC架子搭了起来,即实现了一个Bean的注入,体会了依赖反转的过程。这里提到的依赖反转,大家肯定非常耳熟了,也就是将业务对Bean的依赖反转了,往常我们在使用一个Bean的时候,会在我们的业务代码中通过new创建一个Bean实例,而在IoC框架里,new创建的动作交给了框架来实现并注入到业务代码中去交给业务使用,如下图所示:
接下来,我们在前一篇的基础上,进一步丰富框架的功能:当一个类中有多个属性时,在xml配置上bean字段内容,框架实现对字段的注入。
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<!--setter 注入-->
<bean id="a1" class="com.tiny.spring.test.bean.A">
<property name="property1" value="property1"/>
<property name="property2" value="property2"/>
<property name="property3" value="property3"/>
<property type="Integer" name="age" value="24"/>
</bean>
<!--构造器 注入-->
<bean id="a2" class="com.tiny.spring.test.bean.A">
<constructor-arg name="property1" value="property1"/>
<constructor-arg name="property2" value="property2"/>
<constructor-arg name="property3" value="property3"/>
<property type="Integer" name="age" value="24"/>
</bean>
</beans>
二、属性注入的实现原理
在Spring框架,实现属性注入的方式由以下两种:
- 构造器注入
- setter方法注入
下面我们介绍下两种注入方式的原理。
1、构造器注入
简单来讲,实现构造器注入的过程就是框架对xml中<constructor-arg>标签解析、将该标签下的元数据和ConstructorArgumentValues对象建立关系,将其设置到BeanDefinition中,并在createBean时,通过反射的方式调用对应的构造器函数创建实例。
2、setter方法注入
和构造器注入原理类似,框架解析<property>标签数据,将其和PropertyValues建立映射关系并设置到BeanDefinition中,在createBean时,通过反射的方式调用对应的setXxx方法实现参数注入。
3、理论正确性验证
我们进入到源码中也可以看到,事实确实是如此:(ps: spring框架实现的要远复杂很多,我们只关注核心的流程)
3、Bean之间的依赖
我们前面提的属性都是普通属性,也就是基本类型+String,当涉及到其他引用类型需要注入时,Spring又是如何处理的?实际上,Spring设计的很巧妙,它在标签中增加了ref属性,这个属性就记了需要引用的另一个Bean,如下xml文件所示:
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<bean id="aService" class="com.tiny.spring.test.service.impl.AServiceImpl">
<property type="com.tiny.spring.test.bean.A" name="a" ref="a1"/>
</bean>
<bean id="a1" class="com.tiny.spring.test.bean.A">
<property name="property1" value="property1"/>
<property name="property2" value="property2"/>
<property name="property3" value="property3"/>
<property type="Integer" name="age" value="24"/>
</bean>
</beans>
Spring在加载xml文件时,会对ref属性做特殊处理,会对ref指定的bean做一次getBean(xx)操作,进而获取对应的Bean实例并注入到当前实例参数中。
循环依赖
如上所述,我们在处理Bean之间依赖的问题时,做这样的处理:
- 获取实例A,发现容器中没有A的实例,则进入到实例A创建阶段
- 创建A实例后,进行A实例属性填充时,发现其依赖B,则进入到获取B实例的阶段
- 获取实例B,发现容器中没有B的实例,则进入到实例B创建阶段
- 创建B实例后,进行B实例属性填充时,发现其又依赖A,则进入到获取A实例的阶段
- …
这样一个操作,我们发现循环了,这可蛋疼了!这个问题也是我们经常听到的循环依赖。那么Spring是如何解决的呢?
这就引入了三级缓存的概念:
- 一级缓存:存储最终的对象实例
- 二级缓存:存储早期对象引用,就是创建完实例后填充实例前
- 三级缓存:存储对象工厂(这个我们后面涉及到AOP时,再做说明)
其实在针对一般场景(即两个普通Bean对象间的循环依赖),二级缓存就够了。我们本节实现的代码也仅是实现了二级缓存,第三级缓存我们在AOP环节再做介绍和补充。也不能说AOP环节,在BeanPostProcessor实现时就会介入,而AOP功能的实现也依赖于BeanPostProcessor。
三、代码实现
1、流程图
针对本节要实现的功能,给出下面简图,让大家有个简单的了解:
2、相关Bean
BeanDefinition
package com.tiny.spring.beans.factory.config;
import com.tiny.spring.beans.factory.ArgumentValues;
import com.tiny.spring.beans.factory.PropertyValues;
/**
* @author: markus
* @date: 2023/10/7 8:14 PM
* @Description: Bean配置元信息
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class BeanDefinition {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
/**
* 是否是懒加载
*/
private boolean lazyInit = false;
/**
*
*/
private String[] dependsOn;
private ArgumentValues constructorArgumentValues;
private PropertyValues propertyValues;
private String initMethodName;
private volatile Object beanClass;
private String id;
private String className;
private String scope = SCOPE_SINGLETON;
// 构造器、setter、getter方法省略
}
PropertyValue
package com.tiny.spring.beans.factory;
/**
* @author: markus
* @date: 2023/10/8 11:04 PM
* @Description:
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class PropertyValue {
private final String type;
private final String name;
private final Object value;
private final boolean isRef;
// 构造器、setter、getter方法省略
}
PropertyValues
package com.tiny.spring.beans.factory;
import java.util.ArrayList;
import java.util.List;
/**
* @author: markus
* @date: 2023/10/8 11:17 PM
* @Description: Bean属性对象集合抽象
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class PropertyValues {
private final List<PropertyValue> propertyValueList;
public PropertyValues() {
this.propertyValueList = new ArrayList<>(0);
}
public List<PropertyValue> getPropertyValueList() {
return this.propertyValueList;
}
public int size() {
return this.propertyValueList.size();
}
public void addPropertyValue(PropertyValue pv) {
this.propertyValueList.add(pv);
}
// public void addPropertyValue(String propertyName, Object propertyValue) {
// addPropertyValue(new PropertyValue(propertyName, propertyValue));
// }
public void removePropertyValue(PropertyValue pv) {
this.propertyValueList.remove(pv);
}
public void removePropertyValue(String propertyName) {
this.propertyValueList.remove(getPropertyValue(propertyName));
}
public PropertyValue[] getPropertyValues() {
return this.propertyValueList.toArray(new PropertyValue[0]);
}
public PropertyValue getPropertyValue(String propertyName) {
for (PropertyValue pv : this.propertyValueList) {
if (pv.getName().equals(propertyName))
return pv;
}
return null;
}
public Object get(String propertyName) {
PropertyValue pv = getPropertyValue(propertyName);
return pv != null ? pv.getValue() : null;
}
public boolean contains(String propertyName) {
return getPropertyValue(propertyName) != null;
}
public boolean isEmpty() {
return this.propertyValueList.isEmpty();
}
}
ArgumentValue
package com.tiny.spring.beans.factory;
/**
* @author: markus
* @date: 2023/10/8 11:02 PM
* @Description: 属性值抽象
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class ArgumentValue {
private Object value;
private String type;
private String name;
}
ArgumentValues
package com.tiny.spring.beans.factory;
import com.sun.istack.internal.Nullable;
import java.util.*;
/**
* @author: markus
* @date: 2023/10/8 11:06 PM
* @Description: 构造器参数集合抽象
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class ArgumentValues {
private final List<ArgumentValue> argumentValueList = new ArrayList<>();
public ArgumentValues() {
}
public void addArgumentValue(ArgumentValue argumentValue) {
this.argumentValueList.add(argumentValue);
}
public ArgumentValue getIndexedArgumentValue(int index) {
ArgumentValue argumentValue = this.argumentValueList.get(index);
return argumentValue;
}
public int getArgumentCount() {
return (this.argumentValueList.size());
}
public boolean isEmpty() {
return (this.argumentValueList.isEmpty());
}
}
XmlBeanDefinitionReader
package com.tiny.spring.beans.factory.xml;
import com.sun.media.sound.RIFFReader;
import com.tiny.spring.beans.factory.*;
import com.tiny.spring.beans.factory.config.BeanDefinition;
import com.tiny.spring.beans.factory.support.SimpleBeanFactory;
import com.tiny.spring.core.io.Resource;
import org.dom4j.Element;
import java.util.ArrayList;
import java.util.List;
/**
* @author: markus
* @date: 2023/10/7 8:50 PM
* @Description:
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class XmlBeanDefinitionReader {
SimpleBeanFactory beanFactory;
public XmlBeanDefinitionReader(SimpleBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void loadBeanDefinitions(Resource resource) {
while (resource.hasNext()) {
Element element = (Element) resource.next();
String beanId = element.attributeValue("id");
String className = element.attributeValue("class");
BeanDefinition beanDefinition = new BeanDefinition(beanId, className);
// 处理属性
List<Element> propertyElements = element.elements("property");
PropertyValues pvs = new PropertyValues();
// 存储属性中的引用对象 bean的id
List<String> refs = new ArrayList<>();
for (Element propertyElement : propertyElements) {
String pType = propertyElement.attributeValue("type");
String pName = propertyElement.attributeValue("name");
String pValue = propertyElement.attributeValue("value");
String pRef = propertyElement.attributeValue("ref");
String pV = "";
boolean isRef = false;
if (pValue != null && !pValue.equals("")) {
pV = pValue;
} else if (pRef != null && !pRef.equals("")) {
isRef = true;
pV = pRef;
refs.add(pRef);
}
pvs.addPropertyValue(new PropertyValue(pType, pName, pV, isRef));
}
beanDefinition.setPropertyValues(pvs);
String[] refArray = refs.toArray(new String[0]);
beanDefinition.setDependsOn(refArray);
// 处理构造器参数
List<Element> constructorArgumentElements = element.elements("constructor-arg");
ArgumentValues avs = new ArgumentValues();
for (Element constructorArgumentElement : constructorArgumentElements) {
String aType = constructorArgumentElement.attributeValue("type");
String aName = constructorArgumentElement.attributeValue("name");
String aValue = constructorArgumentElement.attributeValue("value");
avs.addArgumentValue(new ArgumentValue(aValue, aType, aName));
}
beanDefinition.setConstructorArgumentValues(avs);
this.beanFactory.registerBeanDefinition(beanId, beanDefinition);
}
}
}
BeanDefinitionRegistry
package com.tiny.spring.beans.factory.support;
import com.tiny.spring.beans.factory.config.BeanDefinition;
/**
* @author: markus
* @date: 2023/10/7 11:57 PM
* @Description: BeanDefinition注册器
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public interface BeanDefinitionRegistry {
void registerBeanDefinition(String name, BeanDefinition bd);
void removeBeanDefinition(String name);
BeanDefinition getBeanDefinition(String name);
boolean containsBeanDefinition(String name);
}
DefaultBeanDefinitionRegistry
package com.tiny.spring.beans.factory.support;
import com.tiny.spring.beans.factory.config.SingletonBeanRegistry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: markus
* @date: 2023/10/7 11:43 PM
* @Description:
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
protected List<String> beanNames = new ArrayList<>();
/*并发安全的单例Bean操作,保证容器中的Bean唯一*/
protected Map<String, Object> singletons = new ConcurrentHashMap<>(256);
/*早期Bean实例引用(实例被创建但还未被初始化),提前暴露,解决简单的循环依赖问题*/
protected final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
@Override
public void registerSingleton(String beanName, Object singletonObject) {
synchronized (this.singletons) {
this.singletons.put(beanName, singletonObject);
this.beanNames.add(beanName);
}
}
@Override
public Object getSingleton(String beanName) {
return this.singletons.get(beanName);
}
@Override
public boolean containsSingleton(String beanName) {
return this.singletons.containsKey(beanName);
}
@Override
public String[] getSingletonNames() {
return this.beanNames.toArray(new String[0]);
}
protected void removeSingleton(String beanName) {
synchronized (this.singletons) {
this.beanNames.remove(beanName);
this.singletons.remove(beanName);
}
}
}
SingletonBeanRegistry
package com.tiny.spring.beans.factory.config;
/**
* @author: markus
* @date: 2023/10/7 11:40 PM
* @Description: 单例Bean注册器
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public interface SingletonBeanRegistry {
/**
* 单例Bean注册
* @param beanName
* @param singletonObject
*/
void registerSingleton(String beanName, Object singletonObject);
/**
* 获取单例对象
* @param beanName
* @return
*/
Object getSingleton(String beanName);
/**
* 是否包含某个单例Bean
* @param beanName
* @return
*/
boolean containsSingleton(String beanName);
/**
* 获取单例BeanName集合
* @return
*/
String[] getSingletonNames();
}
SimpleBeanFactory
package com.tiny.spring.beans.factory.support;
import com.tiny.spring.beans.BeansException;
import com.tiny.spring.beans.factory.*;
import com.tiny.spring.beans.factory.config.BeanDefinition;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: markus
* @date: 2023/10/7 8:52 PM
* @Description:
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class SimpleBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory, BeanDefinitionRegistry {
/* bean definition map*/
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
private List<String> beanDefinitionNames = new ArrayList<>();
public SimpleBeanFactory() {
}
@Override
public Object getBean(String beanName) throws BeansException {
// 先尝试直接拿bean实例
Object singleton = getSingleton(beanName);
if (singleton == null) {
// 如果没有实例,则尝试从早期Bean引用缓存中去获取一下 todo 先在这里获取下,后续会像spring源码一样 抽象到DefaultSingletonBeanRegistry中去。
singleton = this.earlySingletonObjects.get(beanName);
if (singleton == null) {
// 如果早期Bean引用缓存中还是没有,那就老老实实创建实例吧
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new BeansException("The bean name does not exit. bean name [" + beanName + "]");
} else {
// 获取Bean的定义
try {
singleton = createBean(beanDefinition);
} catch (BeansException e) {
throw e;
}
// 注册最终成形的Bean实例
this.registerSingleton(beanDefinition.getId(), singleton);
}
}
}
return singleton;
}
@Override
public boolean containsBean(String beanName) {
return containsSingleton(beanName);
}
@Override
public boolean isSingleton(String beanName) {
// todo 这里可能会抛空指针
return getBeanDefinition(beanName).isSingleton();
}
@Override
public boolean isPrototype(String beanName) {
// todo 这里可能会抛空指针
return getBeanDefinition(beanName).isPrototype();
}
@Override
public Class<?> getType(String beanName) {
// todo 这里可能会抛空指针
return getBeanDefinition(beanName).getClass();
}
@Override
public void registerBeanDefinition(String name, BeanDefinition bd) {
this.beanDefinitionMap.put(name, bd);
this.beanDefinitionNames.add(name);
if (!bd.isLazyInit()) {
try {
getBean(name);
} catch (BeansException e) {
e.printStackTrace();
}
}
}
@Override
public void removeBeanDefinition(String name) {
this.beanDefinitionMap.remove(name);
this.beanDefinitionNames.remove(name);
this.removeSingleton(name);
}
@Override
public BeanDefinition getBeanDefinition(String name) {
return this.beanDefinitionMap.get(name);
}
@Override
public boolean containsBeanDefinition(String name) {
return this.beanDefinitionMap.containsKey(name);
}
private Object createBean(BeanDefinition beanDefinition) throws BeansException {
Class<?> clazz = null;
// 创建实例
Object obj = doCreateBean(beanDefinition);
// 将创建好的实例 存储到早期Bean引用缓存中
this.earlySingletonObjects.put(beanDefinition.getId(), obj);
try {
clazz = Class.forName(beanDefinition.getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 处理属性
handleProperties(beanDefinition, clazz, obj);
return obj;
}
/**
* 仅仅是创建实例对象,并没有赋值
*
* @param bd
* @return
* @throws BeansException
*/
private Object doCreateBean(BeanDefinition bd) throws BeansException {
Class<?> clazz = null;
Object obj = null;
Constructor<?> constructor = null;
try {
clazz = Class.forName(bd.getClassName());
// 处理构造器参数
ArgumentValues argumentValues = bd.getConstructorArgumentValues();
// 如果有参数
if (!argumentValues.isEmpty()) {
Class<?>[] paramTypes = new Class<?>[argumentValues.getArgumentCount()];
Object[] paramValues = new Object[argumentValues.getArgumentCount()];
// 对每一个参数,分数据类型分别处理
for (int i = 0; i < argumentValues.getArgumentCount(); i++) {
ArgumentValue argumentValue = argumentValues.getIndexedArgumentValue(i);
if ("String".equals(argumentValue.getType()) || "java.lang.String".equals(argumentValue.getType())) {
paramTypes[i] = String.class;
paramValues[i] = argumentValue.getValue();
} else if ("Integer".equals(argumentValue.getType()) || "java.lang.Integer".equals(argumentValue.getType())) {
paramTypes[i] = Integer.class;
paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
} else if ("int".equals(argumentValue.getType())) {
paramTypes[i] = int.class;
paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
} else { //默认为string
paramTypes[i] = String.class;
paramValues[i] = argumentValue.getValue();
}
}
// 按照特定构造器创建实例
constructor = clazz.getConstructor(paramTypes);
obj = constructor.newInstance(paramValues);
} else {
// 如果没有构造器参数,则直接使用默认构造器创建实例即可
obj = clazz.newInstance();
}
} catch (Exception e) {
throw new BeansException("bean constructor invoke error,bean id is" + bd.getId());
}
return obj;
}
private void handleProperties(BeanDefinition bd, Class<?> clazz, Object obj) throws BeansException {
// 处理属性
System.out.println("handle properties for bean : " + bd.getId());
// 如果有属性值的话,进行属性赋值
PropertyValues pvs = bd.getPropertyValues();
if (!pvs.isEmpty()) {
for (int i = 0; i < pvs.getPropertyValues().length; i++) {
// 对每一个属性,分数据类型分别处理
PropertyValue propertyValue = pvs.getPropertyValues()[i];
String pType = propertyValue.getType();
String pName = propertyValue.getName();
Object pValue = propertyValue.getValue();
boolean isRef = propertyValue.isRef();
Class[] paramTypes = new Class[1];
Object[] paramValues = new Object[1];
if (!isRef) {
// 如果不是 ref,只是普通属性,则对该属性分数据类型处理
if ("String".equals(pType) || "java.lang.String".equals(pType)) {
paramTypes[0] = String.class;
paramValues[0] = pValue;
} else if ("Integer".equals(pType) || "java.lang.Integer".equals(pType)) {
paramTypes[0] = Integer.class;
paramValues[0] = Integer.valueOf((String) pValue);
} else if ("int".equals(pType)) {
paramTypes[0] = int.class;
paramValues[0] = Integer.valueOf((String) pValue);
} else { // 默认为string
paramTypes[0] = String.class;
paramValues[0] = pValue;
}
} else {
// 是 ref,则通过 getBean()方式获取引用Bean实例
try {
paramTypes[0] = Class.forName(pType);
paramValues[0] = getBean((String) pValue);
} catch (ClassNotFoundException e) {
throw new BeansException("ref bean " + pValue + " not found!");
}
}
// 按照setXxx规范查找setter方法,调用setter方法设置属性
String methodName = "set" + pName.substring(0, 1).toUpperCase() + pName.substring(1);
Method method = null;
try {
method = clazz.getMethod(methodName, paramTypes);
method.invoke(obj, paramValues);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new BeansException("bean setter method invoke error,bean id is " + bd.getId() + ",method is " + methodName);
}
}
}
}
}
四、本文总结
好了,本文介绍了构造器注入、setter方法注入的实现原理、Bean依赖的问题以及循环依赖问题Spring是如何做的,基于原理我们实现了一个非常简单的属性注入功能,目前类结构还不复杂,大家可以在我的github项目tiny-spring中直接查看,有不明白的地方也可以评论我。