Spring源码阅读目录
第一部分——IOC篇
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
第二部分——AOP篇
第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇
第三部分——事务篇
第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇
第四部分——MVC篇
第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇
第五部分——Boot篇
第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇
文章目录
- Spring源码阅读目录
- 第一部分——IOC篇
- 第二部分——AOP篇
- 第三部分——事务篇
- 第四部分——MVC篇
- 第五部分——Boot篇
- 前言
- 尝试动手写IOC容器
- 第四十四版 环境
- 属性源
- 多个属性文件
- 操作属性
- 环境配置文件
- 环境
- 测试
- 总结
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第五十二章 Spring之再进一步——Boot 中,A君 已经对 Boot 进行了相关调研,并对相关实现进行了一番分析。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大 要求 A君在一周内开发个简单的 IOC容器
前情提要:A君 已经对 Boot 进行了相关调研,并对相关实现进行了一番分析 。。。
第四十四版 环境
A君 今天刚到公司就迫不及待的去找 老大 了,将昨天的调研成果一五一十的说出来。。。
老大 静静地听 A君 说完后,开口道:“不错,大概的思路确实是这么回事。不过呢,我昨天稍微看了下,目前你弄的 IOC容器 还不具备进入 Boot 的条件,需要进行一些补充。”
“啊?是有什么缺陷吗?” A君 惊讶道
“也不算吧,只是功能有些不足。” 老大 说道
“那今天需要做什么呢?” A君 追问道
“今天,你就先把 环境 搞定吧!”
“环境?啥意思,没明白。” A君 疑惑道
“比如说:你的jar程序运行在操作系统上,这时候就会有系统的相关配置,如果是运行在Web环境上,则会有Web相关的配置。总的来说,就是用来获取不同环境的配置、信息的。” 老大 缓缓说道
“好吧,那我明白了。”
“那去吧,做做看。”
属性源
从 老大 办公室出来后,A君 回到了自己的工位上,开始琢磨如何实现所谓的 环境。听 老大 的意思,环境 有包含一堆的配置,比如说:环境变量、系统属性、命令行参数、配置文件一堆东西,这些东西基本都是以key-value
的形式进行存储的,只是来源不同,这部分内容可以单独抽取出来。A君 先定义个PropertySource
类,用以区分不同的来源。代码如下:
/**
* 配置源
*
* @param <T>
*/
public abstract class PropertySource<T> {
protected final String name;
protected final T source;
public abstract Object getProperty(String name);
//省略其他代码。。。
/**
* 有些时候,配置无法直接初始化,需要延迟初始化,这时候需要存根
* 例如:Web环境下需要等ServletContext初始化后才能有值
*/
public static class StubPropertySource extends PropertySource<Object> {
@Override
public String getProperty(String name) {
return null;
}
}
/**
* 集合比较
*/
static class ComparisonPropertySource extends StubPropertySource {
@Override
public String getProperty(String name) {
throw new UnsupportedOperationException(USAGE_ERROR);
}
}
}
这些属性一般都是集合,可以遍历的,所以可以在提取一个抽象类。A君 定义EnumerablePropertySource
类,代码如下:
/**
* 可以遍历的属性
*
* @param <T>
*/
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
public abstract String[] getPropertyNames();
}
除却可遍历之外,这些属性一般也都是key-value
结构。所以可以再次提取一个抽象类。A君 现在已经快魔怔了,划分的这么细。新增MapPropertySource
类,代码如下:
public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
public MapPropertySource(String name, Map<String, Object> source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
return this.source.get(name);
}
@Override
public boolean containsProperty(String name) {
return this.source.containsKey(name);
}
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.keySet());
}
}
剩下的几个具体实现基本都差不多了,A君 就以配置文件为例。新增PropertiesPropertySource
类,代码如下:
public class PropertiesPropertySource extends MapPropertySource {
@Override
public String[] getPropertyNames() {
synchronized (this.source) {
return super.getPropertyNames();
}
}
}
多个属性文件
正常情况下,配置文件可能只有一个,也可能存在多个,不能一概而论。但是多个肯定没错,嘿嘿!于是,A君 再定义了一个PropertySources
接口。代码如下:
/**
* 多个配置文件
*/
public interface PropertySources extends Iterable<PropertySource<?>> {
/**
* 流式操作
*
* @return
*/
default Stream<PropertySource<?>> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* 是否包含某个配置
*
* @param name
* @return
*/
boolean contains(String name);
/**
* 获取指定配置
*
* @param name
* @return
*/
PropertySource<?> get(String name);
}
再给其个默认实现,A君 新增MutablePropertySources
类,代码如下:
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
@Override
public Stream<PropertySource<?>> stream() {
return this.propertySourceList.stream();
}
@Override
public boolean contains(String name) {
for (PropertySource<?> propertySource : this.propertySourceList) {
if (propertySource.getName().equals(name)) {
return true;
}
}
return false;
}
@Override
public PropertySource<?> get(String name) {
for (PropertySource<?> propertySource : this.propertySourceList) {
if (propertySource.getName().equals(name)) {
return propertySource;
}
}
return null;
}
//省略其他代码。。。
}
基本上就是一些循环操作,没啥好说的
操作属性
属性源基本已经定义完成了,现在 A君 把重心放在了操作属性上,毕竟直接给属性源不够友好嘛。操作弄来弄去,无非就是CRUD
罢了。操作属性甚至不需要这么多,只需要查找就行了,毕竟这些都是无法修改的。好,思量完毕,A君 新增PropertyResolver
接口,代码如下:
/**
* 属性操作
*/
public interface PropertyResolver {
/**
* 配置文件是否包含某个key
*
* @param key
* @return
*/
boolean containsProperty(String key);
/**
* 获取某个值
*
* @param key
* @return
*/
String getProperty(String key);
/**
* 获取某个值,没有则用默认值
*
* @param key
* @param defaultValue
* @return
*/
String getProperty(String key, String defaultValue);
/**
* 获取值并转出某个类型
*
* @param key
* @param targetType
* @param <T>
* @return
*/
<T> T getProperty(String key, Class<T> targetType);
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
/**
* 获取某个值,必须存在,不存在则抛出异常
*
* @param key
* @return
* @throws IllegalStateException
*/
String getRequiredProperty(String key) throws IllegalStateException;
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
/**
* 包含占位符
*
* @param text
* @return
*/
String resolvePlaceholders(String text);
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
顶级接口已经定义好了,接着需要考虑下拓展了,毕竟涉及到属性操作的,一般都需要进行类型转换,除了类型转换,还有什么?A君 想到了之前工作中经常使用占位符。例如:
@Component
public class ApiService {
@Value("${api.url}")
private String apiUrl;
}
这种情况就会涉及到前后缀的问题。为此,A君 新增ConfigurablePropertyResolver
接口,代码如下:
public interface ConfigurablePropertyResolver extends PropertyResolver {
/**
* 类型转换
*
* @return
*/
ConfigurableConversionService getConversionService();
void setConversionService(ConfigurableConversionService conversionService);
/**
* 设置占位符前缀
*
* @param placeholderPrefix
*/
void setPlaceholderPrefix(String placeholderPrefix);
/**
* 占位符后缀
*
* @param placeholderSuffix
*/
void setPlaceholderSuffix(String placeholderSuffix);
/**
* 分隔符
*
* @param valueSeparator
*/
void setValueSeparator(String valueSeparator);
/**
* 忽略无法处理的属性
*
* @param ignoreUnresolvableNestedPlaceholders
*/
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
void setRequiredProperties(String... requiredProperties);
void validateRequiredProperties() throws MissingRequiredPropertiesException;
}
抽象类和实现类基本就做些校验,类型转换的事情,没啥东西。简单看下PropertySourcesPropertyResolver
就行了,代码如下:
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
private final PropertySources propertySources;
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);
if (value != null) {
/**
* 字符串,且包含占位符
*/
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
/**
* 进行类型转换
*/
return convertValueIfNecessary(value, targetValueType);
}
}
}
return null;
}
}
环境配置文件
在日常开发中,A君 在本机开发完成后,自测完成后部署到测试环境给测试人员进行测试,然后再上版验环境,最后上生产环境。这些个环境不可谓不多,而环境之间配置往往是不同的,像一些公共组件:数据库、Nacos
、MQ
等各个环境的地址往往是不同的,不可能说部署一次改一次配置文件,这样太麻烦。正常情况下都会为每个环境提供个配置文件,部署时去激活对应环境的配置文件即可。例:
既然有此需求,那么框架自然也得支持了。A君 新增Profiles
接口。代码如下:
@FunctionalInterface
public interface Profiles {
static Profiles of(String... profiles) {
return ProfilesParser.parse(profiles);
}
/**
* 匹配环境文件
*
* @param activeProfiles
* @return
*/
boolean matches(Predicate<String> activeProfiles);
}
接着就是实现了,为了支持更复杂的业务场景,需要支持简单的逻辑表达式:or
、and
、not
、equals
这些,具体的解析方式 A君 就不细说,在 第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式 中有更为详细的说明。ProfilesParser
代码如下:
final class ProfilesParser {
private ProfilesParser() {
}
static Profiles parse(String... expressions) {
Profiles[] parsed = new Profiles[expressions.length];
for (int i = 0; i < expressions.length; i++) {
parsed[i] = parseExpression(expressions[i]);
}
return new ParsedProfiles(expressions, parsed);
}
private static Profiles parseExpression(String expression) {
StringTokenizer tokens = new StringTokenizer(expression, "()&|!", true);
return parseTokens(expression, tokens);
}
private static Profiles parseTokens(String expression, StringTokenizer tokens) {
return parseTokens(expression, tokens, Context.NONE);
}
private static Profiles parseTokens(String expression, StringTokenizer tokens, Context context) {
List<Profiles> elements = new ArrayList<>();
Operator operator = null;
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken().trim();
if (token.isEmpty()) {
continue;
}
switch (token) {
case "(":
Profiles contents = parseTokens(expression, tokens, Context.BRACKET);
if (context == Context.INVERT) {
return contents;
}
elements.add(contents);
break;
case "&":
assertWellFormed(expression, operator == null || operator == Operator.AND);
operator = Operator.AND;
break;
case "|":
assertWellFormed(expression, operator == null || operator == Operator.OR);
operator = Operator.OR;
break;
case "!":
elements.add(not(parseTokens(expression, tokens, Context.INVERT)));
break;
case ")":
Profiles merged = merge(expression, elements, operator);
if (context == Context.BRACKET) {
return merged;
}
elements.clear();
elements.add(merged);
operator = null;
break;
default:
Profiles value = equals(token);
if (context == Context.INVERT) {
return value;
}
elements.add(value);
}
}
return merge(expression, elements, operator);
}
private static Profiles merge(String expression, List<Profiles> elements, Operator operator) {
assertWellFormed(expression, !elements.isEmpty());
if (elements.size() == 1) {
return elements.get(0);
}
Profiles[] profiles = elements.toArray(new Profiles[0]);
return (operator == Operator.AND ? and(profiles) : or(profiles));
}
private static Profiles or(Profiles... profiles) {
return activeProfile -> Arrays.stream(profiles).anyMatch(isMatch(activeProfile));
}
private static Profiles and(Profiles... profiles) {
return activeProfile -> Arrays.stream(profiles).allMatch(isMatch(activeProfile));
}
private static Profiles not(Profiles profiles) {
return activeProfile -> !profiles.matches(activeProfile);
}
private static Profiles equals(String profile) {
return activeProfile -> activeProfile.test(profile);
}
private static Predicate<Profiles> isMatch(Predicate<String> activeProfile) {
return profiles -> profiles.matches(activeProfile);
}
private enum Operator {AND, OR}
private enum Context {NONE, INVERT, BRACKET}
private static class ParsedProfiles implements Profiles {
private final Set<String> expressions = new LinkedHashSet<>();
private final Profiles[] parsed;
ParsedProfiles(String[] expressions, Profiles[] parsed) {
Collections.addAll(this.expressions, expressions);
this.parsed = parsed;
}
@Override
public boolean matches(Predicate<String> activeProfiles) {
for (Profiles candidate : this.parsed) {
if (candidate.matches(activeProfiles)) {
return true;
}
}
return false;
}
}
}
环境
兜兜转转半天,终于可以进入主题了。不过照目前情况来看,所谓的 环境
也就没什么了。无非就是操作Profiles
、PropertyResolver
罢了。A君 新增Environment
接口,代码如下:
public interface Environment extends PropertyResolver {
/**
* 获取激活配置
*
* @return
*/
String[] getActiveProfiles();
/**
* 获取默认配置
*
* @return
*/
String[] getDefaultProfiles();
@Deprecated
boolean acceptsProfiles(String... profiles);
boolean acceptsProfiles(Profiles profiles);
}
接下来就是让他可编程、可操作了。A君 新增ConfigurableEnvironment
接口,代码如下:
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
MutablePropertySources getPropertySources();
Map<String, Object> getSystemProperties();
Map<String, Object> getSystemEnvironment();
void merge(ConfigurableEnvironment parent);
}
好了,接下来就是抽象类,基本就是获取激活的配置文件就行了。A君 新增AbstractEnvironment
类,代码如下:
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
/**
* 激活的配置
*/
private final Set<String> activeProfiles = new LinkedHashSet<>();
/**
* 默认的配置
*/
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
/**
* 配置源
*/
private final MutablePropertySources propertySources;
private final ConfigurablePropertyResolver propertyResolver;
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
/**
* 获取激活的配置
*/
String profiles = doGetActiveProfilesProperty();
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
protected String doGetActiveProfilesProperty() {
return getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
}
//省略其他代码。。。
}
在提供个标准实现,基本也没啥事可做。A君 新增StandardEnvironment
类。代码如下:
public class StandardEnvironment extends AbstractEnvironment {
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
测试
现在全部都定义好,可以进入测试阶段了。这个测试也相当的简单。先新增一个配置文件application.properties
。如下:
myapp.name=evnTest
myapp.version=1.0
这就可以编写测试代码了。代码如下:
@Test
public void v44() throws Exception {
System.out.println("############# 第四十四版: 环境篇 #############");
StandardEnvironment environment = new StandardEnvironment();
// 创建 Properties 对象并加载配置文件
Properties properties = new Properties();
properties.load(Main.class.getResourceAsStream("/v44/application.properties"));
// 将属性加载到环境中
environment.getPropertySources().addFirst(new PropertiesPropertySource("myapp", properties));
// 读取配置属性
String appName = environment.getProperty("myapp.name");
String appVersion = environment.getProperty("myapp.version");
System.out.println("App Name: " + appName);
System.out.println("App Version: " + appVersion);
}
测试结果如下:
OK,今天也比较简单,可以下班啦
总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)