Spring框架5 - 容器的扩展功能 (ApplicationContext)

news2024/11/16 3:30:56
private static ApplicationContext applicationContext;

static {
	applicationContext = new ClassPathXmlApplicationContext("bean.xml");
}

BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanFactory的功能相似,都是用于向IOC中加载Bean的。由于ApplicationConext的功能是多于BeanFactory的,所以在日常使用中,建议直接使用ApplicationConext即可。在 ApplicationConext的构造方法中,主要做了两个事情:

① 设置配置的加载路径;

② 执行ApplicationConext中,所有功能的初始化操作;

/**
 * 创建一个新的ClassPathXmlApplicationContext,从给定的XML文件中加载定义。
 */
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
	this(configLocations, refresh, null);
}

/**
 * 使用给定的父类创建一个新的ClassPathXmlApplicationContext,从给定的XML文件中加载定义。
 */
// eg1:configLocations=["bean.xml"],refresh=true,parent=null
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
	super(parent);

	/** 1:设置配置路径 */
	setConfigLocations(configLocations); // eg1:configLocations=["bean.xml"]

	/** 2:执行初始化操作 */
	if (refresh) // eg1:true
		refresh();
}

setConfigLocations 设置配置加载路径

该方法逻辑不多,主要就是为应用上下文AbstractRefreshableConfigApplicationContext 类设置配置路径(String[] configLocations),源码如下所示:

/**
 * 为应用上下文(application context)设置配置路径(config locations)
 */
// eg1:locations=["bean.xml"]
public void setConfigLocations(@Nullable String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		this.configLocations = new String[locations.length];

		/** 如果路径中包含特殊符号(如:${var}),那么在resolvePath方法中会搜寻匹配的系统变量并且进行替换操作 */
		for (int i = 0; i < locations.length; i++)
			this.configLocations[i] = resolvePath(locations[i]).trim(); // eg1:configLocations=["bean.xml"]  // xml配置文件所在路径
	}
	else
		this.configLocations = null;
}
/**
 * 解析给定的路径,必要时用相应的环境属性值替换占位符。应用于配置位置。
 */
// eg1:path="bean.xml"
protected String resolvePath(String path) {
	return getEnvironment().resolveRequiredPlaceholders(path);
}
// eg1:text="bean.xml"
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	// eg1:propertyResolver=AbstractPropertyResolver
	return this.propertyResolver.resolveRequiredPlaceholders(text);
}
// eg1:text="bean.xml"
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	if (this.strictHelper == null) // eg1:strictHelper=null
		this.strictHelper = createPlaceholderHelper(false);
	return doResolvePlaceholders(text, this.strictHelper);
}
/**
 * 创建PropertyPlaceholderHelper实例对象
 */
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
	/** placeholderPrefix = "${", placeholderSuffix = "}", valueSeparator = ":" */
	return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
			this.valueSeparator, ignoreUnresolvablePlaceholders);
}

// eg1:text="bean.xml", helper=PropertyPlaceholderHelper
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
	return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
/**
 * 将格式${name}中的所有占位符替换为提供的PlaceholderResolver的返回值。
 */
// eg1:value="bean.xml"
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
	Assert.notNull(value, "'value' must not be null");
	return parseStringValue(value, placeholderResolver, null);
}

// eg1:value="bean.xml",visitedPlaceholders=null
protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
	int startIndex = value.indexOf(this.placeholderPrefix);
	// eg1:startIndex=-1
	if (startIndex == -1) return value; // eg1:return "bean.xml"

	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");
			}
			// Recursive invocation, parsing placeholders contained in the placeholder key.
			placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
			// Now obtain the value for the fully resolved key...
			String propVal = placeholderResolver.resolvePlaceholder(placeholder);
			if (propVal == null && this.valueSeparator != null) {
				int separatorIndex = placeholder.indexOf(this.valueSeparator);
				if (separatorIndex != -1) {
					String actualPlaceholder = placeholder.substring(0, separatorIndex);
					String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
					propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
					if (propVal == null) {
						propVal = defaultValue;
					}
				}
			}
			if (propVal != null) {
				// Recursive invocation, parsing placeholders contained in the
				// previously resolved placeholder value.
				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();
}

refresh 初始化工作

