每一个做web的同学都离不开spring,spring的设计思想博大而精深。LK在开发过程中也都是在使用spring的功能和各种配置,其实并不理解spring是如何管理bean的。最近LK在学习的springboot的时候不仅对springboot简化的配置感到惊叹,同时也感到有一点惶恐,如此高度的封装虽然简化了开发配置,提高了生产效率。但只是停留在了会用的程度,所谓知其然不知其所以然,遇到问题只能百度解决。所以下决心从spring的源码下手,看看其底层实现。所谓站在巨人的肩上,事办功倍。
感谢各位大佬和前辈,spring的源码真的太多,没有主线,自己看真的是自寻死路。当然和spring中使用设计模式和高度封装有关。
好了废话不多说,来看看spring核心IOC(控制反转)
IOC主要干了两件事
- 控制bean的生命周期。
- 处理对象间的关系。
注入IOC容器中的bean各种各样,自带属性也不相同,类与类之间的关系也错综复杂,在没有IOC管理bean之前,我们只能通过java的四大特性去维护这些关系。好了IOC的出现使我们从复杂的bean关系中解脱,一切交给容器自身去维护。可以说bean的吃喝拉撒都交给了IOC负责,当然我们这个大保姆也十分有耐心去照顾每一个bean儿子,从生老病死,精心呵护它们实现各自的使命。
LK在具体查看spring源码之前,先通过一个自己实现的的例子来看看IOC在对bean管理时都做了什么。麻雀虽小但五脏俱全。
- 首先定位bean配置文件,相信大家学习spring时都使用过,就不详细说了。
- 将配置文件转换为IO流。
- 将IO流转化为Document对象。
- 通过解析Document对象元素,获取定义的bean.
- 使用HashMap缓存bean对象。
- 通过Key在HashMap中获取指定的bean.
源码:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 1.根据 xml 配置文件加载相关 bean
*
* @author yrz
*
*/
public class SimpleIOC {
private Map<String, Object> beanMap = new HashMap<>();
SimpleIOC(String path) throws Exception {
loadBeans(path);
}
private void loadBeans(String path) throws Exception {
// 1.加载配置文件xml
// 读取xml配置文件
InputStream inputStream = new FileInputStream(path);
//调用 DocumentBuilderFactory.newInstance() 方法得到创建 DOM 解析器的工厂
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance() ;
// 调用工厂对象的 newDocumentBuilder方法得到 DOM 解析器对象。
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//调用 DOM 解析器对象的 parse() 方法解析 XML 文档,得到代表整个文档的 Document 对象,进行可以利用DOM特性对整个XML文档进行操作了。
org.w3c.dom.Document docurment = documentBuilder.parse(inputStream);
//得到 XML 文档的根节点
Element element = docurment.getDocumentElement();
//得到节点的子节点
NodeList nodeList = element.getChildNodes();
//遍历bean标签
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if(node instanceof Element) {
Element ele = (Element)node;
String id = ele.getAttribute("id");
String className = ele.getAttribute("class");
// 加载 beanClass
Class beanClass = Class.forName(className);
//创建bean
Object object = beanClass.newInstance();
// 遍历 <property> 标签
NodeList propertyNodeList = ele.getElementsByTagName("property");
for(int j = 0 ; j < propertyNodeList.getLength() ; j++) {
Node proNode = propertyNodeList.item(j);
if( proNode instanceof Element) {
Element propertyElement = (Element) proNode;
String name = propertyElement.getAttribute("name");
String value = propertyElement.getAttribute("value");
// 利用反射将 bean 相关字段访问权限设为可访问
Field field = object.getClass().getDeclaredField(name);
field.setAccessible(true);
if(value != null && value.length() > 0) {
// 将属性值填充到相关字段中
field.set(object, value);
}else {
String ref = propertyElement.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalArgumentException("ref config error");
}
// 将引用填充到相关字段中
field.set(object, getBean(ref));
}
// 将 bean 注册到 bean 容器中
registerBean(id, object);
}
}
}
}
}
private void registerBean(String id, Object object) {
beanMap.put(id, object);
}
//获取bean
Object getBean(String ref) {
Object object = beanMap.get(ref);
if (object == null) {
throw new IllegalArgumentException("there is no bean with name " + ref);
}
return object;
}
}
LK此时实现的例子正是之后要说的spring实现IOC的一个缩减版。
接下来LK就要来献丑说一下IOC是如何干活的,有些地方LK理解的也不是十分清楚,有问题还请各位老铁指正。
IOC干活流程:
这是IOC将配置文件中的bean解析为自己能识别的过程,次过程叫BeanDefinition(bean的定义)
跟着LK来具体看看IOC是怎么把一个陌生人变成自己亲儿子的
- 找到配置文件的位置
ApplicationContext的子类AbstractApplicationContext中的refresh()实现了配置文件的定位。
@Override
public void refresh() throws BeansException, IllegalStateException {
//同步代码快保证定位操作都是在线程安全的情况下进行的
synchronized (this.startupShutdownMonitor) {
// 准备刷新上下文,设置其启动日期和活动标志以及对属性源执行任何初始化。
prepareRefresh();
// 通知子类去刷新内部类,这个内部类是定位Resource的关键(后面会单独说).
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//配置工厂的标准上下文特征,例如上下文的类加载器和后处理器.
prepareBeanFactory(beanFactory);
try {
// 允许在上下文子类中对bean工厂进行后处理.
postProcessBeanFactory(beanFactory);
// 调用在上下文中注册为bean的工厂处理器.
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截bean创建的bean处理器.
registerBeanPostProcessors(beanFactory);
// 为此上下文初始化消息源.
initMessageSource();
// 为此上下文初始化事件多主机.
initApplicationEventMulticaster();
//初始化特定上下文子类中的其他特殊bean.
onRefresh();
//检查侦听器bean并注册它们。
registerListeners();
// 实例化所有剩余(非lazy init)单例.
finishBeanFactoryInitialization(beanFactory);
// 最后一步:发布对应的事件。
finishRefresh();
}
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
// 销毁已经创建的单例以避免资源悬空。
destroyBeans();
// 重置“活动标志”.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
往下走之前我们先来看一下这个 postProcessBeanFactory(beanFactory)后处理都干了什么,找到它的子类AbstractRefreshablewebApplicationContext
/**
* Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//添加一个新的beanPostProcessor,它将应用于创建的bean在工厂配置期间调用,bean中实例化Servlet的上下文和配置项
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
//忽略依赖接口
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
//注册web作用域,环境。
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}
总结: postProcessBeanFactory主要是在应用程序上下文标准化之后修改其内部bean工厂。此时所有bean定义都将被加载,但没有bean将被实例化。
跟着主线继续出发--------
来看到obtainFreshBeanFactory()
具体实现是在它的子类AbstractRefreshableApplicationContext中实现
protected final void refreshBeanFactory() throws BeansException {
//如果beanfactory已经存在,就销毁和关闭这个bean工厂
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建一个新的bean工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
//设置序列化id
beanFactory.setSerializationId(getId());
//自定义bean工厂
customizeBeanFactory(beanFactory);
//将bean定义加载到给定的bean工厂中,运用委托设计模式将其委托给一个或多个bean定义的阅读者
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
再来看看loadBeanDefinitions方法,具体实现来看它的子类XmlWebApplicationContext
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 给 BeanFactory 创建了一个新的 XmlBeanDefinitionReader .
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 使用此上下文环境给bean definition 读者配置资源和环境
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 允许子类自定义初始化,然后继续实际加载bean定义
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
接着看子类的loadBeanDefinitions
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
首先加载本地配置项信息,接着
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
此方法返回的是加载的bean定义项的数量,接着看
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//得到资源
ResourceLoader resourceLoader = getResourceLoader();
//指定位置没有资源
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//从指定位置加载资源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//得到资源数量
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
//资源添加到集合中
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// 只能通过URL得到资源
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
有没有看到曙光,getResources()和getResource()方法就是加载资源入口。来看看他们具体匹配加载资源规则,进入getResources()方法实现的子类。
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//从开始位置匹配String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 类路劲匹配
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// 返回匹配到的资源路径
return findPathMatchingResources(locationPattern);
}
else {
//获取具有给定名称的所有类路径资源
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// 匹配“ : ”后面的内容
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
//得到匹配到的文件路径
return findPathMatchingResources(locationPattern);
}
else {
//一个具有给定名称的单一资源
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
至此资源定位完成,也就是拿到了我们配置文件的路劲,现在知道配置配置文件路劲要使用classpath*:。上面的实例中少了此过程。
来梳理一下整个过程
下一篇再来介绍xml转换成DOm.
最后感谢大佬 田小波,I,Frankenstein等