通过深入剖析Spring配置的抽象设计,针对性讲解spring配置框架,让你感受抽象的魅力。你说配置就是properties文件,这个抽象层次就很低,因为yaml也可以是配置文件。好的抽象能为系统提供必要的扩展性和可维护性。
1.实现一个类似于Spring的配置框架
何为配置?所谓配置是一种通过调整参数,在不改动软件代码的情况下,改变系统行为的方式。接下来为大家抽象配置必做的三件事情,你看是不是这个理?
- 配置内容获取
- 配置内容解析
- 配置项赋值
如果认同以上观点请往下面接着看。要想实现这样的功能,框架必须要具备一定的灵活性和扩展性。一定要设计得足够抽象,不能写死。比如关于文件格式你如果写死为properties,就没办法支持yaml和xml了。要让设计满足这样的灵活性,有三个核心抽象,你必须要了解这三个抽象分别是Resource抽象(配置内容获取),PropertySource抽象(配置内容解析),以及PropertySources抽象(配置项赋值)。
1.1 配置内容获取:Resource抽象
获取配置内容,是配置系统要做的第一件事情。配置内容(通常是以配置文件的形式)可以从classpath,文件系统或者网络字节流获取。**所以我们需要一个相对大的概念,来统合这些差异性,Resource是个不错的选择,因为它能屏蔽配置内容来源、及格式的差异性。**因为这个抽象程度比较高,所以共性就只剩下对外部资源字节流的获取了。如下所示,我们在Resource抽象中只定义了getInputStream()来获取字节输入流(最好的选择)。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.core.io;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import org.springframework.lang.Nullable;
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return this.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(this.getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
@Nullable
String getFilename();
String getDescription();
}
不同类型的Resource意味着获取字节流的方式会不一样,比如ClassPathResource是从ClassPath获取文件。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.core.io;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
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;
}
/** @deprecated */
@Deprecated
protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
this.path = StringUtils.cleanPath(path);
this.classLoader = classLoader;
this.clazz = clazz;
}
public final String getPath() {
return this.path;
}
@Nullable
public final ClassLoader getClassLoader() {
return this.clazz != null ? this.clazz.getClassLoader() : this.classLoader;
}
public boolean exists() {
return this.resolveURL() != null;
}
public boolean isReadable() {
URL url = this.resolveURL();
return url != null && this.checkReadable(url);
}
@Nullable
protected URL resolveURL() {
try {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
} else {
return this.classLoader != null ? this.classLoader.getResource(this.path) : ClassLoader.getSystemResource(this.path);
}
} catch (IllegalArgumentException var2) {
return null;
}
}
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(this.getDescription() + " cannot be opened because it does not exist");
} else {
return is;
}
}
public URL getURL() throws IOException {
URL url = this.resolveURL();
if (url == null) {
throw new FileNotFoundException(this.getDescription() + " cannot be resolved to URL because it does not exist");
} else {
return url;
}
}
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);
}
@Nullable
public String getFilename() {
return StringUtils.getFilename(this.path);
}
public String getDescription() {
StringBuilder builder = new<