• Spring IOC源码:ApplicationContext刷新前准备工作


    Spring源码系列:

    Spring IOC源码:简单易懂的Spring IOC 思路介绍
    Spring IOC源码:核心流程介绍
    Spring IOC源码:ApplicationContext刷新前准备工作
    Spring IOC源码:obtainFreshBeanFactory 详解(上)
    Spring IOC源码:obtainFreshBeanFactory 详解(中)
    Spring IOC源码:obtainFreshBeanFactory 详解(下)
    Spring IOC源码:<context:component-scan>源码详解
    Spring IOC源码:invokeBeanFactoryPostProcessors 后置处理器详解
    Spring IOC源码:registerBeanPostProcessors 详解
    Spring IOC源码:实例化前的准备工作
    Spring IOC源码:finishBeanFactoryInitialization详解
    Spring IoC源码:getBean 详解
    Spring IoC源码:createBean( 上)
    Spring IoC源码:createBean( 中)
    Spring IoC源码:createBean( 下)
    Spring IoC源码:finishRefresh 完成刷新详解

    前言

    在进入spring正式核心逻辑处理前,Spring IOC需要提前进行些初始化工作,为后续的操作准备好一些环境。下面主要讲解这部分的内容,也是refresh()方法前的代码逻辑,即下面的super(parent),setConfigLocations(configLocations);本人以xml配置的方式,入口为ClassPathXmlApplicationContext

    	public ClassPathXmlApplicationContext(
    			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
    			throws BeansException {
    		//调用父类方法进行初始化
    		super(parent);
    		//对location文件路径进行解析、替换占位符等工作
    		setConfigLocations(configLocations);
    		if (refresh) {
    			refresh();
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    super(parent); 见方法1详解
    setConfigLocations(configLocations); 见方法4详解

    正文

    方法1:super(parent);

    在这里插入图片描述我们可以看到ClassPathXmlApplicationContext最上面的父类为AbstractApplicationContext,这里一直往上调用直到AbstractApplicationContext父类中进行初始化工作,

    	public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    		this();
    		setParent(parent);
    	}
    
    • 1
    • 2
    • 3
    • 4

    this(); 调用无参构造方法 见方法2
    setParent(parent); 设置此上下文的父类 见方法3

    方法2: AbstractApplicationContext:

    	public AbstractApplicationContext() {
    		this.resourcePatternResolver = getResourcePatternResolver();
    	}
    
    • 1
    • 2
    • 3
    	protected ResourcePatternResolver getResourcePatternResolver() {
    		//返回一个返回用于解析占位符的解析器
    		return new PathMatchingResourcePatternResolver(this);
    	}
    
    • 1
    • 2
    • 3
    • 4

    方法3: setParent

    		public void setParent(@Nullable ApplicationContext parent) {
    		//赋值传进来的父上下文到当前上下文属性中
    		this.parent = parent;
    		if (parent != null) {
    			//获取父容器系统环境属性
    			Environment parentEnvironment = parent.getEnvironment();
    			if (parentEnvironment instanceof ConfigurableEnvironment) {
    				//合并到当前上下文中
    				getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方法4:setConfigLocations

    	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;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    this.configLocations[i] = resolvePath(locations[i]).trim(); 见方法5

    方法5: resolvePath

    	protected String resolvePath(String path) {
    		//获取上下文中的系统环境属性,并调用其路径解析方法,如含有占位符:${path}
    		return getEnvironment().resolveRequiredPlaceholders(path);
    	}
    
    • 1
    • 2
    • 3
    • 4

    getEnvironment() 见方法6
    resolveRequiredPlaceholders(path) 见方法9

    方法6:getEnvironment

    	public ConfigurableEnvironment getEnvironment() {
    		if (this.environment == null) {
    			//创建系统环境属性
    			this.environment = createEnvironment();
    		}
    		return this.environment;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    createEnvironment(); 见方法7

    方法7:createEnvironment

    	protected ConfigurableEnvironment createEnvironment() {
    		//创建一个标准的环境属性
    		return new StandardEnvironment();
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    new StandardEnvironment(); 见方法8

    方法8:StandardEnvironment

    在这里插入图片描述
    进入这个类之后,我们看到它并没有构造方法,而且也没有resolveRequiredPlaceholders()方法,不过我们可以看到它继承了AbstractEnvironment方法,所以实例化时,肯定是调用父类的构造器。

    方法9 resolveRequiredPlaceholders

    resolveRequiredPlaceholders方法其实调用的是父类AbstractEnvironment中的方法,其类图关系如下:
    在这里插入图片描述

    	@Override
    	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    		return this.propertyResolver.resolveRequiredPlaceholders(text);
    	}
    
    • 1
    • 2
    • 3
    • 4

    resolveRequiredPlaceholders(text) 见方法10

    方法10 resolveRequiredPlaceholders

    	@Override
    	public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    		if (this.strictHelper == null) {
    			//创建占位符解析器
    			this.strictHelper = createPlaceholderHelper(false);
    		}
    		//解析并返回处理后的配置文件路径
    		return doResolvePlaceholders(text, this.strictHelper);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    doResolvePlaceholders(text, this.strictHelper) 见方法11

    方法11 doResolvePlaceholders

    	public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
    		Assert.notNull(value, "'value' must not be null");
    		return parseStringValue(value, placeholderResolver, null);
    	}
    
    • 1
    • 2
    • 3
    • 4
    	protected String parseStringValue(
    			String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
    		//这里的this.placeholderPrefix 默认值为${
    		int startIndex = value.indexOf(this.placeholderPrefix);
    		//判断下传进来的路径是否有${开头的占位符
    		if (startIndex == -1) {
    			return value;
    		}
    
    		StringBuilder result = new StringBuilder(value);
    		while (startIndex != -1) {
    			//查询一下是否有结束占位符下标
    			int endIndex = findPlaceholderEndIndex(result, startIndex);
    			if (endIndex != -1) {
    				//获取占位符内容
    				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
    				String originalPlaceholder = placeholder;
    				if (visitedPlaceholders == null) {
    					visitedPlaceholders = new HashSet<>(4);
    				}
    				if (!visitedPlaceholders.add(originalPlaceholder)) {
    					throw new IllegalArgumentException(
    							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
    				}
    				// 递归调用,看占位符内容是否还有包含占位符 ,比如${${user}}
    				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
    				// 通过占位符内容名称去查询出,获取具体值,如user从environment中查出当前电脑名称
    				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
    				if (propVal == null && this.valueSeparator != null) {
    					//如果查不到可能是以键值对的形式 如 name:zhangsan
    					//这时候我们要解析出KEY跟VALUE
    					//获取:符号的下标
    					int separatorIndex = placeholder.indexOf(this.valueSeparator);
    					if (separatorIndex != -1) {
    						//获取出key的值,如name
    						String actualPlaceholder = placeholder.substring(0, separatorIndex);
    						//获取出value的值,如zhangsan
    						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
    						//通过key值名称去查询,如果查不到则以actualPlaceholder为默认值进行赋值
    						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
    						if (propVal == null) {
    							propVal = defaultValue;
    						}
    					}
    				}
    				if (propVal != null) {
    					//递归调用,解析包含在以前解析的占位符值中的占位符。
    					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
    					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
    					if (logger.isTraceEnabled()) {
    						logger.trace("Resolved placeholder '" + placeholder + "'");
    					}
    					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
    				}
    				else if (this.ignoreUnresolvablePlaceholders) {
    					// Proceed with unprocessed value.
    					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
    				}
    				else {
    					throw new IllegalArgumentException("Could not resolve placeholder '" +
    							placeholder + "'" + " in value \"" + value + "\"");
    				}
    				visitedPlaceholders.remove(originalPlaceholder);
    			}
    			else {
    				startIndex = -1;
    			}
    		}
    		return result.toString();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    至此占位符解析结束,返回解析后的值赋值给上下文的configLocations数组;

    总结

    refresh方法调用前的解析工作大概就是这样,这里只是Spring IOC的流程,如果是Springboot的话,会有一些方法重载,代码会更复杂。刷新前的准备工作,主要就是创建StandEnvironment属性类,解析占位符等操作;后面的文章再来写下refresh中的核心方法,文章会慢慢更新的,文章可能会有些理解不到位的地方,大家有更好的见解可以交流学习!

  • 相关阅读:
    自动驾驶系统中摄像头相对地面的在线标定
    强化学习环境报错解决
    springboot+vue实现登录案例(附VUE整个项目代码)
    oracle学习42-增加表空间
    MySQL安全配置规范
    selenium WebDriver 中的几种等待--sleep(),implicitly_wait(),WebDriverWait()
    H - Hot Black Hot White(数论/取模运算)
    211 毕业就入职 30 人的小公司是什么体验
    终端安全能力验证环境搭建和渗透测试记录
    【赠书活动】如何提高C++代码的性能
  • 原文地址:https://blog.csdn.net/weixin_45031612/article/details/127678457