在refresh()方法中几乎包含了ApplicationContext中提供的全部功能,下面我们会针对这 个方法进行详细的分析:

/**
 * 返回静态指定的ApplicationListeners的列表
 */
public Collection<ApplicationListener<?>> getApplicationListeners() {
	return this.applicationListeners;
}

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
		/** 1:为refresh操作做提前的准备工作 */
		prepareRefresh();

		/** 2:获得beanFactory实例对象 */
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		/** 3:准备用于此上下文的beanFactory */
		prepareBeanFactory(beanFactory);
		try {
			postProcessBeanFactory(beanFactory); // 允许在上下文子类中对BeanFactory进行后置处理(空方法,可由子类实现)
			StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");

			/** 4:激活各种BeanFactoryPostProcessor的后置处理器 */
			invokeBeanFactoryPostProcessors(beanFactory);

			/** 5:注册各种Bean的后置处理器,在getBean时才会被调用 */
			registerBeanPostProcessors(beanFactory);
			beanPostProcess.end();

			/** 6:为上下文初始化消息源(即:国际化处理)*/
			initMessageSource();

			/** 7:为上下文初始化应用事件广播器 */
			initApplicationEventMulticaster();
			onRefresh(); // 初始化特定上下文子类中的其他特殊bean(空方法,可由子类实现)

			/** 8:在所有注册的bean中查找listener bean,并注册到消息广播器中 */
			registerListeners();

			/** 9:初始化剩下的单例(非惰性non-lazy-init)*/
			finishBeanFactoryInitialization(beanFactory);

			/** 10:完成refresh,通知lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知 */
			finishRefresh();
		} catch (BeansException ex) {……
		} finally {……
		}
	}
}

 /** 1:为refresh操作做提前的准备工作 */ 

prepareRefresh主要是为了容器进行刷新做准备,它实现的步骤如下:

① 设置容器的启动时间。

② 设置活跃状态为true。

③ 设置关闭状态为false

④ 获取Environment对象,并加载当前系统的属性值到Environment对象中。

⑤ 准备监听器和事件的集合对象,默认为空的集合。

/**
 * 为刷新准备这个上下文,设置它的启动日期和活动标志,以及执行任何属性源的初始化。
 */
protected void prepareRefresh() {
	this.startupDate = System.currentTimeMillis(); // 设置容器启动的时间
	this.closed.set(false); // 容器的关闭标志位
	this.active.set(true); // 容器的激活标志位
	initPropertySources(); // 留给子类覆盖,初始化属性资源(空方法)

	/** 创建并获取环境对象,验证需要的属性文件是否都已经放入环境中 */
	getEnvironment().validateRequiredProperties();

	// 如果为空,则以applicationListeners为准
	if (this.earlyApplicationListeners == null) // eg1:earlyApplicationListeners=null
		this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners); // eg1:applicationListeners.size()=0
	// 如果不等于空,则清空applicationListeners,以earlyApplicationListeners为准
	else {
		this.applicationListeners.clear();
		this.applicationListeners.addAll(this.earlyApplicationListeners);
	}
	// 创建刷新前的监听事件集合
	this.earlyApplicationEvents = new LinkedHashSet<>();
}

 validateRequiredProperties():

@Override
public void validateRequiredProperties() {
	MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
	for (String key : this.requiredProperties) { // eg1: requiredProperties.size()=0
		if (this.getProperty(key) == null)
			ex.addMissingRequiredProperty(key);
	}
	if (!ex.getMissingRequiredProperties().isEmpty()) // eg1:ex.getMissingRequiredProperties().size()=0
		throw ex;
}

/** 2:obtainFreshBeanFactory 获得beanFactory实例对象 */

通过obtainFreshBeanFactory()这个方法,ApplicationContext就已经拥有了BeanFactory 的全部功能。而这个方法中也包含了前面我们介绍BeanFactory时候对于xml配置的加载 过程。

/**
 * 告诉子类刷新内部bean工厂。
 */
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	/** 1:初始化BeanFactory,并加载bean的配置文件 */
	refreshBeanFactory(); // eg1:AbstractRefreshableApplicationContext#refreshBeanFactory()

	/** 2:获得BeanFactory */
	return getBeanFactory(); // eg1:AbstractRefreshableApplicationContext#getBeanFactory()
}

