从底层分析Spring源码,原来Spring是这么回事

Posted 程序员权威指南

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从底层分析Spring源码,原来Spring是这么回事相关的知识,希望对你有一定的参考价值。

我们初次了解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的实例对象。

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);
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}

Environment接口

类图:

从底层分析Spring源码,原来Spring是这么回事

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接口

类图

从底层分析Spring源码,原来Spring是这么回事

这个接口实际上就是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来源。继承体系:

从底层分析Spring源码,原来Spring是这么回事

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分支):

从底层分析Spring源码,原来Spring是这么回事

此接口正是用来解析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容器,其继承体系:

从底层分析Spring源码,原来Spring是这么回事

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

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

从底层分析Spring源码,原来Spring是这么回事

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

BeanDefinitionReader

继承体系:

从底层分析Spring源码,原来Spring是这么回事

路径解析(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是代表一种资源的接口,其类图:

从底层分析Spring源码,原来Spring是这么回事

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


以上是关于从底层分析Spring源码,原来Spring是这么回事的主要内容,如果未能解决你的问题,请参考以下文章

Spring中依赖注入底层原理与源码分析

深入浅出Spring原理及实战「源码原理实战」从底层角度去分析研究PropertySourcesPlaceholderConfigurer的原理及实战注入机制

Spring Boot 2从入门到入坟 | 自动配置篇:源码分析之自动包规则原理

spring底层源码分析介绍

低价2020最新版spring底层源码分析

Spring源码分析之 常见底层核心注解