上週把話撂出來,看起來小夥伴們都挺期待的,其實鬆哥也火燒眉毛想要開啓一個全新的系列。java
可是目前的 Spring Security 系列還在連載中,還沒寫完。連載這事,一氣呵成,再而衰三而竭,必定要一次搞定,Spring Security 若是此次放下來,之後就很難再拾起來了。spring
因此目前的更新仍是 Spring Security 爲主,同時 Spring 源碼解讀每週至少更新一篇,等 Spring Security 系列更新完畢後,就開足馬力更新 Spring 源碼。其實 Spring Security 中也有不少和 Spring 相通的地方,Spring Security 你們文章認真看,鬆哥不會讓你們失望的!app
Spring 要從何提及呢?這個問題我考慮了很長時間。框架
由於 Spring 源碼太繁雜了,必定要選擇一個合適的切入點,不然一上來就把各位小夥伴整懵了,那剩下的文章估計就不想看了。ide
想了好久以後,我決定就先從配置文件加載講起,在逐步展開,配置文件加載也是咱們在使用 Spring 時遇到的第一個問題,今天就先來講說這個話題。ui
先來一個簡單的案例,你們感覺一下,而後咱們順着案例講起。this
首先咱們建立一個普通的 Maven 項目,引入 spring-beans 依賴:編碼
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.6.RELEASE</version> </dependency>
而後咱們建立一個實體類,再添加一個簡單的配置文件:url
public class User { private String username; private String address; //省略 getter/setter }
resources 目錄下建立配置文件:spa
<?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 class="org.javaboy.loadxml.User" id="user"/> </beans>
而後去加載這個配置文件:
public static void main(String[] args) { XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); User user = factory.getBean(User.class); System.out.println("user = " + user); }
這裏爲了展現數據的讀取過程,我就先用這個已通過期的 XmlBeanFactory 來加載,這並不影響咱們閱讀源碼。
上面這個是一個很是簡單的 Spring 入門案例,相信不少小夥伴在第一次接觸 Spring 的時候,寫出來的可能都是這個 Demo。
在上面這段代碼執行過程當中,首先要作的事情就是先把 XML 配置文件加載到內存中,再去解析它,再去。。。。。
一步一步來吧,先來看 XML 文件如何被加入到內存中去。
文件讀取在 Spring 中很常見,也算是一個比較基本的功能,並且 Spring 提供的文件加載方式,不只僅在 Spring 框架中可使用,咱們在項目中有其餘文件加載需求也可使用。
首先,Spring 中使用 Resource 接口來封裝底層資源,Resource 接口自己實現自 InputStreamSource 接口:
咱們來看下這兩個接口的定義:
public interface InputStreamSource { InputStream getInputStream() throws IOException; } public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return exists(); } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription(); }
代碼倒不難,我來稍微解釋下:
當咱們加載不一樣資源時,對應了 Resource 的不一樣實現類,來看下 Resource 的繼承關係:
能夠看到,針對不一樣類型的數據源,都有各自的實現,咱們這裏來重點看下 ClassPathResource 的實現方式。
ClassPathResource 源碼比較長,我這裏挑一些關鍵部分來和你們分享:
public class ClassPathResource extends AbstractFileResolvingResource { private final String path; @Nullable private ClassLoader classLoader; @Nullable private Class<?> clazz; public ClassPathResource(String path) { this(path, (ClassLoader) null); } public ClassPathResource(String path, @Nullable ClassLoader classLoader) { Assert.notNull(path, "Path must not be null"); String pathToUse = StringUtils.cleanPath(path); if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } this.path = pathToUse; this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); } public ClassPathResource(String path, @Nullable Class<?> clazz) { Assert.notNull(path, "Path must not be null"); this.path = StringUtils.cleanPath(path); this.clazz = clazz; } public final String getPath() { return this.path; } @Nullable public final ClassLoader getClassLoader() { return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader); } @Override public boolean exists() { return (resolveURL() != null); } @Nullable protected URL resolveURL() { if (this.clazz != null) { return this.clazz.getResource(this.path); } else if (this.classLoader != null) { return this.classLoader.getResource(this.path); } else { return ClassLoader.getSystemResource(this.path); } } @Override public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } if (is == null) { throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist"); } return is; } @Override public URL getURL() throws IOException { URL url = resolveURL(); if (url == null) { throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist"); } return url; } @Override public Resource createRelative(String relativePath) { String pathToUse = StringUtils.applyRelativePath(this.path, relativePath); return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) : new ClassPathResource(pathToUse, this.classLoader)); } @Override @Nullable public String getFilename() { return StringUtils.getFilename(this.path); } @Override public String getDescription() { StringBuilder builder = new StringBuilder("class path resource ["); String pathToUse = this.path; if (this.clazz != null && !pathToUse.startsWith("/")) { builder.append(ClassUtils.classPackageAsResourcePath(this.clazz)); builder.append('/'); } if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } builder.append(pathToUse); builder.append(']'); return builder.toString(); } }
這是 ClassPathResource,另一個你們可能會接觸到的 FileSystemResource ,小夥伴們能夠自行查看其源碼,比 ClassPathResource 簡單。
若是不是使用 Spring,咱們僅僅想本身加載 resources 目錄下的資源,也能夠採用這種方式:
ClassPathResource resource = new ClassPathResource("beans.xml"); InputStream inputStream = resource.getInputStream();
拿到 IO 流以後自行解析便可。
在 Spring 框架,構造出 Resource 對象以後,接下來還會把 Resource 對象轉爲 EncodedResource,這裏會對資源進行編碼處理,編碼主要體如今 getReader 方法上,在獲取 Reader 對象時,若是有編碼,則給出編碼格式:
public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); } }
全部這一切搞定以後,接下來就是經過 XmlBeanDefinitionReader 去加載 Resource 了。
好啦,今天主要和小夥伴們分享一下 Spring 中的資源加載問題,這是容器啓動的起點,下篇文章咱們來看 XML 文件的解析。
若是小夥伴們以爲有收穫,記得點個在看鼓勵下鬆哥哦~