obtainFreshBeanFactory() 的 1:初始化BeanFactory,并加载bean的配置文件

/**
 * 此实现执行此上下文的底层bean工厂的实际刷新,关闭前一个bean工厂(如果有的话)并为上下文生命周期的下一阶段初始化一个新的bean工厂。
 */
@Override
protected final void refreshBeanFactory() throws BeansException {
	/** 1: 如果存在BeanFactory,则执行清理操作(即:删除Bean及关闭BeanFactory) */
	if (hasBeanFactory()) { // eg1:false
		destroyBeans();
		closeBeanFactory();
	}
	try {
		/** 2:创建DefaultListableBeanFactory实例对象 */
		DefaultListableBeanFactory beanFactory = createBeanFactory();

		// eg1:(getId()="org.springframework.context.support.ClassPathXmlApplicationContext@2bec854f"
		beanFactory.setSerializationId(getId());

		/** 3:定制此上下文使用的内部bean工厂,向BeanFactory设置setAllowBeanDefinitionOverriding和setAllowCircularReferences */
		customizeBeanFactory(beanFactory);

		/** 4:加载BeanDefinition */
		loadBeanDefinitions(beanFactory); // eg1:AbstractXmlApplicationContext#loadBeanDefinitions(beanFactory)

		this.beanFactory = beanFactory;
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

1: 如果存在BeanFactory,则执行清理操作(即:删除Bean及关闭BeanFactory) 

/**
 * 确定此上下文当前是否持有bean工厂,即至少被刷新过一次且尚未关闭。
 */
protected final boolean hasBeanFactory() {
	return (this.beanFactory != null);  // 开始的时候beanFactory为空
}

2:创建DefaultListableBeanFactory实例对象:new一个

/**
 * 为此上下文创建一个内部bean工厂。每次尝试refresh()时都会调用。
 * 默认实现会创建一个DefaultListableBeanFactory,其中,getInternalParentBeanFactory()是这个上下文的父bean工厂。
 * 可以在子类中重写,例如自定义DefaultListableBeanFactory的设置。
 */
protected DefaultListableBeanFactory createBeanFactory() {
	return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

3:定制此上下文使用的内部bean工厂,向BeanFactory设置setAllowBeanDefinitionOverriding和setAllowCircularReferences

/**
 * 定制此上下文使用的内部bean工厂,向BeanFactory设置setAllowBeanDefinitionOverriding和setAllowCircularReferences
 */
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
	if (this.allowBeanDefinitionOverriding != null) // eg1:allowBeanDefinitionOverriding=null
		beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);

	if (this.allowCircularReferences != null) // eg2:allowCircularReferences=null
		beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}

4:加载BeanDefinition

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

	// 对beanDefinitionReader设置setValidating
	initBeanDefinitionReader(beanDefinitionReader);

	// 加载xml配置信息
	loadBeanDefinitions(beanDefinitionReader);
}

    // 对beanDefinitionReader设置setValidating

protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
	// eg1:validating=true
	reader.setValidating(this.validating);
}

    // 加载xml配置信息

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
	// eg1:ClassPathXmlApplicationContext#getConfigResources()
	Resource[] configResources = getConfigResources();
	if (configResources != null) // eg1: configResources=null
		reader.loadBeanDefinitions(configResources);

	String[] configLocations = getConfigLocations();
	if (configLocations != null) // eg1: configLocations=["bean.xml"]
		reader.loadBeanDefinitions(configLocations);
}
// eg1: locations=["bean.xml"]
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
	Assert.notNull(locations, "Location array must not be null");
	int count = 0;
	for (String location : locations)
		count += loadBeanDefinitions(location);
	return count;
}
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(location, null);
}

/**
 * 从指定的资源位置(resource location)加载bean定义。
 * 位置(location)也可以是位置模式(location pattern),前提是这个bean definition reader的ResourceLoader是一个ResourcePatternResolver。
 */
