织梦开发小说网站教程,佛山网站优化公司排名,导视设计图片,个人网站备案转公司备案前面几章#xff0c;大致讲了Spring的IOC容器的大致过程和原理#xff0c;以及重要的容器和beanFactory的继承关系#xff0c;为后续这些细节挖掘提供一点理解基础。掌握总体脉络是必要的#xff0c;接下来的每一章都是从总体脉络中#xff0c;
去研究之前没看的一些重要…前面几章大致讲了Spring的IOC容器的大致过程和原理以及重要的容器和beanFactory的继承关系为后续这些细节挖掘提供一点理解基础。掌握总体脉络是必要的接下来的每一章都是从总体脉络中
去研究之前没看的一些重要细节。 本章就是主要从Spring容器的启动开始查看一下Spring容器是怎么启动的调用了父类的构造方法有没有干了什么。 直接从创建容器为切入点进去
ApplicationContext context new ClassPathXmlApplicationContext(applicationContext.xml);
User user context.getBean(User.class); 进去之后会调用到这个方法
可以看到这里是分了三步
1、调用父类构造方法
2、设置配置文件地址
3、刷新容器
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, Nullable ApplicationContext parent)
throws BeansException {//调用父类构造方法其实没做啥就是如果有父容器默认啥空设置父容器和合并父容器的environment到当前容器super(parent);//设置配置文件地址如果有用了$、#{}表达式会解析到这些占位符拿environment里面到属性去替换返回setConfigLocations(configLocations);if (refresh) {//刷新容器是Spring解析配置加载Bean的入口。// 用了模板方法设计模型规定了容器中的一系列步骤refresh();}
} 1. super(parent)-调用父类构造方法
其实这个方法点进去会调用到一系列父类的super方法但是最终只是调用到了 AbstractApplicationContext的构造方法其实每个父类里面对应的属性都可以看一看有些都是直接初始化默认的
/*** Create a new AbstractApplicationContext with the given parent context.* param parent the parent context*/
public AbstractApplicationContext(Nullable ApplicationContext parent) {//会初始化resourcePatternResolver属性为PathMatchingResourcePatternResolver//就是路径资源解析器比如写的classpath:*会默认去加载classpath下的资源this();//设置父容器。并会copy父容器的environment属性合并到当前容器中setParent(parent);
} 1.1 this()
接下来调用自己的this方法
public AbstractApplicationContext() {//设置资源解析器this.resourcePatternResolver getResourcePatternResolver();
}
就是设置了自己的resourcePatternResolver资源解析器 1.1.1 getResourcePatternResolver()
这个代码没啥就是创建了一个默认的资源解析处理器 PathMatchingResourcePatternResolver
protected ResourcePatternResolver getResourcePatternResolver() {return new PathMatchingResourcePatternResolver(this);
}
其实这个对象的功能就是把你传进来的字符串的路径解析加载到具体的文件返回Spring能识别的Resource对象 okthis方法走完了应该就继续走之前的setParent(parent)方法 1.2 setParent(parent)
其实这里目前就是走不进去的默认的parent父容器我们这里没使用所以是空的并不会走if的逻辑
但是代码也挺简单其实就是设置了parent属性合并父容器的Environment到当前容器的Environment中
public void setParent(Nullable ApplicationContext parent) {this.parent parent;//如歌有父容器则合并父容器的Environment的元素到当前容器中//合并PropertySource也就是key和value//合并激活activeProfiles文件列表//合并默认文件列表defaultProfilesif (parent ! null) {Environment parentEnvironment parent.getEnvironment();if (parentEnvironment instanceof ConfigurableEnvironment) {getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);}}
} 当然可以假设我们设置了parent属性。
会先调用到getEnvironment方法获取环境对象如果没有的话会创建一个默认的 1.2.1 getEnvironment
Override
public ConfigurableEnvironment getEnvironment() {if (this.environment null) {this.environment createEnvironment();}return this.environment;
}
默认是空的会跑到createEnvironment方法 1.2.1.1 createEnvironment()
protected ConfigurableEnvironment createEnvironment() {return new StandardEnvironment();
}
会初始化一个StandardEnvironment类型的对象我们可以关注他的构造方法其实并没有内容但是会默认调用他的父类AbstractEnvironment构造器的方法 public AbstractEnvironment() {//这里会默认加载属性属性变量和环境信息this(new MutablePropertySources());
} 1.2.1.1.1 new MutablePropertySources
其实这个对象就是使用了迭代器的设计模式里面用 propertySourceList数组存储不同类型的PropertySource 那么PropertySource是干嘛的呢??
//存放Environment对象里的每个属性一个PropertySource对象里面存有不同的Properties对象
//Properties对象就是有key和value的键值对象
//比如namesystemProperties - 系统属性Properties对象
//比如namesystemEnv - 系统环境变量Properties对象
public abstract class PropertySourceT {protected final Log logger LogFactory.getLog(getClass());protected final String name;protected final T source;
}
这里摘取了他的属性。
其实name只是一个类型而已比如Environment包括了systemProperties系统属性和systemEnv系统环境变量两种。对应就是不同的name的属性存储器 source属性一般都是Java中的Properties对象这个对象大家应该都熟悉吧就跟map差不多有key和value一般用于读取properties文件使用 看一下下面的图就知道了Environment在Spring中算是非常重要的对象了所以必须了解 好了知道了创建了这个默认的对象即可。
接下来就是调用AbstractEnvironment的this方法进去了。 AbstractEnvironmentMutablePropertySources
protected AbstractEnvironment(MutablePropertySources propertySources) {this.propertySources propertySources;//创建属性解析器PropertySourcesPropertyResolverthis.propertyResolver createPropertyResolver(propertySources);//调用子类的方法加载系统的环境变量和系统属性到environment中customizePropertySources(propertySources);
}
可以看到这里就是设置了Environment内部的propertySources对象存储属性的容器
设置了propertyResolver属性解析器类型为PropertySourcesPropertyResolver还把刚刚那个propertySources设置进去了这个解析器在后面会用到在设置配置文件路径时会解析后面会聊到 接下来非常重要的方法就是customizePropertySources方法了其实在当前类AbstractEnvironment中是空方法是子类 StandardEnvironment实现的。这里是不是很熟悉的味道又是模版方法设计模式AbstractEnvironment规定了步骤调用了当前类的空方法子类会去覆盖这个空方法 ok我们进来了子类StandardEnvironment的customizePropertySources方法
其实可以看到这里就是写了两句代码分别就是去读取系统属性和系统环境变量的值加载到Environment中
public class StandardEnvironment extends AbstractEnvironment {/** System environment property source name: {value}. */public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME systemEnvironment;/** JVM system properties property source name: {value}. */public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME systemProperties;Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {//添加系统属性和系统环境变量封装了一个个propertySource对象添加到Environment的propertySources属性列表中propertySources.addLast(//系统属性new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));propertySources.addLast(//系统环境变量new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}}我们可以看其中一个方法getSystemEnvironment就是调用了jdk的System.getenv()方法去获取到你本机的系统环境变量的值然后最后设置到propertySources - Environment中
Override
SuppressWarnings({rawtypes, unchecked})
public MapString, Object getSystemEnvironment() {if (suppressGetenvAccess()) {return Collections.emptyMap();}try {//jdk提供的方法获取系统的环境变量return (Map) System.getenv();}catch (AccessControlException ex) {return (Map) new ReadOnlySystemAttributesMap() {OverrideNullableprotected String getSystemAttribute(String attributeName) {try {return System.getenv(attributeName);}catch (AccessControlException ex) {if (logger.isInfoEnabled()) {logger.info(Caught AccessControlException when accessing system environment variable attributeName ; its value will be returned [null]. Reason: ex.getMessage());}return null;}}};}
} 解析完的Environment的里面的值大概是这样 到这里应该是理解Environment对象了吧。 okk✋回到之前的调用getEnvironment的地方咱们已经看完这个方法啦也就是标题1.2处 接下里有了Environment对象就会进行父子容器的Environment的合并啦! 1.2.2 Environment.merge()-父子容器的Environment合并
这里的代码就非常简单了主要就是合并父容器的Environment的属性到当前子容器中
public void merge(ConfigurableEnvironment parent) {
//合并PropertySource也就是具体存在的属性键值对
for (PropertySource? ps : parent.getPropertySources()) {if (!this.propertySources.contains(ps.getName())) {this.propertySources.addLast(ps);}
}
//合并活跃的profile - 一般SpringBoot中多开发环境都会设置profile
String[] parentActiveProfiles parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {synchronized (this.activeProfiles) {Collections.addAll(this.activeProfiles, parentActiveProfiles);}
}
//合并默认的profile
String[] parentDefaultProfiles parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {synchronized (this.defaultProfiles) {this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);Collections.addAll(this.defaultProfiles, parentDefaultProfiles);}
}
} ok到这里标题1调用父类构造的方法到这里就结束了接下来继续探索setConfigLocations干了什么。 2. setConfigLocations-设置配置文件路径
/*** 设置配置文件地址并且会将文件路径格式化成标准格式* 比如applicationContext-${profile}.xml profile存在在Environment。* 假设我的Environment中有 profile dev* 那么applicationContext-${profile}.xml会被替换成 applicationContext-dev.xml* Set the config locations for this application context.* pIf not set, the implementation may use a default as appropriate.*/
public void setConfigLocations(Nullable 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) {//解析当前配置文件的地址并且将地址格式化成标准格式this.configLocations[i] resolvePath(locations[i]).trim();}}else {this.configLocations null;}
}
这里关键的方法是会调用到resolvePath方法并返回这些字符串路径 点进去有没有感觉到很惊喜为什么用了getEnvironment去调用的呢
其实之前的getEnvironment并没有执行到因为我们没有设置父类parent到这里才是第一次初始化这个Environment对象然后调用它的resolveRequiredPlaceholders方法去解析路径 这里关Environment什么事呢其实我们可以动态地写我们的配置文件配置文件会去读取占位符判断在Environment是否存在这些属性并完成替换
protected String resolvePath(String path) {//这里的获取getEnvironment会默认创建StandardEnvironment对象。//并用这个Environment对象解析路径return getEnvironment().resolveRequiredPlaceholders(path);
}
写个示例就清楚咯 2.1. 示例
我的电脑中存在HOME这个环境变量 接下来修改我的配置文件名称: 修改完之后发现配置文件路径确定给解析到了。 了解这个功能即可。平时很少这么使用 ok解析完配置接下来就是最核心的方法了调用refresh容器刷新方法 3. refresh-容器刷新方法
这个方法是IOC的核心方法只要掌握这个方法中的每一个方法其实就基本掌握了Spring的IOC的整个流程。
后面将会分为很多章节去解释每个方法。
/*** 容器刷新方法是Spring最核心到方法。* 规定了容器刷新到流程比如prepareRefresh 前置刷新准备、* obtainFreshBeanFactory 创建beanfactory去解析配置文、加载beandefinition、* prepareBeanFactory 预设置beanfactory、* invokeBeanFactoryPostProcessors 执行beanfactoryPostProcessor* registerBeanPostProcessors 注册各种beanPostProcesser后置处理器* initMessageSource 国际化调用* initApplicationEventMulticaster 初始化事件多播器* onRefresh 刷新方法给其他子容器调用目前这个容器没干啥* registerListeners 注册时间监听器* finishBeanFactoryInitialization 初始化所有非懒加载的bean对象到容器中* finishRefresh 容器完成刷新 主要会发布一些事件** throws BeansException* throws IllegalStateException*/
Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh this.applicationStartup.start(spring.context.refresh);// Prepare this context for refreshing.//容器刷新的前置准备//设置启动时间激活状态为true关闭状态false//初始化environment//初始化监听器列表prepareRefresh();// Tell the subclass to refresh the internal bean factory.//创建beanFactory对象并且扫描配置文件加载beanDeifination注册到容器中ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// Prepare the bean factory for use in this context.//BeanFactory的预准备处理设置beanFactory的属性比如添加各种beanPostProcessor//设置environment为bean对象并添加到容器中后面可以直接autowrie注入这些对象prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.//子类去实现的回调方法当前容器没做什么工作是个空方法postProcessBeanFactory(beanFactory);StartupStep beanPostProcess this.applicationStartup.start(spring.context.beans.post-process);// Invoke factory processors registered as beans in the context.//加载并处理beanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.//注册BeanPostProcessor对象到容器中registerBeanPostProcessors(beanFactory);beanPostProcess.end();// Initialize message source for this context.//初始化消息源国际化使用initMessageSource();// Initialize event multicaster for this context.//初始化事件多播器对象并注册到容器中initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.//刷新又是spring为了扩展做的一个空实现让子类可以覆盖这个方法做增强功能onRefresh();// Check for listener beans and register them.//注册监听器到容器中如果容器中的earlyApplicationEvents列表中有事件列表//就会先发送这些事件。比如可以在前面的onRefresh方法中设置registerListeners();// Instantiate all remaining (non-lazy-init) singletons.//最最重要的方法根据之前加载好的beandefinition实例化bean到容器中//涉及到三级缓存、bean的生命周期、属性赋值等等finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.//完成刷新会发送事件。//检查earlyApplicationEvents事件列表中有没有新增的未发送的事件有就发送// 在执行applicationEventMulticaster事件列表中的所有事件finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn(Exception encountered during context initialization - cancelling refresh attempt: ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset active flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Springs core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}}
}