spring environment_从底层分析Spring源码,原来Spring比你想象的简单的多

  • Post author:
  • Post category:其他


v2-25badc4502d83e0467ab6a08489a76e6_1440w.jpg?source=172ae18b

我们初次了解Spring,可能是老师傅给我们的第一份源码,可能里面的代码风格影响你今后的代码风格和架构风格,但是我们需要的是打破我们思维的桎梏,让Spring不再成为我们编码上的圣经,而是让Spring成为我们架构之路上的一名得力干将,那么你真的了解这个框架所使用的架构方式,下面我们各个方面全面分析Spring源码,让Spring不再神秘!

从最基本的Spring初始化配置文件开始。配置文件:

<?xml version="1.0" encoding="UTF-8"?>    
<beans>    
   <bean class="base.SimpleBean"></bean>
</beans>

SimpleBean:

public static void main(String[] args) {
   ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
   SimpleBean bean = context.getBean(SimpleBean.class);
   bean.send();
   context.close();
}
public class SimpleBean {
   public void send() {
       System.out.println("I am send method from SimpleBean!");
  }
}

启动代码:

 I am send method from SimpleBean!

在代码中,通过ClassPathXmlApplicationContext类将bean注入到容器中,然后使用getBean方法从容器中获取Bean的实例对象。

v2-d432c1bf637ecd0a9c9ced9be4438d7d_b.jpg
ResourseLoader

这是Spring加载Bean的类图,这里我们可以看到在Spring在加载Bean的实例到容器中使用的是策略模式,我们可以看到在Bean加载的实现方法中有两个ClassPathXmlApplicationContect和FileSystemXmlApplicationContext,其作用都是加载配置文件,不同的是我们在使用的文件路径的区别!

首先我们看父类的构造器,沿着类图我们追溯到AbstractApplicationContext:

public AbstractApplicationContext(ApplicationContext parent) {
    this();
    setParent(parent);
}
public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
}

getResourcePatternResolver:

protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

PathMatchingResourcePatternResolver支持Ant风格的路径解析。


设置配置文件路径

即AbstractRefreshableConfigApplicationContext.setConfigLocations:

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;
  }
}

getEnvironment方法来自于ConfigurableApplicationContext接口,源码很简单,如果为空就调用createEnvironment创建一个。AbstractApplicationContext.createEnvironment:

protected String resolvePath(String path) {
   return getEnvironment().resolveRequiredPlaceholders(path);
}

此方法的目的在于将占位符(placeholder)解析成实际的地址。比如可以这么写:

new ClassPathXmlApplicationContext("classpath:config.xml");

那么classpath:就是需要被解析的。

protected ConfigurableEnvironment createEnvironment() {
   return new StandardEnvironment();
}

Environment接口

类图:

v2-96a74526abe5784999c45d759c94ec8e_b.jpg

Environmen接口

代表了当前应用所处的环境。

从此接口的方法可以看出,其主要和profile、Property相关。


Profile

Spring Profile特性是从3.1开始的,其主要是为了解决这样一种问题: 线上环境和测试环境使用不同的配置或是数据库或是其它。有了Profile便可以在 不同环境之间无缝切换。

Spring容器管理的所有bean都是和一个profile绑定在一起的。

使用了Profile的配置文件示例:

<beans profile="develop">  
   <context:property-placeholder location="classpath*:jdbc-develop.properties"/>  
</beans>  
<beans profile="production">  
   <context:property-placeholder location="classpath*:jdbc-production.properties"/>  
</beans>  
<beans profile="test">  
   <context:property-placeholder location="classpath*:jdbc-test.properties"/>  
</beans>

在启动代码中可以用如下代码设置活跃(当前使用的)Profile:

context.getEnvironment().setActiveProfiles("dev");

我们也可以在web.xml文件中配置不同的profile

<context-param>  
    <param-name>spring.profiles.active</param-name>  
    <param-value>develop</param-value>  
</context-param>  


Property

