看這篇文章以前能夠先了解以前的跟蹤流程,https://www.jianshu.com/p/4934233f0eadjava
代碼過寬,能夠shift + 鼠標滾輪 左右滑動查看web
AbstractApplicationContext類refresh()方法中的第二個調用方法obtainFreshBeanFactory()的跟蹤。spring
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { ... // Tell the subclass to refresh the internal bean factory. // 告知子類去刷新內部的 bean factory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); ··· }
斷點進入跟蹤。數組
/** * Tell the subclass to refresh the internal bean factory. * @return the fresh BeanFactory instance * * 告知子類去刷新內部的 bean factory,返回刷新後的 bean factory 實例 */ protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //刷新 bean factory,進入此方法查看 refreshBeanFactory(); //返回已被刷新、註冊了beandefinition的factory ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
refreshBeanFactory方法在子類AbstractRefreshableApplicationContext中實現,在AbstractApplicationContext中被定義。app
//1.刷新 bean factory refreshBeanFactory(); //先看下這個方法的定義 /** * Subclasses must implement this method to perform the actual configuration load. * The method is invoked by {@link #refresh()} before any other initialization work. * <p>A subclass will either create a new bean factory and hold a reference to it, * or return a single BeanFactory instance that it holds. In the latter case, it will * usually throw an IllegalStateException if refreshing the context more than once. * @throws BeansException if initialization of the bean factory failed * @throws IllegalStateException if already initialized and multiple refresh * attempts are not supported * * 子類必須實現這個方法去執行配置的加載。這個方法在其餘任何初始化工做以前被 refresh 方法所調用, * 他的子類要麼建立一個新的 bean factory ,並拿到factory的引用;要麼返回一個 * 已有的單例 bean factory 實例。 * 在後一個狀況中,若是刷新這個 context 超過一次,那麼就會拋出非法狀態異常 */ protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException; /** * 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. * * 這個實現對 context 底層的 bean factory 執行刷新操做,關閉之前的 bean factory(若是有), * 而且爲 context 生命週期的下一個階段初始化一個新的 bean factory */ @Override protected final void refreshBeanFactory() throws BeansException { //此時沒有 bean factory,直接跳過 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //1.建立一個 bean factory DefaultListableBeanFactory beanFactory = createBeanFactory(); //將 context 的 id 設置爲 bean factory 的序列化id, //並創建起id和該 bean factory 實例的映射關係 beanFactory.setSerializationId(getId()); //2.自定義內部的 bean factory customizeBeanFactory(beanFactory); //3.加載 beanDefinition loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { // 新建立的工廠,其內部屬性就位後,被 context 拿到引用 this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
跟蹤標記1的方法ide
這個方法在AbstractRefreshableApplicationContext類中ui
//1.建立一個 bean factory DefaultListableBeanFactory beanFactory = createBeanFactory(); /** * 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. * * 爲 context 建立一個內部的 bean factory。 * 每一個refresh方法都會嘗試調用該方法 * 默認的實現是建立一個DefaultListableBeanFactory對象,利用getInternalParentBeanFactory方法 * 將 context 的 parent 的 bean factory ,做爲該 context 內部 bean factory 的 parent bean factory * 該方法能夠被子類覆蓋,例如自定義DefaultListableBeanFactory的設定 */ protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); }
getInternalParentBeanFactory方法在AbstractApplicationContext類中this
AbstractApplicationContext是AbstractRefreshableApplicationContext的父類url
/** * Return the internal bean factory of the parent context if it implements * ConfigurableApplicationContext; else, return the parent context itself. * * 若是 parent context 實現了ConfigurableApplicationContext接口, * 那麼返回 parent context 內部的 bean factory * 不然返回 parent context 自身 */ protected BeanFactory getInternalParentBeanFactory() { return (getParent() instanceof ConfigurableApplicationContext) ? ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent(); }
不論是 application context 仍是 bean factory,都有實現BeanFactory這個接口,因此 context 也能夠看作是一個特殊類型的 bean factory 。spa
跟蹤標記2.自定義內部的bean工廠
//2.自定義內部的 bean factory customizeBeanFactory(beanFactory); /** * Customize the internal bean factory used by this context. * Called for each {@link #refresh()} attempt. * <p>The default implementation applies this context's * {@linkplain #setAllowBeanDefinitionOverriding "allowBeanDefinitionOverriding"} * and {@linkplain #setAllowCircularReferences "allowCircularReferences"} settings, * if specified. Can be overridden in subclasses to customize any of * * 自定義內部的 bean factory 用於這個 context * 每一個refresh方法都會嘗試調用該方法 * 默認實現應用這個context的setAllowBeanDefinitionOverriding方法和setAllowCircularReferences方法 * 去設置,若是有這兩個方法所需的參數的話。 * 能夠在子類中覆蓋該方法去自定義DefaultListableBeanFactory中的任何設定 */ protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { if (this.allowBeanDefinitionOverriding != null) { //相同名稱的不一樣bean definition可否被重複註冊 beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.allowCircularReferences != null) { //是否嘗試自動去解決兩個bean之間的循環引用問題 beanFactory.setAllowCircularReferences(this.allowCircularReferences); } }
跟蹤標記3的方法
這個方法在AbstractRefreshableApplicationContext中被定義,在XmlWebApplicationContext中被實現
//3.加載 beanDefinition loadBeanDefinitions(beanFactory); //在AbstractRefreshableApplicationContext類中的定義 /** * Load bean definitions into the given bean factory, typically through * delegating to one or more bean definition readers. * * 一般經過一個或者多個 bean definition readers 去加載 bean definitions 到指定的 bean factory */ protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException; //在XmlWebApplicationContext類中的實現 /** * Loads the bean definitions via an XmlBeanDefinitionReader. * 經過一個XmlBeanDefinitionReader加載bean definitions */ @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // 爲指定的 BeanFactory 建立一個新的XmlBeanDefinitionReader // 3.1XmlBeanDefinitionReader的初始化須要瞭解下 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. // 將context加載了資源的environment配置給bean definition reader beanDefinitionReader.setEnvironment(getEnvironment()); // 將 ResourceLoader 換成 XmlWebApplicationContext 對象 // XmlWebApplicationContext 實現了 ResourceLoader 接口 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. // 容許子類提供reader的自定義初始化 // 而後處理正在加載中的bean definitions // 此方法是空實現 initBeanDefinitionReader(beanDefinitionReader); // 3.2加載beanDefinition操做 loadBeanDefinitions(beanDefinitionReader); }
跟蹤3.1XmlBeanDefinitionReader的初始化的過程
// 3.1XmlBeanDefinitionReader的初始化須要瞭解下 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); /** * Create new XmlBeanDefinitionReader for the given bean factory. * * 經過給定的 bean factory 建立一個新的 XmlBeanDefinitionReader */ public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); }
先看下幾個類的繼承關係圖。
XmlBeanDefinitionReader的構造入參接受的是BeanDefinitionRegistry,這是工廠默認實現DefaultListableBeanFactory所實現的接口
root web application context 的默認實現是 XmlWebApplicationContext,他實現了ApplicationContext,ApplicationContext有幾個上層接口比較重要。
XmlBeanDefinitionReader調用了父類的構造並傳遞 bean factory,父類構造的註釋上作了不少說明:
/** * Create a new AbstractBeanDefinitionReader for the given bean factory. * <p>If the passed-in bean factory does not only implement the BeanDefinitionRegistry * interface but also the ResourceLoader interface, it will be used as default * ResourceLoader as well. This will usually be the case for * {@link org.springframework.context.ApplicationContext} implementations. * <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}. * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its * environment will be used by this reader. Otherwise, the reader will initialize and * use a {@link StandardEnvironment}. All ApplicationContext implementations are * EnvironmentCapable, while normal BeanFactory implementations are not. * * * 經過給定的 bean factory 建立一個新的 AbstractBeanDefinitionReader。 * 若是傳入的 bean factory 不只僅實現了 BeanDefinitionRegistry 接口, * 還實現了ResourceLoader接口,那麼 bean factory 還會被當作ResourceLoader資源加載器。 * 一般這種狀況的 bean factory 它每每是ApplicationContext的實現。 * 若是給定一個簡單的BeanDefinitionRegistry實現,那麼默認的ResourceLoader採用 * PathMatchingResourcePatternResolver。 * 若是傳入的 bean factory 還實現了EnvironmentCapable接口, * 那麼這個 bean factory 的environment會被這個reader所使用。 * 否者這個reader初始化使用默認StandardEnvironment。 * 全部的 ApplicationContext 都實現了 EnvironmentCapable 接口, * 可是普通的BeanFactory實現並無實現. */ protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); //拿到 bean factory 的引用,解析後的beandefinition會放入 bean factory 中 this.registry = registry; // Determine ResourceLoader to use. // 肯定ResourceLoader資源加載器使用哪個 if (this.registry instanceof ResourceLoader) { this.resourceLoader = (ResourceLoader) this.registry; } else { // DefaultListableBeanFactory未實現ResourceLoader,因此走這個 this.resourceLoader = new PathMatchingResourcePatternResolver(); } // Inherit Environment if possible // 若是能夠的話拿到他的environment if (this.registry instanceof EnvironmentCapable) { this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); } else { // DefaultListableBeanFactory未實現EnvironmentCapable,因此走這個 // StandardEnvironment初始化的時候就會將systemEnvironment和systemProperties加載進來 this.environment = new StandardEnvironment(); } }
這樣3.1XmlBeanDefinitionReader的初始化也就走完了。
在XmlWebApplicationContext類中,跟蹤3.2標記方法
// 3.2加載beanDefinition操做 loadBeanDefinitions(beanDefinitionReader); /** * Load the bean definitions with the given XmlBeanDefinitionReader. * <p>The lifecycle of the bean factory is handled by the refreshBeanFactory method; * therefore this method is just supposed to load and/or register bean definitions. * <p>Delegates to a ResourcePatternResolver for resolving location patterns * into Resource instances. * * 使用給定的XmlBeanDefinitionReader加載 bean definitions * bean factory 的生命週期已經被refreshBeanFactory方法所處理 * 所以這個方法應該僅僅只是加載或者註冊 bean definitions * 委派一個 ResourcePatternResolver 去將 location patterns 解析成資源實例 */ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { //3.2.1返回 context 的 configLocations,若是沒有被指定則返回null //這裏web.xml只配置了一個,就是"classpath:spring/applicationContext.xml" String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { //3.2.2加載解析beanDefinitions reader.loadBeanDefinitions(configLocation); } } }
跟蹤標記3.2.1的方法
getConfigLocations 方法調用的是父類 AbstractRefreshableWebApplicationContext 的 getConfigLocations 方法
此方法又調用了更上一級父類 AbstractRefreshableConfigApplicationContext 的 getConfigLocations 方法。
//3.2.1返回 context 的配置路徑,若是沒有被指定則返回null String[] configLocations = getConfigLocations(); //此方法在AbstractRefreshableWebApplicationContext中 @Override public String[] getConfigLocations() { return super.getConfigLocations(); } //此方法在AbstractRefreshableConfigApplicationContext類中 /** * Return an array of resource locations, referring to the XML bean definition * files that this context should be built with. Can also include location * patterns, which will get resolved via a ResourcePatternResolver. * <p>The default implementation returns {@code null}. Subclasses can override * this to provide a set of resource locations to load bean definitions from. * * 返回一個資源路徑的數組,它參照了 context 應該構建的xml bean definition文件。 * 也能夠包含被ResourcePatternResolver解析的location patterns 。 * 默認的實現是返回一個null,子類能夠覆蓋並提供一個資源路徑的集合從中加載bean definitions */ protected String[] getConfigLocations() { //在web.xml文件中已經配置了contextConfigLocation屬性, //因此變量configLocations值爲classpath:spring/applicationContext.xml //順便看若是configLocations爲null,getDefaultConfigLocations()方法怎麼實現的 return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations()); } //此方法在XmlWebApplicationContext類中 /** * The default location for the root context is "/WEB-INF/applicationContext.xml", * and "/WEB-INF/test-servlet.xml" for a context with the namespace "test-servlet" * (like for a DispatcherServlet instance with the servlet-name "test"). * * root context 默認路徑是"/WEB-INF/applicationContext.xml" * 名稱空間爲"test-servlet"的上下文路徑爲"/WEB-INF/test-servlet.xml" * 像DispatcherServlet實例就是用"test"的servlet-name */ @Override protected String[] getDefaultConfigLocations() { if (getNamespace() != null) { // "/WEB-INF/" + 名稱空間 + ".xml" return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX}; } else { // "/WEB-INF/applicationContext.xml" return new String[] {DEFAULT_CONFIG_LOCATION}; } }
跟蹤標記3.2.2的方法,利用reader加載解析beanDefinitions
BeanDefinitionReader接口中對這個方法作了定義
AbstractBeanDefinitionReader中實現了這個方法
//3.2.2加載解析beanDefinitions reader.loadBeanDefinitions(configLocation); //在AbstractBeanDefinitionReader類中 /** * 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. * * 從指定的資源路徑加載bean definitions * 若是bean definition reader的ResourceLoader資源加載器是ResourcePatternResolver * 那麼這路徑也能夠是location pattern */ @Override public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); } //在AbstractBeanDefinitionReader類中 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { //這裏拿到的是 root web application context,ApplicationContext接口有繼承ResourceLoader接口 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } //ApplicationContext接口也有繼承ResourcePatternResolver接口 if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. // 可使用Resource pattern作匹配 try { // 3.2.2.1經過路徑獲取 資源處理器 數組 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 3.2.2.2經過 資源處理器 加載 BeanDefinitions int loadCount = loadBeanDefinitions(resources); // 這裏傳過來是null,跳過 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. // 只能加載絕對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; } }
跟蹤標記 3.2.2.1的方法。
該方法實際上走的是AbstractApplicationContext的getResources方法,經過 context 的成員屬性resourcePatternResolver去解析資源,也就是PathMatchingResourcePatternResolver類的getResources方法
// 3.2.2.1經過路徑獲取 資源處理器 數組 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); /** * Resolve the given location pattern into Resource objects. * <p>Overlapping resource entries that point to the same physical * resource should be avoided, as far as possible. The result should * have set semantics. * * 解析指定的location pattern 生成資源對象 * 儘量的避免指向同一物理資源的多個資源項重複 * 結果應該具備語義 */ @Override public Resource[] getResources(String locationPattern) throws IOException { return this.resourcePatternResolver.getResources(locationPattern); }
PathMatchingResourcePatternResolver實現了ResourcePatternResolver接口。在看他的實現前,先看下兩個常量的定義。
/** * Pseudo URL prefix for all matching resources from the class path: "classpath*:" * This differs from ResourceLoader's classpath URL prefix in that it * retrieves all matching resources for a given name (e.g. "/beans.xml"), * for example in the root of all deployed JAR files. * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX * * 從類路徑中匹配全部資源的僞URL前綴:"classpath*:" * 他和ResourceLoader的類路徑URL前綴("classpath:")有所不一樣 * 他經過指定名稱(例如 "/beans.xml"),檢索全部的匹配資源,像在根中的被部署的全部jar文件 */ String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; /** * Pseudo URL prefix for loading from the class path: "classpath:" * * 從類路徑中加載的僞URL前綴:"classpath:" */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; @Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); //由於web.xml中配置的是"classpath:" ,因此走第二個條件 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // 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 // 一個類路徑的 resource pattern // 能進入這個條件,說明除去前綴的後面那一部分中,帶有*或者?等表達式 return findPathMatchingResources(locationPattern); } else { // all class path resources with the given name // 給定名稱的全部類路徑資源 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } else { // Only look for a pattern after a prefix here // (to not get fooled by a pattern symbol in a strange prefix). // 只查找前綴後的pattern,也就是"classpath:spring/applicationContext.xml"中的 // "spring/applicationContext.xml"部分,(classpath可能帶*,後面的路徑也可能帶*) // 不要被在奇怪前綴中的pattern符號所迷惑 int prefixEnd = locationPattern.indexOf(":") + 1; // 這裏的判斷和前面同樣,看後部分有沒有帶*或者?等Pattern if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // a file pattern // 匹配pattern的文件 return findPathMatchingResources(locationPattern); } else { // a single resource with the given name //指定名稱的單一資源文件 //web.xml中沒有用通配符,因此走這個。 //getResourceLoader返回的是 root web application context, //getResource方法進的是DefaultResourceLoader類中, //DefaultResourceLoader是 root web application context 的上層父類,進入這個方法 return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } } /** * Return a Resource handle for the specified resource. * The handle should always be a reusable resource descriptor, * allowing for multiple {@link Resource#getInputStream()} calls. * <p><ul> * <li>Must support fully qualified URLs, e.g. "file:C:/test.dat". * <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat". * <li>Should support relative file paths, e.g. "WEB-INF/test.dat". * (This will be implementation-specific, typically provided by an * ApplicationContext implementation.) * </ul> * <p>Note that a Resource handle does not imply an existing resource; * you need to invoke {@link Resource#exists} to check for existence. * * 根據指定的資源返回一個資源處理器 * 這個處理器是一個可以被重複使用的資源描述符 * 容許多種輸入流的調用方式 * 必定支持徹底限定URLS,例如 "file:C:/test.dat". * 必定支持類路徑僞URLS,例如 "classpath:test.dat". * 應該支持相對文件路徑,例如 "WEB-INF/test.dat". *(一般根據不一樣的ApplicationContext實現,這個方法的相應實現也不同) * 注意,資源處理器並不意味着一個真實存在的資源,須要調用exists方法去檢測 */ @Override 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)) { //前綴被幹掉 return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... // 嘗試做爲一個URL去解析 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. // 不是URL,仍是做爲資源路徑去解析 return getResourceByPath(location); } } }
跟蹤標記3.2.2.2的方法
此方法在類AbstractBeanDefinitionReader中
// 3.2.2.2經過 資源處理器 加載 BeanDefinitions int loadCount = loadBeanDefinitions(resources); @Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { counter += loadBeanDefinitions(resource); } return counter; }
counter += loadBeanDefinitions(resource); -> 此方法裏面的內容很是多,單獨開了一篇文章:
https://www.jianshu.com/p/a0cfaedf3fc5
接下來跟蹤prepareBeanFactory方法:
https://www.jianshu.com/p/3468118a31f9
——————————————————————————————————
——————————————————————————————————
——————————————————————————————————
web.xml
中沒有配置,默認去/WEB-INF/
下查找Reader
實例去解析配置文件路徑——————————————————————————————————
classpath
只加載類路徑下的資源,classpath*
檢索全部的匹配資源,包括jar文件。