您的位置:首页 > 编程语言 > Java开发

Spring源码阅读-- IOC容器资源定位

2017-09-15 17:35 651 查看

1、环境搭建

工程目录结构:



首先我们从最基础的spring开发代码开始,首先上场的是spring配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd ">

<!-- 引入配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--属性注入-->
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 连接池最大使用连接数 -->
<property name="maxActive" value="${jdbc.maxActive}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${jdbc.initialSize}"/>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${jdbc.maxWait}"/>
<!-- 连接池最大空闲 -->
<!--  property name="maxIdle" value="${jdbc.maxIdle}"/>-->
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${jdbc.minIdle}"/>
<!-- 自动清除无用连接 -->
<property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
<!-- 清除无用连接的等待时间 -->
<property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
<!-- 连接属性 -->
<property name="connectionProperties" value="${jdbc.connectionProperties}"/>
</bean>

<bean id="zhangsan"  class="com.igood.entity.User">
<constructor-arg type="java.lang.String">
<value>zhangsan</value>
</constructor-arg>
<constructor-arg type="java.lang.String">
<value>123456</value>
</constructor-arg>
<constructor-arg type="java.lang.Integer">
<value>20</value>
</constructor-arg>
</bean>
<!--给zhangsan这个bean起几个别名,其中有一个别名和原来bean名称相同-->
<alias name="zhangsan" alias="zhangsan,zhang3,alias1"/>

<!--设置名称为wangwu的bean不是懒加载-->
<bean id="wangwu"  class="com.igood.entity.User" lazy-init="false" scope="prototype">
<constructor-arg type="java.lang.String">
<value>wangwu</value>
</constructor-arg>
<constructor-arg type="java.lang.String">
<value>111111</value>
</constructor-arg>
<constructor-arg type="java.lang.Integer">
<value>25</value>
</constructor-arg>
</bean>
</beans>


接着是main函数启动代码

//代码片段1
public static void main(String[] args) {

ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("classpath:spring-beans.xml");
User bean = (User)context.getBean("zhang3");
bean.getUsername();
context.close();
}


在跟踪源码前,先来看一下spring IOC 体系结构相关类的继承关系图:



为了思路清晰,类图中只列出比较重要的方法和变量。

好了,开始跟踪spring源码。首先进入ClassPathXmlApplicationContext的构造函数

//代码片段2
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {

super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}


一直沿着继承关系查看父类的构造函数,到AbstractApplicationContext

//代码片段3
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}


从ClassPathXmlApplicationContext的类图可以看出AbstractApplicationContext和PathMatchingResourcePatternResolver实现了ResourceLoader接口,也就是说AbstractApplicationContext也有ResourcePatternResolver的getResource()的能力,但是AbstractApplicationContext不亲自具体实现getResource,而是委派给PathMatchingResourcePatternResolver去做。

2、径路解析

回到“代码片段2”中,ClassPathXmlApplicationContext的构造函数中setConfigLocations,这个函数在父类AbstractRefreshableConfigApplicationContext中实现

//代码片段4
public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}


resolvePath的作用就是将创建ApplicationContext传入进来的locations(classpath: spring-beans.xml)配置文件路径转换为合法的location,就是将路径中包含环境变量转成变量的实际值,比如路径有占位符中的变量替换成变量实际的值。getEnviroment()拿到StandardEnvironment对象来进行占位符{}的替换。

//代码片段5
public class StandardEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value} */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

/** JVM system properties property source name: {@value} */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

}


StandardEnvironment就是读取操作系统的环境变量和JVM的变量。当传进来的路径中包含这些环境变量就替换成系统中变量的实际值。比如我们传进来的路径是”classpath: JAVAHOME/spring−beans.xml",经过resolveRequiredPlaceholders函数处理就把{JAVA_HOME}替换成你电脑配置的JAVA_HOME环境变量的值,如”classpath:C:/Program Files/Java/jdk1.8.0_91/spring-beans.xml”。

3、容器刷新

好了,资源路径准备好了,我们回到”代码片段2”中的refresh()函数,重头戏都在这个函数里了。这个函数在父类AbstractApplicationContext中实现。

//代码片段6
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 刷新工厂之前需要做一些准备工作的啦,就想你在运动之前要做一些准备运动一样哦
prepareRefresh();

// 我会告诉我的子类创造一个工厂,来把我需要创建bean的原料BeanDefinition准备好
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 原料准备好之后呢,我要声明一些特殊的依赖关系, 所谓依赖,就是我在创造一个bean A的时候,发现它里面有另外一个属性B
// 那么B就是A的依赖,我在创造A的时候,必须先把B创造好,特殊关系的依赖就是指我遇到B的类型,我该放弃呢,还是告诉他直接用
// 现成的(也就是不用再去创造B了)
prepareBeanFactory(beanFactory);