这里的Property指的是程序运行时的一些参数,引用注释:

properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects,Maps, and so on.


Environment构造器

    private final MutablePropertySources propertySources;
    public AbstractEnvironment() {
        this.propertySources = new MutablePropertySources(this.logger);
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
        if(this.logger.isDebugEnabled()) {
            this.logger.debug(String.format("Initialized %s with PropertySources %s", new Object[]{this.getClass().getSimpleName(), this.propertySources}));
        }

    }


PropertySources接口

类图

v2-d9bb4e4779b30df585e424cd74a58c8d_b.jpeg

这个接口实际上就是PropertySource的容器,默认的MutablePropertiesSources实现内部含有一个CopyOnWriteArrayList作为载体。

StandardEnvironment.customizePropertySources:

/** System environment property source name: {@value} */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value} */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
   propertySources.addLast(new MapPropertySource
      (SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
   propertySources.addLast(new SystemEnvironmentPropertySource
      (SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}


PropertySource接口

PropertySource接口代表了键值对的Property来源。继承体系:

v2-e4ccf685e24081005e434f1c7b1907af_b.jpg

AbstractEnvironment.getSystemProperties:

@Override
public Map<String, Object> getSystemProperties() {
   try {
       return (Map) System.getProperties();
  }
   catch (AccessControlException ex) {
       return (Map) new ReadOnlySystemAttributesMap() {
           @Override
           protected String getSystemAttribute(String attributeName) {
               try {
                   return System.getProperty(attributeName);
              }
               catch (AccessControlException ex) {
                   if (logger.isInfoEnabled()) {
                       logger.info(format("Caught AccessControlException when accessing system " +
                               "property [%s]; its value will be returned [null]. Reason: %s",
                               attributeName, ex.getMessage()));
                  }
                   return null;
              }
          }
      };
  }
}

getSystemEnvironment方法也是一个套路,不过最终调用的是System.getenv,可以获取jvm和OS的一些版本信息。

这里的实现很有意思,如果安全管理器阻止获取全部的系统属性,那么会尝试获取单个属性的可能性,如果还不行就抛异常了。


路径Placeholder处理

AbstractEnvironment.resolveRequiredPlaceholders:

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
   //text即配置文件路径,比如classpath:config.xml
   return this.propertyResolver.resolveRequiredPlaceholders(text);
}

propertyResolver是一个PropertySourcesPropertyResolver对象:

private final ConfigurablePropertyResolver propertyResolver =
            new PropertySourcesPropertyResolver(this.propertySources);


PropertyResolver接口

PropertyResolver继承体系(排除Environment分支):

v2-013fa2c5bb6ccb6e294e5d4a0b1902d9_b.jpg

此接口正是用来解析PropertyResource。


解析

AbstractPropertyResolver.resolveRequiredPlaceholders:

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}

PropertyPlaceholderHelper

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    //三个参数分别是${, }, :
    return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
        this.valueSeparator, ignoreUnresolvablePlaceholders);
}

doResolvePlaceholders:

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
   //PlaceholderResolver接口依然是策略模式的体现
   return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
       @Override
       public String resolvePlaceholder(String placeholderName) {
           return getPropertyAsRawString(placeholderName);
      }
  });
}

其实代码执行到这里的时候还没有进行xml配置文件的解析,那么这里的解析placeHolder是什么意思呢,原因在于可以这么写:

System.setProperty("spring", "classpath");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("${spring}:config.xml");
SimpleBean bean = context.getBean(SimpleBean.class);

这样就可以正确解析。placeholder的替换其实就是字符串操作,这里只说一下正确的属性是怎么来的。实现的关键在于PropertySourcesPropertyResolver.getProperty:

@Override
protected String getPropertyAsRawString(String key) {
   return getProperty(key, String.class, false);
}
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
   if (this.propertySources != null) {
       for (PropertySource<?> propertySource : this.propertySources) {
           Object value = propertySource.getProperty(key);
           return value;
      }
  }
   return null;
}

