spring4.2.9 java项目环境下ioc源码分析(三)——refresh之obtainFreshBeanFactory方法(@1准备工作与加载Resource)

14 篇文章 0 订阅

obtainFreshBeanFactory方法从字面的意思看获取新的Bean工厂,实际上这是一个过程,一个加载Xml资源并解析,根据解析结果组装BeanDefinitions,然后初始化BeanFactory的过程。

在加载Xml文件之前,spring还做了一些其他的工作,比如说判断是否已经存在容器,创建Beanfactory并设置忽略自动装配等等。下面结合代码具体看看

1、准备工作。

在spring中,基本上各司其职,每个类都有每个类的作用。AbstractApplicationContext#obtainFreshBeanFactory()

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

其中refreshBeanFactory是具体的刷新BeanFactory,负责这个工作做在类AbstractRefreshableApplicationContext中,顾名思义这是专门用来刷新的。getBeanFactory方法同样是从此类中获取得到的Beanfactory并返回。

protected final void refreshBeanFactory() throws BeansException {
		//判断是否已经存在BeanFactory,存在则销毁所有Beans,并且关闭BeanFactory.
		//目的是避免重复加载BeanFactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//创建具体的beanFactory,这里创建的是DefaultListableBeanFactory,最重要的beanFactory
			//spring注册及加载bean就靠它。其实这里还是一个基本的容器
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			//扩展
			customizeBeanFactory(beanFactory);
			//初始化XmlBeanDefinitionReader用来读取xml,并加载解析
			loadBeanDefinitions(beanFactory);
			//设置为全局变量,AbstractRefreshableApplicationContext持有DefaultListableBeanFactory引用
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

下面看看createBeanFactory方法这里还有些东西要说。

protected DefaultListableBeanFactory createBeanFactory() {
		return new DefaultListableBeanFactory(getInternalParentBeanFactory());
	}
protected BeanFactory getInternalParentBeanFactory() {
		return (getParent() instanceof ConfigurableApplicationContext) ?
				((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent();
	}
public DefaultListableBeanFactory(BeanFactory parentBeanFactory) {
		super(parentBeanFactory);
	}
public AbstractAutowireCapableBeanFactory(BeanFactory parentBeanFactory) {
		this();
		setParentBeanFactory(parentBeanFactory);
	}
public AbstractAutowireCapableBeanFactory() {
		super();
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
	}

以上是createBeanFactory追溯到的所有代码。看看做了些什么事

1、为DefaultListableBeanFactory设置父factory,如果父容器存在的话。

2、忽略自动装配。这里指定的都是接口。什么意思呢?如果在加载bean的时候发现此bean是忽略接口的实现类,spring就不自动将其初始化,而是交给他自己。下面引用一段看到的内容来看


customizeBeanFactory方法如下,在类AbstractRefreshableApplicationContext中,这里是根据AbstractRefreshableApplicationContext类的属性为Beanfactory设置值。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
		if (this.allowBeanDefinitionOverriding != null) {
			beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.allowCircularReferences != null) {
			beanFactory.setAllowCircularReferences(this.allowCircularReferences);
		}
	}

allowBeanDefinitionOverriding属性是指是否允对一个名字相同但definition不同进行重新注册,默认是true。

allowCircularReferences属性是指是否允许Bean之间循环引用,默认是true.

默认情况下两个属性都为空,既然是可扩展的,那么自然可以自己设置属性,方法就是继承ClassPathXmlApplicationContext并复写customizeBeanFactory方法为两个属性设置值即可。

接下来看loadBeanDefinitions方法,其实这也是准备工作

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//为指定的beanFactory创建XmlBeanDefinitionReader,做了XmlBeanDefinitionReader的初始化
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		//为XmlBeanDefinitionReader配置环境属性
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		//扩展。。。子类复写此方法,进行自定义初始化
		initBeanDefinitionReader(beanDefinitionReader);
		//XmlBeanDefinitionReader读取xml
		loadBeanDefinitions(beanDefinitionReader);
	}

XmlBeanDefinitionReader初始化,无非是初始化自己与父类,父类做了些其他的工作

protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		// Determine ResourceLoader to use.
		if (this.registry instanceof ResourceLoader) {
			this.resourceLoader = (ResourceLoader) this.registry;
		}
		else {
			this.resourceLoader = new PathMatchingResourcePatternResolver();
		}

		// Inherit Environment if possible
		if (this.registry instanceof EnvironmentCapable) {
			this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
		}
		else {
			this.environment = new StandardEnvironment();
		}
	}

