IOC容器的初始化过程主要包括BeanDefinition的Resource定位、载入和注册。在实际项目中我们基本上操作的都是ApplicationContex的实现,我们比较熟悉的ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebapplicationContext等。ApplicationContext的具体继承体系如下图所示:
其实,不管是XmlWebApplicationContext还是ClasspathXmlApplicationContext 他们的区别只是Bean的资源信息来源不一样而已,最终都会解析为统一数据结构BeanDefinition。
下面我们源码的解析就从高富帅的ClassPathXmlApplicationContext开始。
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath*:test.xml");
构造方法:
/*** Create a new ClassPathXmlApplicationContext, loading the definitions* from the given XML file and automatically refreshing the context.* @param configLocation resource location* @throws BeansException if context creation failed*/public ClassPathXmlApplicationContext(String configLocation) throws BeansException {this(new String[] {configLocation}, true, null);}
最终调用构造方法:
/*** Create a new ClassPathXmlApplicationContext with the given parent,* loading the definitions from the given XML files.* @param configLocations array of resource locations* @param refresh whether to automatically refresh the context,* loading all bean definitions and creating all singletons.* Alternatively, call refresh manually after further configuring the context.* @param parent the parent context* @throws BeansException if context creation failed* @see #refresh()*/public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}}
1.设置父级上下文,最终是给AbstractApplicationContext的parent属性赋值,AbstractApplicationContext是ApplicationContext最顶层的实现类。
2.设置XML文件的位置,调用了AbstractRefreshableConfigApplicationContext的setConfigLocations方法
/*** Set the config locations for this application context.* <p>If not set, the implementation may use a default as appropriate.*/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;}}
3.刷新容器,调用了AbstractApplicationContext的refresh方法,这是一个模板方法,具体的操作都是有子类去实现的。
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.//刷新容器前的准备工作 prepareRefresh();// Tell the subclass to refresh the internal bean factory.//由子类实现容器的刷新(重启)ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context./*容器使用前的准备工作*/prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.//甚至beanFacotry的后置处理 postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.//调用BeanFactory的后置处理器,这些后置处理是在Bean定义中想容器注册的 invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.//注册Bean的后置处理器,在Bean的创建过程中调用 registerBeanPostProcessors(beanFactory);// Initialize message source for this context.//对上下文中的消息源进行初始化 initMessageSource();// Initialize event multicaster for this context.//初始化上下文的事件 initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.//初始化其他特殊的Bean onRefresh();// Check for listener beans and register them.//向容器注册监听Bean registerListeners();// Instantiate all remaining (non-lazy-init) singletons.//实例化所有非延迟加载的Bean finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.//发布容器事件,结束refresh过程 finishRefresh();}catch (BeansException ex) {logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);// Destroy already created singletons to avoid dangling resources. destroyBeans();// Reset 'active' flag. cancelRefresh(ex);// Propagate exception to caller.throw ex;}}}
prepareRefresh():主要是设置启动时间、状态等等;
我们着重看一下刷新容器的obtainFreshBeanFactory()方法:
/*** Tell the subclass to refresh the internal bean factory.* @return the fresh BeanFactory instance* @see #refreshBeanFactory()* @see #getBeanFactory()*/protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {//销毁已有容器,重新创建容器并加载Bean refreshBeanFactory();ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (logger.isDebugEnabled()) {logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);}return beanFactory;}
AbstractApplicationConetxt的refreshBeanFactory()方法是一个抽象方法,是由它的子类AbstractRefreshableApplicationContext实现的,从类的命名上可以看出这个类主要就是进行容器Refresh用的。
/*** This implementation performs an actual refresh of this context's underlying* bean factory, shutting down the previous bean factory (if any) and* initializing a fresh bean factory for the next phase of the context's lifecycle.*/@Overrideprotected final void refreshBeanFactory() throws BeansException {//如果容器已经存在则销毁容器中的bean并关闭容器if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {//创建beanFacotryDefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);//根据bean定义的方式(XML、注解等)不同,由子类选择相应的BeanDefinitionReader去解析 loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}}
第一步: 这个方法会判断如果已存在容器,则先销毁所有的Bean并且关闭容器,这也是为了保证容器的唯一性。
第二步:createBeanFactory()创建了一个DefaultListableBeanFactory,这个类是BeanFacotry最高级的实现,有了它就有个容器最基本的功能了。
/*** Create an internal bean factory for this context.* Called for each {@link #refresh()} attempt.* <p>The default implementation creates a* {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}* with the {@linkplain #getInternalParentBeanFactory() internal bean factory} of this* context's parent as parent bean factory. Can be overridden in subclasses,* for example to customize DefaultListableBeanFactory's settings.* @return the bean factory for this context* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping*/protected DefaultListableBeanFactory createBeanFactory() {//新建一个DefaultListableBeanFactoryreturn new DefaultListableBeanFactory(getInternalParentBeanFactory());}
DefaultListableBeanFactory的继承关系:
我们看到DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,也就是说最终BeanDefinition的注册工作是由它和它的子类来完成的。
第三步:loadBeanDefinitions(beanFactory),这个方法也是一个抽象方法。因为Bean定义方式不同(XML、注解等),会有多个子类分别去实现具体的解析。
此处,调用的是AbstractXmlApplicationContext的实现:
/*** Loads the bean definitions via an XmlBeanDefinitionReader.* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader* @see #initBeanDefinitionReader* @see #loadBeanDefinitions*/@Overrideprotected 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());//ApplicationContext继承了ResourceLoader接口,所以this是可以直接使用的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);//委派模式,具体事情委派给beanDefinitionReader去做 loadBeanDefinitions(beanDefinitionReader);}
进入loadBeanDefinitions(XmlBeanDefinitionReader reader):
/*** Load the bean definitions with the given XmlBeanDefinitionReader.* <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}* method; hence this method is just supposed to load and/or register bean definitions.* @param reader the XmlBeanDefinitionReader to use* @throws BeansException in case of bean registration errors* @throws IOException if the required XML document isn't found* @see #refreshBeanFactory* @see #getConfigLocations* @see #getResources* @see #getResourcePatternResolver*/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);}}
该方法调用了XmlBeanDefinitionReader父类AbstractBeanDefinitionReader的loadBeanDefinitions方法:
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;}
l循环加载location并返回加载个数,最终调用了本类的loadBeanDefinitions(String location, Set<Resource> actualResources)方法,actualResources为null:
/*** Load bean definitions from the specified resource location.* <p>The location can also be a location pattern, provided that the* ResourceLoader of this bean definition reader is a ResourcePatternResolver.* @param location the resource location, to be loaded with the ResourceLoader* (or ResourcePatternResolver) of this bean definition reader* @param actualResources a Set to be filled with the actual Resource objects* that have been resolved during the loading process. May be {@code null}* to indicate that the caller is not interested in those Resource objects.* @return the number of bean definitions found* @throws BeanDefinitionStoreException in case of loading or parsing errors* @see #getResourceLoader()* @see #loadBeanDefinitions(org.springframework.core.io.Resource)* @see #loadBeanDefinitions(org.springframework.core.io.Resource[])*/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");}//resourceLoader是ClasspathXmlApplicationContext,ApplicationContext接口本身继承了ResourcePatternResolver接口if (resourceLoader instanceof ResourcePatternResolver) {// Resource pattern matching available.try {//location转为Resource完成定位工作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;}}
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location)将location转成了Resource[],这一步完成了资源的定位工作。
它调用了PathMatchingResourcePatternResolver的getResources方法:
1 public Resource[] getResources(String locationPattern) throws IOException { 2 Assert.notNull(locationPattern, "Location pattern must not be null"); 3 //是否以classpath*:开头 4 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { 5 // a class path resource (multiple resources for same name possible) 6 //是否为Ant-style路径 7 //? 匹配任何单字符 8 //* 匹配0或者任意数量的字符 9 //** 匹配0或者更多的目录 10 if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { 11 // a class path resource pattern 12 return findPathMatchingResources(locationPattern); 13 } 14 else { 15 // all class path resources with the given name 16 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); 17 } 18 } 19 else { 20 // Only look for a pattern after a prefix here 21 // (to not get fooled by a pattern symbol in a strange prefix). 22 int prefixEnd = locationPattern.indexOf(":") + 1; 23 if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { 24 // a file pattern 25 return findPathMatchingResources(locationPattern); 26 } 27 else { 28 // a single resource with the given name 29 return new Resource[] {getResourceLoader().getResource(locationPattern)}; 30 } 31 } 32 }
根据location写法,解析方式也不同:
1、前缀为classpath*
1)文件路径路径中包含*和?
调用findPathMatchingResources方法
2)文件路径中不含*和?
调用findAllClassPathResources方法
2.、前缀为classpath
1)文件路径路径中包含*和?
调用findPathMatchingResources方法
2)文件路径中不含*和?
调用DefaultResourceLoader的getResource方法new一个ClasspathResource并返回,如果资源文件根本就不存在,此处也不会校验。
findPathMatchingResources和findAllClassPathResources具体都干了什么呢?
先看一下findAllClassPathResources:
/*** Find all class location resources with the given location via the ClassLoader.* @param location the absolute path within the classpath* @return the result as Resource array* @throws IOException in case of I/O errors* @see java.lang.ClassLoader#getResources* @see #convertClassLoaderURL*/protected Resource[] findAllClassPathResources(String location) throws IOException {String path = location;if (path.startsWith("/")) {path = path.substring(1);}ClassLoader cl = getClassLoader();Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));Set<Resource> result = new LinkedHashSet<Resource>(16);while (resourceUrls.hasMoreElements()) {URL url = resourceUrls.nextElement();result.add(convertClassLoaderURL(url));}return result.toArray(new Resource[result.size()]);}
protected Resource convertClassLoaderURL(URL url) {
return new UrlResource(url);
}
这个方法很简单,根据具体的location通过classLoader的getResources方法返回RUL集合,根据URL创建UrlResource并返回UrlResource的集合。
再来看一下findPathMatchingResources方法:
/*** Find all resources that match the given location pattern via the* Ant-style PathMatcher. Supports resources in jar files and zip files* and in the file system.* @param locationPattern the location pattern to match* @return the result as Resource array* @throws IOException in case of I/O errors* @see #doFindPathMatchingJarResources* @see #doFindPathMatchingFileResources* @see org.springframework.util.PathMatcher*/protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {String rootDirPath = determineRootDir(locationPattern);String subPattern = locationPattern.substring(rootDirPath.length());Resource[] rootDirResources = getResources(rootDirPath);Set<Resource> result = new LinkedHashSet<Resource>(16);for (Resource rootDirResource : rootDirResources) {rootDirResource = resolveRootDirResource(rootDirResource);if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));}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()]);}
1.String rootDirPath = determineRootDir(locationPattern),获取location前缀classpath*/classpath
2.Resource[] rootDirResources = getResources(rootDirPath),调用的上面讲到的getResources方法,返回classpath根路径的Resource[],如果是classpath会返回一个Resource,
如果是classpath*会放回所有的classpath路径。
3.遍历根路径Resource[],doFindPathMatchingFileResources方法就是获取给定路径下的所有文件,根据指定的文件名test*.xml去模糊匹配,返回的是FileSystemResource。所以location为classpath:test*.xml可能会找不到文件。