try {
// 这里没啥,就是留给子类做扩展的啦
postProcessBeanFactory(beanFactory);

// 到了这里,工厂已经准备好了,如果你之前告诉过我工厂准备好之后应该干什么事情,这边我就可以满足你的需求哦
// 不信,你去看看BeanFactoryPostProcessors接口是干嘛用的吧==
invokeBeanFactoryPostProcessors(beanFactory);

// 在创建一个bean的前后,我也留给你很多扩展,原理上和上面的工厂扩展差不多的哦
registerBeanPostProcessors(beanFactory);

// 就是处理一些国际化的操作啦,啊?什么是国际化,就是i18n啦,还不懂?你没救了
initMessageSource();

// 我的功能很丰富,除了可以给你创建bean,还可以有事件管理的功能哦,这里我就创建一个管理器(ApplicationEventMulticaster(),
// 用来注册事件(ApplicationEvent)
// 我会将这些事件广播给合适的监听者(ApplicationListener)那边哦
initApplicationEventMulticaster();

// 啥也不干,留给子类扩展啦
onRefresh();

// 前面不是事件管理器搞好了嘛,这边呢,就是把那些事件监听器给注册进来啦,这样来一个新的事件我就知道该发给谁啦
registerListeners();

// 如果某些bean告我我,他想在我工厂创建之初就想初始化(一般要是单件singleton并且lazy-init为false),那么我在这个函数会满足他
finishBeanFactoryInitialization(beanFactory);

// 终于刷新完了,我要开始发布事件了!
finishRefresh();
}

// 什么?刷新的时候报错了?oh my god,我需要做一些清理
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

// 我需要将我创建的bean销毁掉
destroyBeans();

// 我不再活跃
cancelRefresh(ex);

// 我要告诉你,我出异常了,救我!!
throw ex;
}

finally {
// 一些通用的缓存清掉!!
resetCommonCaches();
}
}
}

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}


refresh()方法主要为 IOC 容器 Bean 的生命周期管理提供条件, Spring IOC 容器载入 Bean 定义资源文件从其子类容器的 refreshBeanFactory()方法启动, 所以整个 refresh()中“ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();” 这句以后代码的都是注册容器的信息源和生命周期事件, 载入过程就是从这句代码启动。

3.1、创建容器

顺着obtainFreshBeanFactory方法,进入到AbstractRefreshableApplicationContext的refreshBeanFactory方法,这个方法的主要功能就是创建默认的IOC容器和Xml配置文件的相关操作。

//代码片段7
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {//如果已经有容器, 销毁容器中的 bean, 关闭容器,以保证在 refresh 之后使用的是新建立起来的 IOC 容器
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();//创建 IOC 容器了
beanFactory.setSerializationId(getId());
//对 IOC 容器进行定制化, 如设置启动参数, 开启注解的自动装配等
customizeBeanFactory(beanFactory);
// 加载bean的定义,我们用xml描述了各种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);
}
}


第7行代码
DefaultListableBeanFactory beanFactory = createBeanFactory()
为我们新创建一个IOC容器,所谓容器就是一个Map数据结构,进入DefaultListableBeanFactory代码就发现有
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
这个成员变量,spring就是将以bean的名字做key,bean的定义作为值存到这个Map中,这就是我们需要创建bean所需要的原材料。

3.2、加载bean定义

既然容器创建好了,那么第12行代码
loadBeanDefinitions(beanFactory)
就要为beanFactory容器加载bean定义(BeanDefinition)了。

AbstractRefreshableApplicationContext 中只定义了抽象的 loadBeanDefinitions 方法, 容器真正调用的是其子类 AbstractXmlApplicationContext 对该方法的实现。

//代码片段8
//AbstractXmlApplicationContext实现父类的抽象方法
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
//设置资源加载器就是容器本身,1、环境搭建小节中“代码片段3”说明AbstractApplicationContext也有资源加载器的能力
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}


方法里定义了一个XmlBeanDefinitionReader类型的对象,这个对象的作用就是读取Xml配置文件。最后一行代码
loadBeanDefinitions(beanDefinitionReader);
就是用这个读取器来加载bean定义的。

//代码片段9
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}


我们会看到有两种方式加载配置文件,一种是从Resource类型的路径中加载,还有一种是从String类型的路径中加载。 由于我们main函数入口是
new ClassPathXmlApplicationContext("classpath:spring-beans.xml");
所以new出来ClassPathXmlApplicationContext对象里getConfigResources()是null的,而getConfigLocations()就前面“ 2、径路解析”小节准备的配置文件路径,因此进入到第8行代码,从从String类型的路径中加载xml文件。