注意,classpath:XXX这种写法的classpath前缀到目前为止还没有被处理。

很明显了,就是从System.getProperty和System.getenv获取,但是由于环境变量是无法自定义的,所以其实此处只能通过System.setProperty指定。



refresh

Spring bean解析就在此方法,所以单独提出来。

AbstractApplicationContext.refresh:

    public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if(this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }


prepareRefresh

protected void prepareRefresh() {
   this.startupDate = System.currentTimeMillis();
   this.closed.set(false);
   this.active.set(true);
   // Initialize any placeholder property sources in the context environment
   //空实现
   initPropertySources();
   // Validate that all properties marked as required are resolvable
   // see ConfigurablePropertyResolver#setRequiredProperties
   getEnvironment().validateRequiredProperties();
   // Allow for the collection of early ApplicationEvents,
   // to be published once the multicaster is available...
   this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}


属性校验

AbstractEnvironment.validateRequiredProperties:

@Override
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
   this.propertyResolver.validateRequiredProperties();
}

AbstractPropertyResolver.validateRequiredProperties:

@Override
public void validateRequiredProperties() {
    MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
    for (String key : this.requiredProperties) {
        if (this.getProperty(key) == null) {
            ex.addMissingRequiredProperty(key);
        }
    }
    if (!ex.getMissingRequiredProperties().isEmpty()) {
        throw ex;
    }
}

requiredProperties是通过setRequiredProperties方法设置的,保存在一个list里面,默认是空的,也就是不需要校验任何属性。


BeanFactory创建

由obtainFreshBeanFactory调用AbstractRefreshableApplicationContext.refreshBeanFactory:

@Override
protected final void refreshBeanFactory() throws BeansException {
   //如果已经存在,那么销毁之前的
   if (hasBeanFactory()) {
       destroyBeans();
       closeBeanFactory();
  }
   //创建了一个DefaultListableBeanFactory对象
   DefaultListableBeanFactory beanFactory = createBeanFactory();
   beanFactory.setSerializationId(getId());
   customizeBeanFactory(beanFactory);
   loadBeanDefinitions(beanFactory);
   synchronized (this.beanFactoryMonitor) {
       this.beanFactory = beanFactory;
  }
}


BeanFactory接口

此接口实际上就是Bean容器,其继承体系:

v2-6d61d6f48bb5ca3266fba5754659affd_b.jpg


eanFactory定制

AbstractRefreshableApplicationContext.customizeBeanFactory方法用于给子类提供一个自由配置的机会,默认实现:

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
   if (this.allowBeanDefinitionOverriding != null) {
       //默认false,不允许覆盖
       beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
  }
   if (this.allowCircularReferences != null) {
       //默认false,不允许循环引用
       beanFactory.setAllowCircularReferences(this.allowCircularReferences);
  }
}


Bean加载

AbstractXmlApplicationContext.loadBeanDefinitions,这个便是核心的bean加载了:

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
   // 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());
   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);
   loadBeanDefinitions(beanDefinitionReader);
}


EntityResolver

此处只说明用到的部分继承体系:

v2-8e80b3c7b2e0f1c3075193fb0bf661f2_b.jpg

EntityResolver接口在org.xml.sax中定义。DelegatingEntityResolver用于schema和dtd的解析。


BeanDefinitionReader

继承体系:

v2-e5e9d5f24a6d9672618310e6fe43d732_b.jpg


路径解析(Ant)

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) {
   Resource[] configResources = getConfigResources();
   if (configResources != null) {
       reader.loadBeanDefinitions(configResources);
  }
   String[] configLocations = getConfigLocations();
   //here
   if (configLocations != null) {
       reader.loadBeanDefinitions(configLocations);
  }
}

AbstractBeanDefinitionReader.loadBeanDefinitions:

@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;
}

之后调用:

