跟着大佬阅读spring源码(一)

每一个做web的同学都离不开spring,spring的设计思想博大而精深。LK在开发过程中也都是在使用spring的功能和各种配置,其实并不理解spring是如何管理bean的。最近LK在学习的springboot的时候不仅对springboot简化的配置感到惊叹,同时也感到有一点惶恐,如此高度的封装虽然简化了开发配置,提高了生产效率。但只是停留在了会用的程度,所谓知其然不知其所以然,遇到问题只能百度解决。所以下决心从spring的源码下手,看看其底层实现。所谓站在巨人的肩上,事办功倍。
感谢各位大佬和前辈,spring的源码真的太多,没有主线,自己看真的是自寻死路。当然和spring中使用设计模式和高度封装有关。
好了废话不多说,来看看spring核心IOC(控制反转)
IOC主要干了两件事

  1. 控制bean的生命周期。
  2. 处理对象间的关系。
    注入IOC容器中的bean各种各样,自带属性也不相同,类与类之间的关系也错综复杂,在没有IOC管理bean之前,我们只能通过java的四大特性去维护这些关系。好了IOC的出现使我们从复杂的bean关系中解脱,一切交给容器自身去维护。可以说bean的吃喝拉撒都交给了IOC负责,当然我们这个大保姆也十分有耐心去照顾每一个bean儿子,从生老病死,精心呵护它们实现各自的使命。

LK在具体查看spring源码之前,先通过一个自己实现的的例子来看看IOC在对bean管理时都做了什么。麻雀虽小但五脏俱全。

  1. 首先定位bean配置文件,相信大家学习spring时都使用过,就不详细说了。
  2. 将配置文件转换为IO流。
  3. 将IO流转化为Document对象。
  4. 通过解析Document对象元素,获取定义的bean.
  5. 使用HashMap缓存bean对象。
  6. 通过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()

扫描二维码关注公众号,回复: 8812863 查看本文章

在这里插入图片描述
具体实现是在它的子类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等

发布了47 篇原创文章 · 获赞 18 · 访问量 5711

猜你喜欢

转载自blog.csdn.net/yuruizai110/article/details/93677103
今日推荐