spring是日常開發中用的非常多的一個架構,那麼spring究竟是如何幫我們簡化開發?短短的幾行配置裡,spring究竟做了啥?後續幾篇部落格會分析下spring的源碼。
從一個配置檔案開始
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.hdj.learn.spring.demo.Person">
<property name="name" value="duanji"></property>
</bean>
</beans>
使用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