// eg1:location="bean.xml" actualResources=null
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
	ResourceLoader resourceLoader = getResourceLoader();
	// eg1:resourceLoader=ClassPathXmlApplicationContext@2bec854f
	if (resourceLoader == null)
		throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");

	if (resourceLoader instanceof ResourcePatternResolver) {
		try {
			// eg1:location="bean.xml",AbstractApplicationContext#getResources(location)
			Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

			// eg1:resources=[ClassPathContextResource]
			int count = loadBeanDefinitions(resources);

			if (actualResources != null) // eg1:actualResources=null
				Collections.addAll(actualResources, resources);
			return count;
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);
		}
	}
	else {
		Resource resource = resourceLoader.getResource(location);
		int count = loadBeanDefinitions(resource);
		if (actualResources != null) actualResources.add(resource);
		return count;
	}
}
// eg1:resources=[ClassPathContextResource]
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
	Assert.notNull(resources, "Resource array must not be null");
	int count = 0;
	for (Resource resource : resources) {
		// 加载配置
		count += loadBeanDefinitions(resource); // eg1: XmlBeanDefinitionReader#loadBeanDefinitions(resource)
	}
	return count;
}
/**
 * 从指定的XML文件中加载bean定义。
 */
// eg1:resource=ClassPathContextResource
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}

代码走到这一步,就跟xmlBeanFactory方法重合了。

obtainFreshBeanFactory() 的 2:获得BeanFactory

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
	DefaultListableBeanFactory beanFactory = this.beanFactory;
	if (beanFactory == null) // eg1:beanFactory=DefaultListableBeanFactory@6e01f9b0
		throw new IllegalStateException("BeanFactory not initialized or already closed - " +
				"call 'refresh' before accessing beans via the ApplicationContext");
	return beanFactory;
}

/** 3:prepareBeanFactory 准备用于此上下文的beanFactory */

/**
 * 配置工厂的标准上下文特征,例如上下文的类加载器(context's ClassLoader)和后处理器(post-processors)
 */
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	/** 1: 设置beanFactory的classLoader */
	beanFactory.setBeanClassLoader(getClassLoader()); // eg1:DefaultResourceLoader#getClassLoader()

	/** 2: 是否支持SpEL表达式解析,即:可以使用#{bean.xxx}的形式来调用相关属性值 */
	if (!shouldIgnoreSpel) // eg1:shouldIgnoreSpel=false
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));

	/** 3: 添加1个属性编辑器,它是对bean的属性等设置管理的工具 */
	beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

	/** 4: 添加1个bean的后置处理器 */
	beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

	/** 5: 设置7个需要忽略自动装配的接口,维护到ignoredDependencyInterfaces中 */
	beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
	beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
	beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
	beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
	beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
	beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
	beanFactory.ignoreDependencyInterface(ApplicationStartupAware.class);

	/** 6: 注册4个依赖,向resolvableDependencies中注册类及实现类*/
	beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
	beanFactory.registerResolvableDependency(ResourceLoader.class, this);
	beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
	beanFactory.registerResolvableDependency(ApplicationContext.class, this);

	/** 7: 添加1个bean的后置处理器 */
	beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

	/** 8: 增加对AspectJ的支持 */
	// eg1: NativeDetector.inNativeImage()=false, beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)=false
	if (!NativeDetector.inNativeImage() && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
		beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
		beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
	}

	/** 9: 注册4个默认的系统环境bean */
	if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) // eg1: 注册"environment"名称的bean
		beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
	if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) // eg1: 注册"systemProperties"名称的bean
		beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
	if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) // eg1: 注册"systemEnvironment"名称的bean
		beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
	if (!beanFactory.containsLocalBean(APPLICATION_STARTUP_BEAN_NAME)) // eg1: 注册"applicationStartup"名称的bean
		beanFactory.registerSingleton(APPLICATION_STARTUP_BEAN_NAME, getApplicationStartup());
}

prepareBeanFactory方法的主要作用是配置工厂的标准上下文特征,如上下文的类加载 器和后处理器。操作步骤如下所述:

① 设置beanFactory的classLoader

/**
 * 返回用来加载类路径资源的类加载器(ClassLoader)。
 * 将被传递给这个资源加载器创建的所有ClassPathResource对象的ClassPathResource构造函数。
 */
