Spring源码阅读目录
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第二章 Spring源码阅读之假如让我来写IOC容器——加载资源篇 已经说到了如何通过协议加载不同路径下的资源,接下来继续看下如何解析配置文件吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大要求A君在一周内开发个简单的IOC容器
前情提要:在A君提交代码后,老大提出配置文件可是有多种多样的,目前这个只支持XML,如果是Properties、YML、JSON的呢?
第四版 抽取配置文件接口
A君骂骂咧咧的回到了工位:“XML不香吗?还要支持别的”,独自生了会儿闷气。A君又开始思考了,混口饭吃,不容易啊
既然是想要多种实现,老样子,还是定一个接口BeanDefinitionReader,用来读取不同配置文件,接口代码如下:
import com.hqd.ch03.v4.io.Resource;
/**
* 读取配置文件
*/
public interface BeanDefinitionReader {
void loadBeanDefinition(Resource resource);
}
接口定义完成之后,就开始定义具体实现了。先是解析XML的配置文件,代码没什么变动,只不过换了个地方。代码如下:
import com.hqd.ch03.v4.io.Resource;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.List;
import java.util.Map;
/**
* 解析xml配置
*/
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
private Map<String, Object> bdMap;
public XmlBeanDefinitionReader(Map<String, Object> bdMap) {
this.bdMap = bdMap;
}
private void initBean(Object bean, List<Element> properties) {
try {
properties.forEach(property -> {
String name = property.attributeValue("name");
String value = property.attributeValue("value");
try {
BeanUtils.setProperty(bean, name, value);
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private Object createBean(String className) {
try {
return Class.forName(className).getConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void loadBeanDefinition(Resource resource) {
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resource.getInputStream());
Element rootElement = document.getRootElement();
List<Element> beanEles = rootElement.elements("bean");
beanEles.forEach(beanEle -> {
String className = beanEle.attributeValue("class");
Object bean = createBean(className);
List<Element> properties = beanEle.elements("property");
if (CollectionUtils.isNotEmpty(properties)) {
initBean(bean, properties);
}
bdMap.put(beanEle.attributeValue("id"), bean);
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
关于Properties的,这里偷个懒,先不实现了,如下:
import com.hqd.ch03.v4.io.Resource;
/**
* 解析Properties配置
*/
public class PropertiesBeanDefinitionReader implements BeanDefinitionReader {
@Override
public void loadBeanDefinition(Resource resource) {
//TODO
}
}
由于配置文件有了多种实现,所以SpringImitation类也需要多种实现,先抽取公共实现为父类,如下:
import com.hqd.ch03.v4.io.DefaultResourceLoader;
import com.hqd.ch03.v4.io.ResourceLoader;
import java.util.HashMap;
import java.util.Map;
public abstract class SpringImitationV4 {
protected Map<String, Object> bdMap = new HashMap<>();
/**
* 资源加载器
*/
protected ResourceLoader resourceLoader = new DefaultResourceLoader();
public <T> T getBean(String name, Class<T> clazz) {
return (T) bdMap.get(name);
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
提取完父类,接下来是XML配置的实现,如下:
import com.hqd.ch03.v4.reader.XmlBeanDefinitionReader;
public class SpringImitationV4Xml extends SpringImitationV4 {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bdMap);
public SpringImitationV4Xml(String location) {
this.reader.loadBeanDefinition(resourceLoader.getResource(location));
}
}
再来个Properties的实现,如下:
import com.hqd.ch03.v4.reader.PropertiesBeanDefinitionReader;
public class SpringImitationV4Property extends SpringImitationV4 {
private PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader();
public SpringImitationV4Property(String location) {
this.reader.loadBeanDefinition(resourceLoader.getResource(location));
}
}
接着做个测试,测试代码如下:
/**
* 添加配置文件读取接口
*/
@Test
public void v4() {
System.out.println("############# 第四版:简化资源接口使用 #############");
SpringImitationV4 bean = new SpringImitationV4Xml("classpath:v4/bean.xml");
User user = bean.getBean("user", User.class);
System.out.println("基于类路径加载:" + user);
SpringImitationV4 bean1 = new SpringImitationV4Property("file:E:/代码/java/JavaEE/spring-imitation/src/main/resources/v4/bean.xml");
User user1 = bean1.getBean("user", User.class);
System.out.println("基于文件系统路径加载:" + user1);
}
输出如下:
捣鼓了半天,A君多少松了一口气,总算可以交差了。这回A君已经做好心里准备了,颤抖着双手把代码提交上去,坐等老大批评(开摆)
老大又看了遍代码,语重心长的说:A君,你没有将bean定义提取出来,假如用户想在运行时修改bean定义,这时候怎么办?拿回去优化下吧
第五版 提取Bean配置信息
得!这个老大不是个好糊弄的主,继续优化吧
A君看着代码,琢磨了一会儿,心想:既然运行时可能修改bean定义,那就先把Bean定义缓存缓存起来,等到用的时候在缓存里边取就行了
于是,A君定义了BeanDefinition类用来存储Bean定义,代码如下:
import lombok.Data;
/**
* Bean配置信息类
*/
@Data
public class BeanDefinition {
private String id;
private String beanClass;
private String scope;
private Boolean lazyInit;
private String initMethodName;
private String destroyMethodName;
private String description;
private MutablePropertyValues properties;
}
Bean配置信息的实体类有了,还需要个地方缓存这个Bean信息才行。考虑到缓存可能有多种实现,A君又定义了BeanDefinitionRegistry接口,代码如下:
import com.hqd.ch03.v5.config.BeanDefinition;
/**
* 缓存Bean定义信息
*/
public interface BeanDefinitionRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
void removeBeanDefinition(String beanName);
BeanDefinition getBeanDefinition(String beanName);
boolean containsBeanDefinition(String beanName);
}
有了BeanDefinitionRegistry接口之后还需要提供实现,默认用map实现即可,代码如下:
import com.hqd.ch03.v5.config.BeanDefinition;
import java.util.HashMap;
import java.util.Map;
/**
* Bean信息缓存,map实现
*/
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}
@Override
public void removeBeanDefinition(String beanName) {
beanDefinitionMap.remove(beanName);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) {
return beanDefinitionMap.get(beanName);
}
@Override
public boolean containsBeanDefinition(String beanName) {
return beanDefinitionMap.containsKey(beanName);
}
}
XmlBeanDefinitionReader需要新增构造器,用来接收传入的BeanDefinitionRegistry ,改造如下:
完整代码:
import com.hqd.ch03.v5.config.BeanDefinition;
import com.hqd.ch03.v5.config.MutablePropertyValues;
import com.hqd.ch03.v5.io.Resource;
import com.hqd.ch03.v5.registry.BeanDefinitionRegistry;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.List;
/**
* XML方式读取Bean定义
*/
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
private BeanDefinitionRegistry beanDefinitionRegistry;
public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
@Override
public void loadBeanDefinition(Resource resource) {
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resource.getInputStream());
Element rootElement = document.getRootElement();
List<Element> beanEles = rootElement.elements("bean");
beanEles.forEach(beanEle -> {
BeanDefinition bd = parseBeanEle(beanEle);
beanDefinitionRegistry.registerBeanDefinition(bd.getId(), bd);
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 转换bean节点
*
* @param element
* @return
*/
private BeanDefinition parseBeanEle(Element element) {
BeanDefinition bd = new BeanDefinition();
bd.setScope(element.attributeValue("scope"));
bd.setId(element.attributeValue("id"));
String lazyInit = element.attributeValue("lazy-init");
bd.setLazyInit(StringUtils.isBlank(lazyInit) ? true : Boolean.valueOf(lazyInit));
bd.setBeanClass(element.attributeValue("class"));
bd.setProperties(parsePropertyEles(element.elements("property")));
return bd;
}
/**
* 转换bean下的property节点
*
* @param elements
* @return
*/
private MutablePropertyValues parsePropertyEles(List<Element> elements) {
if (CollectionUtils.isNotEmpty(elements)) {
MutablePropertyValues propertyValues = new MutablePropertyValues();
elements.forEach(element -> {
propertyValues.addProperty(element.attributeValue("name"), element.attributeValue("value"));
});
return propertyValues;
}
return null;
}
}
SpringImitation需要提供BeanDefinitionRegistry属性,创建bean时从缓存获取获取Bean配置信息。改造如下:
完整代码:
import com.hqd.ch03.v5.config.BeanDefinition;
import com.hqd.ch03.v5.config.MutablePropertyValues;
import com.hqd.ch03.v5.io.DefaultResourceLoader;
import com.hqd.ch03.v5.io.ResourceLoader;
import com.hqd.ch03.v5.registry.BeanDefinitionRegistry;
import com.hqd.ch03.v5.registry.SimpleBeanDefinitionRegistry;
import org.apache.commons.beanutils.BeanUtils;
public abstract class SpringImitationV5 {
protected BeanDefinitionRegistry beanDefinitionRegistry = new SimpleBeanDefinitionRegistry();
/**
* 资源加载器
*/
protected ResourceLoader resourceLoader = new DefaultResourceLoader();
private void initBean(Object bean, BeanDefinition bd) {
try {
MutablePropertyValues properties = bd.getProperties();
properties.getProperties().keySet().forEach(name -> {
String value = properties.getValue(name);
try {
BeanUtils.setProperty(bean, name, value);
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private Object createBean(BeanDefinition bd) {
try {
String beanClass = bd.getBeanClass();
Object o = Class.forName(beanClass).getConstructor().newInstance();
initBean(o, bd);
return o;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public <T> T getBean(String beanName, Class<T> clazz) {
BeanDefinition bd = beanDefinitionRegistry.getBeanDefinition(beanName);
return (T) createBean(bd);
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
SpringImitationXml也需要改造下,创建XmlBeanDefinitionReader时候,需要传入Bean配置注册器,改造如下:
完整代码:
import com.hqd.ch03.v5.reader.XmlBeanDefinitionReader;
public class SpringImitationV5Xml extends SpringImitationV5 {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanDefinitionRegistry);
public SpringImitationV5Xml(String location) {
this.reader.loadBeanDefinition(resourceLoader.getResource(location));
}
}
做个简单的测试,测试代码如下:
/**
* 提取Bean配置信息
*/
@Test
public void v5() {
System.out.println("############# 第五版:提取Bean配置信息 #############");
SpringImitationV5 bean = new SpringImitationV5Xml("classpath:v5/bean.xml");
User user = bean.getBean("user", User.class);
System.out.println("基于类路径加载:" + user);
}
输出如下:
做完这些,A君又看几遍,觉得没什么问题后,再次把代码提交上去。他不太相信老大还能提出问题
老大这次没有马上提出问题,过了许久,才缓缓说道:XmlBeanDefinitionReader这个类做的事太多了,不论是Document的加载,解析,还是注册BeanDefinition都在里边做,类太过臃肿了。而且把XML文件加载成DOM可以有多种方式,你又把他写死了
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)