javascript
Spring源码解析(二)BeanDefinition的Resource定位
? 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可能會找不到文件。
?
?
?
轉載于:https://www.cnblogs.com/monkey0307/p/8436134.html
總結
以上是生活随笔為你收集整理的Spring源码解析(二)BeanDefinition的Resource定位的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux查看杀死进程
- 下一篇: js 打开新窗口 修改 窗口大小