@Override
@Nullable
public ClassLoader getClassLoader() {
	// eg1:classLoader=null
	return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
/**
 * 返回要使用的默认类加载器:通常是线程上下文的类加载器(如果有的话);加载ClassUtils类的类加载器将用作备用。
 * 如果你想使用线程上下文的类加载器,而你显然更喜欢非空的类加载器引用,那么可以调用这个方法:例如,用于类路径资源加载(但不一定用于类。
 * forName也接受一个null类加载器引用)。
 */
@Nullable
public static ClassLoader getDefaultClassLoader() {
	ClassLoader cl = null;
	try {
		cl = Thread.currentThread().getContextClassLoader(); // 1:获得当前线程上下文的ClassLoader
	} catch (Throwable ex) {}

	if (cl == null) {
		cl = ClassUtils.class.getClassLoader(); // 2:获得ClassUtils类的ClassLoader
		if (cl == null) {
			try {
				cl = ClassLoader.getSystemClassLoader(); // 3:获得系统的类加载器
			} catch (Throwable ex) {}
		}
	}
	return cl;
}

(上面代码块:SPI破话双亲委派机制)

② 是否支持SpEL表达式解析,即:可以使用#{bean.xxx}的形式来调用相关属性值

SpEL全称是“Spring Expression Language”,它能在运行时构件复杂表达式、存取对象图 属性、对象方法调用等;它是单独模块,只依赖了core模块,所以可以单独使用。SpEL 使用 #{...} 作为界定符,所有在大括号中的字符都会被认定为SpEL,使用方式如下所示:

在prepareBeanFactory(beanFactory)方法中,通过beanFactory类的 setBeanExpressionResolver(new StandardBeanExpressionResolver(...))方法注册SpEL语言 解析器,就可以对SpEL进行解析了。

那么,在注册了解析器后,Spring又是在什么时候调用这个解析器进行解析操作的呢?调 用方式如下图所示:

其实就是在Spring进行bean初始化的时候,有一个步骤是——属性填充(即:populateBean()), 而在这一步中Spring会调用AbstractAutowireCapableBeanFactory#applyPropertyValues(...)方 法来完成功能。而就在这个方法中,会创建BeanDefinitionValueResolver实例对象valueResolver 来进行属性值的解析操作。同时,也是在这个步骤中,通过 AbstractBeanFactory#evaluateBeanDefinitionString(...)方法去完成SpEL的解析操作。

(视频6-5:1h50min)

③ 添加1个属性编辑器ResourceEditorRegistrar,对bean的属性等设置管理的工具

什么是属性编辑器呢?在Spring加载bean的时候,可以把基本类型属性注入进来,但是 对于类似Date这种复杂属性就无法被识别了,不过可以通过属性编辑器解决。

因为date 属性是Date类型的,但是给配置的属性值是字符串类型的,所以需要进行一下转换。

以上例子:

@Data
public class Schedule {
    private String name;
    private Date date; // 添加Date类型的属性
}
public class DatePropertyEditor implements PropertyEditorRegistrar {
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }
}
<!-- 属性编辑器演示 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="propertyEditorRegistrars">
		<list>
			<bean class="com.muse.springdemo.propertyeditor.DatePropertyEditor"/>
		</list>
	</property>
</bean>
<bean id="schedule" class="com.muse.springdemo.propertyeditor.Schedule">
	<property name="name" value="work"/>
	<property name="date" value="2023-01-01"/> <!-- date是Date类型,需要将String转换为Date -->
</bean>
@Test
void testPropertyEditor() {
	Schedule schedule = applicationContext.getBean("schedule", Schedule.class);
	log.info("schedule={}", schedule);
}

运行结果:

属性编辑器源码解析:

通过上面对于注册属性编辑器的配置,我们可以看到自定义的属性编辑器 DatePropertyEditor被保存到了CustomEditorConfigurer的propertyEditorRegistrars属性 中,那么由于CustomEditorConfigurer类实现了BeanFactoryPostProcessor接口,所以当 bean初始化的时候,会调用它的postProcessBeanFactory(...)方法,那么在这个方法中, 会将propertyEditorRegistrars属性中的所有属性编辑器添加到beanFactory中。

分析到这一步之后,发现自定义属性编辑器都会保存到beanFactory的变量 propertyEditorRegistrars中。那么问题来了——保存我们知道了,那什么时候属性编辑 器会被调用呢?其实就是在初始化bean的时候,在initBeanWrapper(...)方法中,会被调用。

④ 添加1个bean的后置处理器ApplicationContextAwareProcessor

