前言:
本博客将完全通过自己手写的代码来实现基于XML配置方式的SpringIOC轻量级框架,主要是对自己学习tiny_spring项目的记录和总结。本人水平有限,难免存在不足和错误,还请大家多多指正。
一、Spring IOC概述
IOC,即控制反转。不是一种技术,而是 Spring 的一种设计思想。
在传统的程序设计中,我们直接在对象内部通过 new XXX() 来创建对象,是程序主动去创建依赖的对象;而在Spring中提供了一种IOC容器,用来控制对象的创建,无论是创建对象,还是处理对象之间的依赖关系,都由该容器进行统一管理。
所有类的创建,销毁都由Spring来控制,即控制对象生命周期的不是引用该对象的对象,而是Spring容器。对于某个具体的对象而言,以前是它控制其他对象,现在是所有的对象都被Spring容器控制,这叫控制反转。
下面通过两张图来理解Spring IOC
二、项目结构图
三、具体实现
(1)定义配置资源文件操作相关的类,用于解决IOC容器中的内容从哪里来的问题,也就是配置文件从哪里读取、配置文件如何读取的问题。
/**
* Resource 接口,标识一个外部资源。通过 getInputStream() 方法获取资源的输入流。
*/
public interface Resource {
InputStream getInputStream() throws IOException;
}
/**
* 实现 Resource 接口的资源类,通过 URL 获取资源
*/
public class URLResource implements Resource {
private URL url;
public URLResource(URL url) {
this.url = url;
}
public InputStream getInputStream() throws IOException {
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
}
/**
* 资源加载接口,通过 getResource(String) 方法获取一个 Resouce 对象,
是获取 Resource 的主要途径
*/
public interface ResourceLoader {
Resource getResource(String config);
}
/**
* 实现 ResourceLoader 接口,获取 URLResource 对象
*/
public class UrlResourceLoader implements ResourceLoader {
public Resource getResource(String config) {
URL url = this.getClass().getClassLoader().getResource(config);
return new URLResource(url);
}
}
(2)定义了以 BeanDefinition 类为核心发散的几个类,用于解决 Bean的具体定义,包括 Bean的名字,类型,属性(值或引用)。
/**
* Bean 定义
* (1) 包括 Bean 的名字 beanName,Bean 的类型 beanClass,Bean 的属性集合 beanPropertys
*/
public class BeanDefinition {
private String beanName;
private String beanClass;
private BeanPropertys beanPropertys;
private Object bean;
public BeanDefinition(String beanName, String beanClass) {
this.beanName = beanName;
this.beanClass = beanClass;
}
public void setBeanPropertys(BeanPropertys beanPropertys) {
this.beanPropertys = beanPropertys;
}
public String getBeanName() {
return beanName;
}
public Object getBean() {
return bean;
}
public String getBeanClass() {
return beanClass;
}
public BeanPropertys getBeanPropertys() {
return beanPropertys;
}
}
/**
* 定义 Bean 属性集合,包含 beanPropertys 变量,里面包含一个个PropertyValue条目
*/
public class BeanPropertys {
// bean 属性名集合
private List<String> propNames = new ArrayList<String>();
// bean 属性集合
private List<BeanProperty> beanPropertys = new ArrayList<BeanProperty>();
/**
* 增加 BeanProperty对象(Bean属性)到 Bean 属性集合
* 如果 属性集合已经存在该 Bean 属性名,则不再次添加
* @param beanProperty
*/
public void addBeanProperty(BeanProperty beanProperty) {
String propName = beanProperty.getPropName();
if (beanPropertys.contains(propName)) {
return;
}
beanPropertys.add(beanProperty);
propNames.add(propName);
}
public boolean isEmpty() {
return beanPropertys.isEmpty();
}
public List<BeanProperty> getBeanPropertys() {
return beanPropertys;
}
}
/**
* 定义 Bean属性 类
* 每个对象都是键值对 String-Object,分别对应要生成示例的属性的名字与类型。
* 在Spring的XML中的 property中,键是 key,值是 value 或者 ref。
* 用来定义xml配置文件中的如下节点:
* <property name="desc" value="I am Print Service"></property>
* or
* <property name="helloWorldService" ref="helloWorldService"></property>
*/
public class BeanProperty {
// 属性名称
private String propName;
public String getPropName() {
return propName;
}
// 属性值或属性引用
private Object propValue;
public Object getPropValue() {
return propValue;
}
public BeanProperty(String propName, Object propValue) {
this.propName = propName;
this.propValue = propValue;
}
}
/**
* 用来定义Bean的引用,其中保存了Bean的名字,
* 需要用的时候先进行解析beanRef,再根据值获取Bean对象
*/
public class BeanReference {
private String beanRef;
public BeanReference(String beanRef) {
this.beanRef = beanRef;
}
public String getBeanRef() {
return beanRef;
}
}
(3)定义以实现 BeanDefinitionReader 核心接口的几个类,主要用于解析配置文件,构建 BeanDefinition。
/**
* @Desc 解析 BeanDefinition 的接口
* @author xiongxl
* @Date 2022/7/25
*/
public interface BeanDefinitionReader {
/**
* 通过 loadBeanDefinitions(String) 来从一个地址加载类定义
* @param location
*/
void loadBeanDefinitions(String location);
}
/**
* @Desc 实现 BeanDefinitionReader 接口的抽象类(未具体实现 loadBeanDefinitions,而是规范了 BeanDefinitionReader 的基本结构)
* @author xiongxl
* @Date 2022/7/25
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
/**
* BeanDefinitionReader 的基本机构由:
* beanDefinitionRegister :用于保存 BeanDefinition
* resourceLoader:用于保存资源加载器
* 用意在于,使用时,只需要向其 loadBeanDefinitions() 传入一个资源地址,就可以自动调用其类加载器,
* 并把解析到的 BeanDefinition 保存到 beanDefinitionRegister 中去。
*
*/
private List<BeanDefinition> beanDefinitionRegister;
private ResourceLoader resourceLoader;
public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
beanDefinitionRegister = new ArrayList<BeanDefinition>();
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
public List<BeanDefinition> getBeanDefinitionRegister() {
return beanDefinitionRegister;
}
}
/**
* @Desc 继承 AbstractBeanDefinitionReader,实现了 loadBeanDefinitions() 方法,从 XML 文件中读取类定义。
* @author xiongxl
* @Date 2022/7/25
*/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
/**
* 解析XML资源文件,读取类定义BeanDefinition,存储到 beanDefinitionRegister
* @param location
*/
public void loadBeanDefinitions(String location) {
// 根据Spring资源文件获取 URLResource
Resource resource = getResourceLoader().getResource(location);
try {
// 获取文件 InputStream
InputStream inputStream = resource.getInputStream();
parseInputStream(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 解析Spring配置文件的InputStream
* @param inputStream
* @throws Exception
*/
private void parseInputStream(InputStream inputStream) throws Exception {
// 获取 DocumentBuildFactory 对象
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 获取 DocumentBuilder 对象
DocumentBuilder db = dbf.newDocumentBuilder();
// 调用 DocumentBuilder 对象的 parse 方法 获取 Document 对象
Document document = db.parse(inputStream);
parseDocument(document);
}
/**
* 解析Spring配置文件的Document对象
* @param document
*/
private void parseDocument(Document document) {
// 获取 Document对象 的根节点
Element rootEle = document.getDocumentElement();
// 获取所有的子节点
NodeList nodeList = rootEle.getChildNodes();
if (nodeList.getLength() > 0) {
for (int i=0; i< nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) {
Element beanEle = (Element) node;
// <bean name="helloWorldService" class="study.tinyioc.service.HelloWorldService">
// 获取Bean节点的name属性值
String beanName = beanEle.getAttribute("id");
// 获取Bean节点的class属性值
String beanClass = beanEle.getAttribute("class");
// 构建 BeanDefinition
BeanDefinition bd = new BeanDefinition(beanName, beanClass);
// 获取 Bean 节点的子节点,即 Bean 的属性集合节点节点集合
NodeList beanPropertyNodes = beanEle.getElementsByTagName("property");
if (beanPropertyNodes.getLength() > 0) {
BeanPropertys beanPropertys = buildBeanPropertys(beanPropertyNodes);
if (!beanPropertys.isEmpty()) {
bd.setBeanPropertys(beanPropertys);
}
}
getBeanDefinitionRegister().add(bd);
}
}
}
}
/**
* 解析Bean的成员变量节点集合
* @param beanPropNodeList
* @return
*/
private BeanPropertys buildBeanPropertys(NodeList beanPropNodeList) {
BeanPropertys beanPropertys = new BeanPropertys();
for (int i=0; i<beanPropNodeList.getLength(); i++) {
Node propNode = beanPropNodeList.item(i);
if (propNode instanceof Element) {
Element propEle = (Element) propNode;
// <property name="city" value="JiangNing"></property> OR <property name="helloWorldService" ref="helloWorldService"></property>
// 获取 Bean 的属性名
String pn = propEle.getAttribute("name");
if (pn == null || pn.length() == 0) {
throw new IllegalArgumentException("There id an error in Spring configuration. " +
"There is a property node without name");
}
// 获取 Bean 的属性值
String pv = propEle.getAttribute("value");
if (pv != null && pv.length() > 0) {
// 如果 value 值存在,则构建 BeanProperty
BeanProperty bp = new BeanProperty(pn, pv);
beanPropertys.addBeanProperty(bp);
} else {
// 如果 value 值不存在,则尝试获取 ref 值
String propRef = propEle.getAttribute("ref");
if (propRef != null && propRef.length() > 0) {
// 如果 ref 值存在,则构建 BeanReference
BeanReference bf = new BeanReference(propRef);
BeanProperty bp = new BeanProperty(pn, bf);
beanPropertys.addBeanProperty(bp);
} else {
throw new IllegalArgumentException("Configuration problean :<property> element for " +
"property [" + pn + "] must specify a ref or value");
}
}
}
}
return beanPropertys;
}
}
(4)定义以 BeanFactory 接口为核心发散出的几个类,用于解决IOC容器在已经获取 Bean 的定义(BeanDefinition)的情况下,如何装配、获取 Bean 实例的问题。
/**
* @Desc Bean工厂接口,标识一个 IOC 容器。通过 getBean(String) 方法来 获取一个对象
* @author xiongxl
* @Date 2022/7/26
*/
public interface BeanFactory {
/**
* 根据 Bean Name 从 IOC容器 获取Bean实体
* @param beanName
* @return
*/
Object getBean(String beanName);
/**
* 注册 BeanDefinition
* @param beanDefinition
*/
void registerBeanDefinition(BeanDefinition beanDefinition);
}
/**
* @Desc BeanFactory 的一种抽象类表现,规范了IOC容器的基本结构。
* @author xiongxl
* @Date 2022/7/26
*/
public abstract class AbstractBeanFactory implements BeanFactory {
/**
* beanDefinitionMap 哈希表用于保存类的定义信息(BeanDefinition)。
*/
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
/**
* 根据 beanName 从 beanDefinitionMap 获取Bean,如果 Bean 已经存在于容器中,则返回即可,
* 如果 Bean 不存在于容器中,则调用 createBean 方法装配一个 Bean
* 注:所谓存在于容器中,是指容器可以通过 beanDefinitionMap 获取 BeanDefinition 进而通过其 getBean() 方法获取 Bean实例
* @param beanName
* @return
*/
public Object getBean(String beanName) {
if (!beanDefinitionMap.containsKey(beanName)) {
throw new RuntimeException("No Found Bean Named:[" + beanName + "]");
}
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
Object bean = beanDefinition.getBean();
if (bean == null) {
bean = createBean(beanDefinition);
}
return bean;
}
public void registerBeanDefinition(BeanDefinition beanDefinition) {
String beanName = beanDefinition.getBeanName();
if (beanDefinitionMap.containsKey(beanName)) {
return;
}
beanDefinitionMap.put(beanName, beanDefinition);
}
protected abstract Object createBean(BeanDefinition beanDefinition);
}
/**
* @Desc 可以实现自动装配的 BeanFactory
* @author xiongxl
* @Date 2022/7/26
*/
public class AutowiredCapableBeanFactory extends AbstractBeanFactory {
protected Object createBean(BeanDefinition beanDefinition) {
Object bean = null;
String beanClass = beanDefinition.getBeanClass();
try {
// 通过 Bean定义(BeanDefinition)实例化出 Bean 对象
bean = Class.forName(beanClass).newInstance();
BeanPropertys beanPropertys = beanDefinition.getBeanPropertys();
if (!beanPropertys.isEmpty()) {
loadBeanPropertys(bean, beanPropertys);
}
} catch (Exception e) {
e.printStackTrace();
}
// 把对象保存在 BeanDefinition 中,以备下次获取
beanDefinition.setBean(bean);
return bean;
}
/**
* 装配 Bean 实例的属性
* @param bean
* @param beanPropertys
* @throws Exception
*/
private void loadBeanPropertys(Object bean, BeanPropertys beanPropertys) throws Exception {
// 从 Bean定义(BeanDefinition)中获取 Bean 属性集合
List<BeanProperty> beanPropertyList = beanPropertys.getBeanPropertys();
// 遍历 Bean 属性集合
for (BeanProperty beanProperty : beanPropertyList) {
// 获取 Bean 属性名
String propName = beanProperty.getPropName();
// 获取 Bean 属性值
Object propValue = beanProperty.getPropValue();
/**
* 通过反射,把 String - Value 键值对注入到 Bean 的属性中去。
* 如果 Value 的类型是 BeanReference,则说明其是一个引用(对应于 XML 中的 ref),通过 getBean 对其进行获取,然后注入到属性中。
*/
Field field = bean.getClass().getDeclaredField(propName);
field.setAccessible(true);
if (propValue instanceof BeanReference) {
BeanReference beanReference = (BeanReference)propValue;
String beanRef = beanReference.getBeanRef();
Object refBean = getBean(beanRef);
field.set(bean, refBean);
} else {
field.set(bean, propValue);
}
}
}
}
(5)定义了以 ApplicationContext 接口为核心发散出的几个类,主要是对前面 Resource、BeanFactory、BeanDefinition 进行了功能的封装,解决 根据地址获取IOC容器并使用的问题。
/**
* @Desc 标记接口,继承了 BeanFactory。通常,要实现一个IOC容器时,需要先通过 ResourceLoader 获取一个 Resource,其中包括了容器的配置、Bean的定义信息。
* 接着,使用 BeanDefinitionReader 读取该 Resource 中的 BeanDefinition 信息。最后,把 BeanDefinition 保存在 BeanFactory 中,容器配置完毕可以使用。
* 注意到 BeanFactory 只是实现了 Bean 的装配、获取,并未说明 Bean 的来源 也就是 BeanDefinition 是如何加载的。该接口把 BeanFactory 和 BeanDefinitionReader 结合在一起了。
* @author xiongxl
* @Date 2022/7/27
*/
public interface ApplicationContext extends BeanFactory {
}
/**
* @Desc ApplicationContext 的抽象实现,内部包含一个 BeanFactory 类。主要方法有 getBean() 和 refresh() 方法。
* @author xiongxl
* @Date 2022/7/27
*/
public abstract class AbstractApplicationContext implements ApplicationContext {
protected AbstractBeanFactory beanFactory;
public AbstractApplicationContext(AbstractBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* refresh() 则用于实现 BeanFactory 的刷新,也就是告诉 BeanFactory 该使用哪个资源(Resource)加载类定义(BeanDefinition)信息
* 该方法留给了子类实现,用以实现从不通来源的不同类型的资源加载类定义的效果
*/
protected abstract void refresh();
/**
* getBean() 直接调用了内置 BeanFactory 的 getBean() 方法
* @param beanName
* @return
*/
public Object getBean(String beanName) {
return beanFactory.getBean(beanName);
}
}
/**
* @Desc 从类路径加载资源的具体实现类。
* @author xiongxl
* @Date 2022/7/27
*/
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
private String springConfig;
public ClassPathXmlApplicationContext(String springConfig) {
super(new AutowiredCapableBeanFactory());
this.springConfig = springConfig;
refresh();
}
/**
* 通过 XmlBeanDefinitionReader 解析 UrlResourceLoader 读取到 Resource,获取 BeanDefinition 信息,然后将其保存在内置的 BeanFactory 中。
*/
@Override
protected void refresh() {
ResourceLoader resourceLoader = new UrlResourceLoader();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(resourceLoader);
xmlBeanDefinitionReader.loadBeanDefinitions(this.springConfig);
List<BeanDefinition> beanDefinitionList = xmlBeanDefinitionReader.getBeanDefinitionRegister();
for (BeanDefinition beanDefinition : beanDefinitionList) {
beanFactory.registerBeanDefinition(beanDefinition);
}
}
}
(6)Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean id="helloWorldService" class="study.tinyioc.service.HelloWorldService">
<property name="city" value="Nanjing"></property>
<property name="desc" value="我爱南京"></property>
</bean>
<bean id="printService" class="study.tinyioc.service.PrintService">
<property name="desc" value="I am Print Service"></property>
<property name="helloWorldService" ref="helloWorldService"></property>
</bean>
</beans>
(7)Service类
public class HelloWorldService {
private String city;
private String desc;
public void setCity(String city) {
this.city = city;
}
public void setDesc(String desc) {
this.desc = desc;
}
public void say() {
System.out.println("城市:[" + city + "] " + desc);
}
}
public class PrintService {
private String desc;
private HelloWorldService helloWorldService;
public void setDesc(String desc) {
this.desc = desc;
}
public void setHelloWorldService(HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}
public void print() {
System.out.println(desc);
helloWorldService.say();
}
}
(8)测试类
@Test
public void testContext() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
PrintService printService = (PrintService) applicationContext.getBean("printService");
printService.print();
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.say();
}
源码下载:git@gitee.com:xiong-xianliang/study-tiny-spring.git
由于本人水平有限,本博客可能存在不足和错误在所难免,还请大家多多指正。