进入XmlBeanDefinitionReader的loadBeanDefinitions方法:

//代码片段10
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}


loadBeanDefinitions循环从每个路径中加载xml文件。真正加载资源的是它重载函数:

//代码片段11
//真正干活的函数
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 {
// Can only load single resources by absolute 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;
}
}


第4行代码
ResourceLoader resourceLoader = getResourceLoader();
获取的ResourceLoader对象就是“代码片段8”中
beanDefinitionReader.setResourceLoader(this);
,也就是ClassPathXmlApplicationContext类的对象,1、环境搭建小节中“代码片段3”说明AbstractApplicationContext实现ResourcePatternResolver接口,因此作为AbstractApplicationContext的子类ClassPathXmlApplicationContext对象拥有getResource()的能力,但是ClassPathXmlApplicationContext不亲自具体实现getResource,而是委派给PathMatchingResourcePatternResolver去做。因此”代码片段11”中
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
最终调用如下图:



4、资源定位

好了,跟踪这么久现在才进入本文的标题真正要说的事。进入PathMatchingResourcePatternResolver类的getResource方法:

//代码片段12
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {//处理以classpath*:开头的路径
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {//路径中包含*或?这样的通配符
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
//路径中不包含通配符
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
//处理不以classpath*:开头的路径
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {//路径中包含*或?这样的通配符
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
//路径中不包含通配符
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}


由上面代码我们可以看出在加载配置文件时,以是否是以classpath*开头分为2种情况处理场景,每种情况类在又根据路径中是否包括通配符进行处理。



4.1、处理以classpath*开头且不包含通配符

进入
findAllClassPathResources
函数看看

protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isDebugEnabled()) {
logger.debug("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
Set<Resource> result = new LinkedHashSet<Resource>(16);
ClassLoader cl = getClassLoader();
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
result.add(convertClassLoaderURL(url));
}
if ("".equals(path)) {
// The above result is likely to be incomplete, i.e. only containing file system references.
// We need to have pointers to each of the jar files on the classpath as well...
addAllClassLoaderJarRoots(cl, result);
}
return result;
}


跟踪
doFindAllClassPathResources
方法中的
ClassLoader cl = getClassLoader();
进入到
ClassLoader
的getResources()方法:

//ClassLoader.java
public Enumeration<URL> getResources(String name) throws IOException {
@SuppressWarnings("unchecked")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
//如果存在父加载器,则向上迭代获取资源
tmp[0] = parent.getResources(name);
} else {
//Bootstrap classLoader主要加载JVM自身工作需要的类,位于$JAVA_HOME/jre/lib/下的jar包
tmp[0] = getBootstrapResources(name);
}
tmp[1] = findResources(name);

return new CompoundEnumeration<>(tmp);
}


当前类加载器,如果存在父加载器,则向上迭代获取资源, 因此能加到jar包里面的资源文件。

4.2、处理不以classpath*开头且不包含通配符

就是一行代码

new Resource[] {getResourceLoader().getResource(locationPattern)};


getResourceLoader()
方法获取到就是默认的资源加载器

public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
this.resourceLoader = new DefaultResourceLoader(classLoader);
}


因此,我们进入
DefaultResourceLoader
类的
getResource
方法:

public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");

for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}

if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
//如果以classpath开头,则创建为一个ClassPathResource
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
//以URL的方式加载资源,创建一个UrlResource.
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}


由此可看出,不以“classpath*”的开头路径getResource()仅返回路径(包括jar包)中的一个且仅一个资源;对于多个匹配的也只返回一个。

4.3、路径包含通配符的

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//获取确定的根路径(没有通配符的最长路径),如cn/javass/config-*.xml,则根路径是cn/javass/;
//如cn/**/config.xml,跟路径是cn/
String rootDirPath = determineRootDir(locationPattern);
//含通配符的,如cn/javass/config-*.xml,则subPattern是config-*.xml
String subPattern = locationPattern.substring(rootDirPath.length());
//获取跟路径下所有资源
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {//查找满足通配符的资源
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirURL = rootDirResource.getURL();
if (equinoxResolveMethod != null) {
if (rootDirURL.getProtocol().startsWith("bundle")) {
rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
rootDirResource = new UrlResource(rootDirURL);
}
}
if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
}
else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}


以“classpath*”的开头路径,getResources()加载类路径(包括jar包)中的所有匹配的资源。

至此, Spring IOC 容器在初始化时将配置的 Bean 定义资源文件定位为 Spring 封装的 Resource。

总结

来一张图过一遍代码执行简要过程



参考

http://blog.csdn.net/ray_seu/article/details/50096889

http://blog.csdn.net/zl3450341/article/details/9306983
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: