spring是日常开发中用的非常多的一个框架,那么spring究竟是如何帮我们简化开发?短短的几行配置里,spring究竟做了啥?后续几篇博客会分析下spring的源码。
从一个配置文件开始
使用xml配置spring的话,这个配置可以说非常熟悉了。
然后如果想通过spring容器来加载配置这个类,简单的代码如下。
public class TestDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) context.getBean("person");
System.out.println("person name:" + person.getName());
}
}
假想下,如果让我来写spring,那么我要做的第一步是啥?我想会是找到配置文件,加载它
(这里先不管使用java配置的方式)
Spring对资源的封装
spring对于各种各样的资源抽象了一个接口,比如文件资源或者类路径的资源。
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return true;
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
所有的资源都会通过这个类来抽象。
那么简单的说来,spring容器加载资源的第一步,就是加载配置文件,将这个配置文件转换成spring的抽象资源Resource
源码实现
源码还是比较简单的
1)在构造函数里,将路径处理下(替换占位符)存储在成员变量里
2)将配置文件转换为spring的一个资源(具体步骤在loadBeanDefinition里)
解析路径代码
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
//1、父类设置ResourcePatternResolver
super(parent);
//设置路径到configLocations成员变量里,中间会执行一步,替换${}这样的占位符,
//比如路径填了 ${path}/application.xml,可以被替换为.properties里的路径
setConfigLocations(configLocations);
if (refresh) {
//实际启动spring容器
refresh();
}
}
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++) {
//把占位符给换掉 比如${path.xxx} 换成PropertyPlaceHolder的值
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
AbstractXmlApplicationContext类
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);
}
}
最后会回调DefaultResourceLoader的getResources方法
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 = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
// 没有填前缀,最后会被解析为 ClassPathContextResource
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
由于这里没有配置协议前缀(比如classpath:xxx)最后资源会被解析为ClassPathContextResource
ResourceLoader和ResourcePatternLoader
总结
spring启动会去加载配置文件,将配置文件转换为spring可以识别的Resource