Spring第一步,资源来开路。
连接: https://juejin.im/post/5d2945...
Spring资源的加载逻辑比较复杂,咱们以相对简单的FileSystemXmlApplicationContext为例来说解BeanDefinition的定位过程。java
后续的文章中,将更进一步的带领你们逐步深刻地了解Spring的的运行流程ide
FileSystemXmlApplicationContext 用于从文件系统中加载指定的Xml文件,来以此做为Spring资源,下面是构造函数
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { //初始化基类,主要是AbstractApplicationContext的初始化 super(parent); //设置资源(xml文件) setConfigLocations(configLocations); if (refresh) { //调用AbstractApplicationContext的refresh方法,进行容器的刷新 refresh(); } }
refresh是Spring容器的核心方法,咱们此文中仅仅探讨前两项内容。
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //准备刷新容器,通知子类刷新容器 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. //获取BeanFactory,实际是获取子类配置的BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); ..... //下面代码与BeanDefinition资源的定位、载入、注册关系不大,不在此处分析,后续文章中进行分析
protected void prepareRefresh() { this.startupDate = System.currentTimeMillis(); //获取激活锁,设置激活状态 synchronized (this.activeMonitor) { this.active = true; } if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); } // Initialize any placeholder property sources in the context environment //初始化属性源,交由子类配置(FileSystemXmlApplicationContext没有重写此方法 initPropertySources(); // Validate that all properties marked as required are resolvable // see ConfigurablePropertyResolver#setRequiredProperties //验证全部标记为必须的属性,此处没有进行任何须须的配置,因此验证经过 getEnvironment().validateRequiredProperties(); }
obtainFreshBeanFactory实际是调用了子类AbstractRefreshableApplicationContext的实现,
@Override protected final void refreshBeanFactory() throws BeansException { //若是以前有BeanFactory了,就销毁从新构建一个 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //建立一个BeanFactory,默认实现是DefaultListableBeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); //设置id beanFactory.setSerializationId(getId()); //1.设置一些基本属性 allowBeanDefinitionOverriding,allowCircularReferences // 是否容许beanDefinition重载,容许循环引用 //2.设置一个自动注入候选者判断器QualifierAnnotationAutowireCandidateResolver // 专用于@Querifiler @Value的条件判断 customizeBeanFactory(beanFactory); //定位、加载、注册beanDefinitiion,交由子类实现,由于不一样的业务场景下,资源的未知是不一样的,因此父类不能肯定具体的资源加载形式,因此交由子类实现,对于xml来讲是交由子类AbstractXmlApplicationContext实现, loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } } //这里是子类AbstractXmlApplicationContext实现 @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. //建立一个XmlBeanDefinitionReader,并初始化 //向XmlBeanDefinitionReader //设置一个BeanDefinitionRegistry //设置一个ResourceLoader //由于DefaultListableBeanFactory不是一个ResoueceLoader,因此这里用了默认值PathMatchingResourcePatternResolver //设置环境,用的默认值StandardEnvironment //可是不要慌,下面的代码中,就会使用FileSystemXmlApplicationContext来替换这两个值 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. //使用FileSystemXmlApplicationContext中的环境替换 beanDefinitionReader.setEnvironment(this.getEnvironment()); //使用FileSystemXmlApplicationContext来做为资源加载器 beanDefinitionReader.setResourceLoader(this); //设置一个实体解析器,用于获取XML中的校验DTD文件,下篇文章中会使用到,这里是一个伏笔 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); //定位、加载、注册 loadBeanDefinitions(beanDefinitionReader); } // protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //咱们使用的是String配置的资源,不会走这个加载 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } //今后处进入 String[] configLocations = getConfigLocations(); if (configLocations != null) { //使用XmlBeanDefinitionReader定位、加载、注册指定的configLocations reader.loadBeanDefinitions(configLocations); } } //这里传入的String[]类型,因此调用的是XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader的方法 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); //加载的BeanDefinition的个数统计 int counter = 0; //迭代,加载全部的location for (String location : locations) { //加载并统计数量 counter += loadBeanDefinitions(location); } return counter; } public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); } //加载流程的具体实现 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { //获取ResoruceLoader,实际就是上文中传入的FileSystemXmlApplicaitonContext ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } //FileSystemXmlApplicaitonContext实现了这个ResourcePatternResolver接口 if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //这行很重要,这里就是资源定位和加载的核心代码,这里是利用FileSystemXmlApplicaitonContext来进行资源的定位和加载,具体分析见下文的资源定位 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); //资源的加载和BeanDefintiion的注册 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; } }
下面是Spring真正加载资源的逻辑
//FileSystemXmlApplicationContext自己并无进行资源的加载,而是调用了基类AbstractApplicaiotnContext资源加载的方法,注意此处的方法名是 getResources , //内部实际是调用本身内部的resourcePatternResolver,这个resourcePatternResolver是在AbstractApplicationContext实例化是被建立的,是一个PathMatchingResourcePatternResolver //因此这里资源的加载是先交给PathMatchingResourcePatternResolver来解析 public Resource[] getResources(String locationPattern) throws IOException { return this.resourcePatternResolver.getResources(locationPattern); }
public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); //若是以 classpath*: 开头 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // a class path resource (multiple resources for same name possible) //若是是Ant表达式,则进入此 if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { // a class path resource pattern return findPathMatchingResources(locationPattern); } else { //不然在classpath中寻找资源 // all class path resources with the given name return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } //若是不以classpath*:开头 else { //查看 : 后面的路径 int prefixEnd = locationPattern.indexOf(":") + 1; //若是:后的路径符合ant表达式 if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // a file pattern return findPathMatchingResources(locationPattern); } else { //最后 : 后的表达式不是ant表达式的话,就调用本身的ResourceLoader进行资源的加载 //注意 PathMatchingResourcePatternResolver的构造函数中,已经把AbstractApplicationCotexnt做为了本身的资源加载器,因此此处调用的方法就是AbstractApplicationContext的getResource,注意这个方法的名称,是getResource,不是getResources //由于AbstractApplicationContext继承了DefaultResourceLoader,因此此处调用的getResource,实际调用的DafaultResourceLoader的getResource方法, // a single resource with the given name return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } }
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); //若是 location 以 classpath: 开头,就返回一个ClassPathResouce if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { //不以classpath: 开头的话,就尝试使用url来获取资源,若是不抛出异常,就返回一个UrlResource资源 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { //异常出现,说明url不能正确解析,只好调用 getResourceByPath 来加载资源 //注意 DefaultResourceLoader中已有getResourceByPath的实现,就是把location看成一个ClassPathContextResource来解析,可是在此处并非,由于FileSystemXmlApplicationContext重写了这个方法,因此getResourceByPath实际是调用的FileSystemXmlApplicationContext中的实现, return getResourceByPath(location); } } }
//能够看出,把资源看成一个FileSystemResource返回,至此,咱们就找到了真正的资源位置,完成了资源的定位 protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
咱们能够发现Spring中对于资源的定位是比较复杂的,我大体梳理一下,大体逻辑以下:函数
使用PathMatchingResourcePatternResolver来解析Ant表达式路径,成功则返回,失败则向下post
若是是classpath* 开头的资源 ,ui
- 符合Ant规则的按照Ant路径解析
- 不符合Ant规则的,解析成ClasspathResource
不是classpath*开头的资源this
- 若是 :后面的路径符合Ant规则,按照Ant路径解析
- :后的路径不符合Ant规则,调用传入的ResouceLoader来解析(AbstractApplicaitonContext把这份工做交由DefaultResourceLoader来执行)
使用DefaultResouceLoader加载资源url
- 若是资源以 classpath: 开头,返回 ClassPathResource
不是 classpath: 开头spa
- 按照Url解析不出错,返回UrlResource
- 解析Url出错了,调用getResourceByPath来解析(这个方法被FileSystemXmlApplicationContext重写了)
以上就是FileSystemXmlApplicationContext定位资源的基本流程。debug