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完成,接下来就是解析,后面讲。