//第二个参数为空
public int loadBeanDefinitions(String location, Set<Resource> actualResources) {
   ResourceLoader resourceLoader = getResourceLoader();
   //参见ResourceLoader类图,ClassPathXmlApplicationContext实现了此接口
   if (resourceLoader instanceof ResourcePatternResolver) {
       // Resource pattern matching available.
       try {
           Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
           int loadCount = loadBeanDefinitions(resources);
           if (actualResources != null) {
               for (Resource resource : resources) {
                   actualResources.add(resource);
              }
          }
           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);
      }
       return loadCount;
  }
}

getResource的实现在AbstractApplicationContext:

@Override
public Resource[] getResources(String locationPattern) throws IOException {
   //构造器中初始化,PathMatchingResourcePatternResolver对象
   return this.resourcePatternResolver.getResources(locationPattern);
}

PathMatchingResourcePatternResolver是ResourceLoader继承体系的一部分。

@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)) {
       // a class path resource (multiple resources for same name possible)
       //matcher是一个AntPathMatcher对象
       if (getPathMatcher().isPattern(locationPattern
          .substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
           // a class path 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).
       int prefixEnd = locationPattern.indexOf(":") + 1;
       if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
           // a file pattern
           return findPathMatchingResources(locationPattern);
      }
       else {
           // a single resource with the given name
           return new Resource[] {getResourceLoader().getResource(locationPattern)};
      }
  }
}

isPattern:

@Override
public boolean isPattern(String path) {
   return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}

可以看出配置文件路径是支持ant风格的,也就是可以这么写:

new ClassPathXmlApplicationContext("con*.xml");


配置文件加载

入口方法在AbstractBeanDefinitionReader的217行:

//加载
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//解析
int loadCount = loadBeanDefinitions(resources);

最终逐个调用XmlBeanDefinitionReader的loadBeanDefinitions方法:

@Override
public int loadBeanDefinitions(Resource resource) {
   return loadBeanDefinitions(new EncodedResource(resource));
}

Resource是代表一种资源的接口,其类图:

v2-6c026eac4856f2d6e0417279512a2608_b.jpg

EncodedResource扮演的其实是一个装饰器的模式,为InputStreamSource添加了字符编码(虽然默认为null)。这样为我们自定义xml配置文件的编码方式提供了机会。

之后关键的源码只有两行:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   InputStream inputStream = encodedResource.getResource().getInputStream();
   InputSource inputSource = new InputSource(inputStream);
   return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}

InputSource是org.xml.sax的类。

doLoadBeanDefinitions:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
   Document doc = doLoadDocument(inputSource, resource);
   return registerBeanDefinitions(doc, resource);
}

doLoadDocument:

protected Document doLoadDocument(InputSource inputSource, Resource resource) {
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
       getValidationModeForResource(resource), isNamespaceAware());
}

NamespaceAware默认false,因为默认配置了校验为true。

校验模型其实就是确定xml文件使用xsd方式还是dtd方式来校验,忘了的话左转度娘。Spring会通过读取xml文件的方式判断应该采用哪种。

documentLoader是一个DefaultDocumentLoader对象,此类是DocumentLoader接口的唯一实现。getEntityResolver方法返回ResourceEntityResolver,上面说过了。errorHandler是一个SimpleSaxErrorHandler对象。

DefaultDocumentLoader.loadDocument:

@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
   ErrorHandler errorHandler, int validationMode, boolean namespaceAware) {
   //这里就是老套路了,可以看出,Spring还是使用了dom的方式解析,即一次全部load到内存
   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   return builder.parse(inputSource);
}

createDocumentBuilderFactory比较有意思:

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware{
   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   factory.setNamespaceAware(namespaceAware);
   if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
       //此方法设为true仅对dtd有效,xsd(schema)无效
       factory.setValidating(true);
       if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
           // Enforce namespace aware for XSD...
            //开启xsd(schema)支持
           factory.setNamespaceAware(true);
            //这个也是Java支持Schema的套路,可以问度娘
           factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
      }
  }
   return factory;
}