⑤ 设置7个需要忽略自动装配的接口,维护到ignoredDependencyInterfaces中

⑥ 注册4个依赖,向resolvableDependencies中注册类及实现类

⑦ 添加1个bean的后置处理器ApplicationListenerDetector

⑧ 增加对AspectJ的支持 ⑨ 注册4个默认的系统环境bean

/** 4:激活各种BeanFactoryPostProcessor的后置处理器 */
            invokeBeanFactoryPostProcessors(beanFactory);

            /** 5:注册各种Bean的后置处理器,在getBean时才会被调用 */
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            /** 6:为上下文初始化消息源(即:国际化处理)*/
            initMessageSource();

            /** 7:为上下文初始化应用事件广播器 */
            initApplicationEventMulticaster();
            onRefresh(); // 初始化特定上下文子类中的其他特殊bean(空方法,可由子类实现)

            /** 8:在所有注册的bean中查找listener bean,并注册到消息广播器中 */
            registerListeners();

            /** 9:初始化剩下的单例(非惰性non-lazy-init)*/
            finishBeanFactoryInitialization(beanFactory);

            /** 10:完成refresh,通知lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知 */
            finishRefresh();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2117150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024下学期学习总结加今日学习总结

Vue router Vue Router 是一个为 Vue.js 应用程序设计的官方路由管理器。它使你能够轻松地在 Vue 应用中实现页面导航&#xff0c;处理 URL 和视图的映射。 安装router 在运行框内输入 npm install vue-router4 //vue2专用 npm install vue-router3 //vue3专用 对router进…

uniapp数据缓存和发起网络请求

数据缓存 uni.onStorageSync同步的方式将数据存储到本地缓存 <template><button click"onStorageSync()">存储数据</button> </template><script setup>const onStorageSync () > {// 存储数据uni.setStorageSync(username, 张三)…

【验收交付体系文档】系统验收计划书,软件交付验收成套文档体系

软件系统验收计划书是确保新开发的软件系统符合预期要求并稳定运行的关键步骤。本计划书概述了验收过程的主要环节&#xff0c;包括系统功能的详细测试、性能评估、用户接受度测试以及文档完整性的核查。验收团队将依据项目需求规格说明书和合同要求&#xff0c;对系统进行全面…

axure判断

在auxre中我们也可以实现判断的功能&#xff0c;当目标等于什么内容时则执行下方的功能。 一、判断输入框中是否有值 画布添加一个输入框、一个文本标签删除其中内容&#xff0c;添加一个按钮&#xff0c;输入框命名为【文本显示】文本标签命名为【提示】 给按钮新增一个交互…

多个索引干扰导致索引失效如何解决

视频讲解&#xff1a;索引干扰导致索引失效如何解决_哔哩哔哩_bilibili 1 场景说明 表tb_order有订单状态order_status和创建时间create_time的索引。 现在业务的需求是&#xff0c;查询半年内&#xff0c;已支付订单状态的总数。SQL语句如下&#xff1a; SELECTCOUNT(1) FRO…

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元&#xff0c;传统键盘输入方式正悄然进化。以往&#xff0c;面对实体键盘&#xff0c;我们常需目光游离于屏幕与键盘之间&#xff0c;以确认指尖下的精准位置。而屏幕键盘虽直观可见&#xff0c;却常因占据屏幕空间&#xff0c;迫使我们在操作与视野间做出…

Windows系统下安装JMeter

目录 一、官网下载JMeter 二、运行 JMeter 一、官网下载JMeter JMeter 官网安装地址 Apache JMeter - Apache JMeter™https://jmeter.apache.org/ 下载Windows版本 下载完成后 解压 二、运行 JMeter 打开bin目录 下面两个文件其中一个均可运行双击jmeter.bat 或者使用…

支持黑神话悟空的超长视频理解,Qwen2-VL多模态大模型分享

Qwen2-VL是由阿里巴巴达摩院开发并开源的第二代视觉与语言多模态人工智能模型。 Qwen2-VL结合了视觉理解和自然语言处理的能力&#xff0c;使得它能够处理和理解图像、视频以及文本数据。 Qwen2-VL支持多种语言&#xff0c;包括但不限于英语、中文、大多数欧洲语言、日语、韩…

【分支-快速排序】

【分支-快速排序】 1. 颜色分类1.1 题目来源1.2 题目描述1.3 题目解析 2. 排序数组2.1 题目来源2.2 题目描述2.3 题目解析 3. 数组中的第K个最大元素3.1 题目来源3.2 题目描述3.3 题目解析 4. 库存管理 III4.1 题目来源4.2 题目描述4 .3 题目解析 1. 颜色分类 1.1 题目来源 7…

JS基础学习笔记

1.引入方式 内部脚本 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…

为什么要使用大模型RAG一体机

使用大模型RAG&#xff08;Retrieval-Augmented Generation&#xff09;一体机&#xff0c;如AntSKPro AI 离线知识库一体机&#xff0c;有以下几个原因和优势&#xff1a; 提高效率&#xff1a;RAG模型结合了检索&#xff08;Retrieval&#xff09;和生成&#xff08;Generati…

鸿蒙(API 12 Beta6版)GPU加速引擎服务【自适应VRS】

XEngine Kit提供自适应VRS功能&#xff0c;其通过合理分配画面的计算资源&#xff0c;视觉无损降低渲染频次&#xff0c;使不同的渲染图像使用不同的渲染速率&#xff0c;能够有效提高渲染性能。 接口说明 以下接口为自适应VRS设置接口&#xff0c;如要使用更丰富的设置和查询…

windows10-VMware17-Ubuntu-22.04-海康2K摄像头兼容问题,求解(已解决)

文章目录 1.webrtc camera测试2.ffmpeg 测试3.Ubuntu 自带相机4.解决办法 环境&#xff1a;windows10系统下&#xff0c;VMware的Ubuntu-22.04系统 问题&#xff1a;摄像头出现兼容问题&#xff0c;本来是想开发测试的&#xff0c;Ubuntu方便些。买了海康2K的USB摄像头&#xf…

人机交互与现代战争

人机交互技术在现代战争中的应用越来越广泛&#xff0c;它可以帮助士兵更好地完成任务&#xff0c;提高作战效能&#xff0c;减少人员伤亡。人机交互与认知在军事应用方面的进展有很多&#xff0c;比如&#xff1a; &#xff08;1&#xff09;虚拟现实和增强现实技术&#xff1…

PAT甲级-1085 Perfect Sequence

题目 题目大意 在一组数中找到一个完美数列&#xff0c;满足M < mp&#xff0c;M是该数列的最大值&#xff0c;m是最小值&#xff0c;p是题目给定的一个常数。 思路 模拟或者二分法。二分法可用upper_bound()函数实现。 知识点 upper_bound() 和 lower_bound() 函数在&…

C高级编程 第十六天(树 二叉树)

1.树 1.1结构特点 非线性结构&#xff0c;有一个直接前驱&#xff0c;但可能有多个直接后继有递归性&#xff0c;树中还有树可以为空&#xff0c;即节点个数为零 1.2相关术语 根&#xff1a;即根结点&#xff0c;没有前驱叶子&#xff1a;即终端结点&#xff0c;没有后继森…

02-java实习工作一个多月-经历分享

一、描述一下最近不写博客的原因 离我发java实习的工作的第一天的博客已经过去了一个多月了&#xff0c;本来还没入职的情况是打算每天工作都要写一份博客来记录一下的&#xff08;最坏的情况也是每周至少总结一下的&#xff09;&#xff0c;其实这个第一天的博客都是在公司快…

笔记整理—内核!启动!—kernel部分(2)从汇编阶段到start_kernel

kernel起始与ENTRY(stext)&#xff0c;和uboot一样&#xff0c;都是从汇编阶段开始的&#xff0c;因为对于kernel而言&#xff0c;还没进行栈的维护&#xff0c;所以无法使用c语言。_HEAD定义了后面代码属于段名为.head .text的段。 内核起始部分代码被解压代码调用&#xff0c…

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-qK…

Java 入门指南:JVM(Java虚拟机)—— Java 内存运行时的数据区域

前言 对于 Java 程序员来说&#xff0c;在虚拟机自动内存管理机制下&#xff0c;不再需要像 C/C程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作&#xff0c;不容易出现内存泄漏和内存溢出问题。 由于程序员把内存控制权利交给 Java 虚拟机&#xff0c;一旦…