根据BeanDefinitionRegistry的类型去创建不同的resourceLoader与environment,这里resourceLoader为

PathMatchingResourcePatternResolver类型,environment为StandardEnvironment类型。但是在为

XmlBeanDefinitionReader配置环境属性时候又把resourceLoader设置为本身ClassPathXmlApplicationContext,这里有点不懂为什么这么做。不过做了也就这样了

看下loadBeanDefinitions方法这里开始加载xml文件了。

2、XML文件的读取

先看代码

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		//扩展。。。默认是null。子类实现
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		//ClassPathXmlApplicationContext构造函数传入的applicationContext.xml
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			//解析
			reader.loadBeanDefinitions(configLocations);
		}
	}

哈哈又一个扩展点,如果有一天我们的applicationContext.xml不是在calsspath下了,我们只有Resource,那怎么办,直接传递进来即可,继承ClassPathXmlApplicationContext类重写getConfigResources方法,返回Resource即可。

接下来看reader是怎样解析的。

@Override
	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;
	}

委托给了AbstractBeanDefinitionReader类进行解析

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		//获取当前资源加载器,这里是ClassPathXmlApplicationContext
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}
		//ClassPathXmlApplicationContext继承了ResourcePatternResolver
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				//吧Xml解析为Resource
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				//解析resource
				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;
		}
	}

重点看把location解析为Resource

@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		//是不是以classpath*:开始的
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			//以classpath*:开始的
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// 路径中有?或*号通配符 如classpath*:applicationContext-*.xml
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 路径没有?或*号通配符 如classpath*:applicationContext.xml
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			//不以classpath*:开始的
			int prefixEnd = locationPattern.indexOf(":") + 1;
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// 路径中有?或*号通配符
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 路径没有?或*号通配符
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

解析的依据有两个是不是以classpath*:开头的,是不是有通配符?或者*

先看findPathMatchingResources方法,比如classpath*:/APP/applicationContext-*.xml

首先不看代码想一下我们如果要匹配应该怎么去做:

是不是要拿到APP目录下所有的文件,然后再与applicationContext-*.xml相匹配,符合就返回。

Spring也是这么做的,下面来看具体代码。

	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		//获取跟目录路径这里是classpath*:/APP/
		String rootDirPath = determineRootDir(locationPattern);
		//匹配模式这里是applicationContext-*.xml
		String subPattern = locationPattern.substring(rootDirPath.length());
		//然后继续调用getResources,这时候传进去的是classpath*:/APP/,这里返回的是classpath*:/APP/的URLResources
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<Resource>(16);
		for (Resource rootDirResource : rootDirResources) {
			//解析,目前是原样返回
			rootDirResource = resolveRootDirResource(rootDirResource);
			//vfs(略)
			if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
			}
			//jar(略)
			else if (isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, 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()]);
	}
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {

		File rootDir;
		try {
			//获取Resource的绝对文件
			rootDir = rootDirResource.getFile().getAbsoluteFile();
		}
		catch (IOException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Cannot search for matching files underneath " + rootDirResource +
						" because it does not correspond to a directory in the file system", ex);
			}
			return Collections.emptySet();
		}
		//匹配解析
		return doFindMatchingFileSystemResources(rootDir, subPattern);
	}

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
		}
		//具体匹配解析
		Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
		Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
		//返回的是FileSystemResource
		for (File file : matchingFiles) {
			result.add(new FileSystemResource(file));
		}
		return result;
	}
protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
		if (!rootDir.exists()) {
			// Silently skip non-existing directories.
			if (logger.isDebugEnabled()) {
				logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
			}
			return Collections.emptySet();
		}
		if (!rootDir.isDirectory()) {
			// Complain louder if it exists but is no directory.
			if (logger.isWarnEnabled()) {
				logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
			}
			return Collections.emptySet();
		}
		if (!rootDir.canRead()) {
			if (logger.isWarnEnabled()) {
				logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
						"] because the application is not allowed to read the directory");
			}
			return Collections.emptySet();
		}
		//真实路径
		String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
		if (!pattern.startsWith("/")) {
			//最后以“/”结尾
			fullPattern += "/";
		}
		//要匹配的路径这里是真实路径+applicationContext-*.xml
		fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
		Set<File> result = new LinkedHashSet<File>(8);
		//do开头的开始做事
		doRetrieveMatchingFiles(fullPattern, rootDir, result);
		return result;
	}
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Searching directory [" + dir.getAbsolutePath() +
					"] for files matching pattern [" + fullPattern + "]");
		}
		//获取文件下的所有文件
		File[] dirContents = dir.listFiles();
		if (dirContents == null) {
			if (logger.isWarnEnabled()) {
				logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
			}
			return;
		}
		for (File content : dirContents) {
			//文件的路径
			String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
			//判断文件是不是目录并且和currPath+“/”是匹配fullPattern
			if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
				if (!content.canRead()) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
								"] because the application is not allowed to read the directory");
					}
				}
				else {
					//递归调用
					doRetrieveMatchingFiles(fullPattern, content, result);
				}
			}
			//不是目录的话判断是不是匹配
			if (getPathMatcher().match(fullPattern, currPath)) {
				result.add(content);
			}
		}
	}
代码比较多,其实逻辑很简单,就是拿到路径下的文件与设定的文件匹配,其中穿插着递归调用。匹配算法有兴趣的可以研究下。

接下来是findAllClassPathResources方法,在上面的代码也看到了,需要调用findAllClassPathResources,代码如下

protected Resource[] findAllClassPathResources(String location) throws IOException {
		//传进来的是/APP/
		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();
		//根据类加载器加载资源,默认是classpath下
		Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
		//遍历
		while (resourceUrls.hasMoreElements()) {
			URL url = resourceUrls.nextElement();
			//转变为URLResource
			result.add(convertClassLoaderURL(url));
		}
		//这里是全jar查找,比如说设定是classpath*:applicationContext-*.xml,就会走这里的方法
		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;
	}
上面的两个方法覆盖了3中情况,还剩下一种,既不是以classpath*:开头的,也没有通配符。
返回的代码是
return new Resource[] {getResourceLoader().getResource(locationPattern)};

这里用资源加载器去获取资源的。这里的ResourceLoader是ClassPathXmlApplicationContext,getResource方法委托给了其父类DefaultResourceLoader,代码如下

public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		//以“/”开始,返回的ClassPathContextResource是ClassPathResource的子类,处理了“/”开始的情况
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		//以"classpath:"开始
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			//返回的是ClassPathResource
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			//都不是的情况下,是不是URL,以http://开始,返回UrlResource
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				//按照path解析,返回的ClassPathContextResource是ClassPathResource的子类
				return getResourceByPath(location);
			}
		}
	}

说点题外话:这里一共三个方法解析路径到Resource,返回的却是不同类型的。

大体上有这几类:URLResource,ClassPathResource,FileSystemResource.这些都是Spring封装的,比如URLResource封装了URL/URI,FileSystemResource封装了文件与路径,ClassPathResource封装了路径与类加载器。根据不同的加载资源的方式去用不同的Resource。但是这样又有点问题,用错了怎么办?所以Spring又提供了一个接口ResourceLoader其实现是DefaultResourceLoader用来根据不同的地址前缀去用不同的Resource。

ResourceLoader的代码上面提到了,下面看下:

public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);
			}
		}
	}

一张图解决问题。但是这个类并没有实现通配符功能。所以又提供一个接口ResourcePatternResolver其实现类PathMatchingResourcePatternResolver解决了,通配符的情况。

至此准备工作与通过xml加载Resource完成,接下来就是解析,后面讲。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackson陈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值