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

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:truerefresh();
}

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配置文件所在路径}elsethis.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=AbstractPropertyResolverreturn this.propertyResolver.resolveRequiredPlaceholders(text);
}
// eg1:text="bean.xml"
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {if (this.strictHelper == null) // eg1:strictHelper=nullthis.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=-1if (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=nullthis.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()=0if (this.getProperty(key) == null)ex.addMissingRequiredProperty(key);}if (!ex.getMissingRequiredProperties().isEmpty()) // eg1:ex.getMissingRequiredProperties().size()=0throw 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:falsedestroyBeans();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=nullbeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);if (this.allowCircularReferences != null) // eg2:allowCircularReferences=nullbeanFactory.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设置setValidatinginitBeanDefinitionReader(beanDefinitionReader);// 加载xml配置信息loadBeanDefinitions(beanDefinitionReader);
}

    // 对beanDefinitionReader设置setValidating

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

    // 加载xml配置信息

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {// eg1:ClassPathXmlApplicationContext#getConfigResources()Resource[] configResources = getConfigResources();if (configResources != null) // eg1: configResources=nullreader.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@2bec854fif (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=nullCollections.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@6e01f9b0throw 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=falsebeanFactory.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)=falseif (!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"名称的beanbeanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) // eg1: 注册"systemProperties"名称的beanbeanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) // eg1: 注册"systemEnvironment"名称的beanbeanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());if (!beanFactory.containsLocalBean(APPLICATION_STARTUP_BEAN_NAME)) // eg1: 注册"applicationStartup"名称的beanbeanFactory.registerSingleton(APPLICATION_STARTUP_BEAN_NAME, getApplicationStartup());
}

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

① 设置beanFactory的classLoader

/*** 返回用来加载类路径资源的类加载器(ClassLoader)。* 将被传递给这个资源加载器创建的所有ClassPathResource对象的ClassPathResource构造函数。*/
@Override
@Nullable
public ClassLoader getClassLoader() {// eg1:classLoader=nullreturn (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类的ClassLoaderif (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 {@Overridepublic 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.mzph.cn/diannao/53462.shtml

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

相关文章

linux 安装redis

1. 更新系统和安装依赖 sudo apt update sudo apt install build-essential tcl2. 下载 Redis 源码(没有opt文件夹&#xff0c;则先创建opt文件夹) cd /opt wget http://download.redis.io/releases/redis-6.2.6.tar.gz3. 解压和编译 Redis 解压下载的文件&#xff0c;并进入…

Qt/C++编写的Onvif调试助手调试神器工具/支持云台控制/预置位设置等/有手机版本

一、功能特点 广播搜索设备&#xff0c;支持IPC和NVR&#xff0c;依次返回。可选择不同的网卡IP进行对应网段设备的搜索。依次获取Onvif地址、Media地址、Profile文件、Rtsp地址。可对指定的Profile获取视频流Rtsp地址&#xff0c;比如主码流地址、子码流地址。可对每个设备设…

深入了解以太坊

1. 以太坊编程语言和操作码 以太坊中智能合约的代码以高级语言编写&#xff0c;如 Serpent、LLL、Solidity 或 Viper,并可转换为 EVM 可以理解的字节码&#xff0c;以便执行。 Solidity 是为以太坊开发的高级语言之一&#xff0c;它具有类似 JavaScript 的语法&#xff0c;可以…

用广播星历计算卫星运动的平均角速度

用广播星历计算卫星位置 1.计算卫星运动的平均角速度 首先根据广播星历中给出的参数计算参考时刻的平均角速度: 式中&#xff0c;GM为万有引力常数G与地球总质量M之乘积&#xff0c;其值为GM3.98600510^14b m3/s2。 然后根据广播星历中给定的摄动参数计算观测时刻卫星的平均…

使用AI写WebSocket知识是一种怎么样的体验?

一、WebSocket基础知识 1. WebSocket概念 1.1 为什么会出现WebSocket 一般的Http请求我们只有主动去请求接口&#xff0c;才能获取到服务器的数据。例如前后端分离的开发场景&#xff0c;自嘲为切图仔的前端大佬找你要一个配置信息的接口&#xff0c;我们后端开发三下两下开…

JDBC:连接数据库

文章目录 报错 报错 Exception in thread “main” java.sql.SQLException: Can not issue SELECT via executeUpdate(). 最后这里输出的还是地址&#xff0c;就是要重写toString()方法&#xff0c;但是我现在还不知道怎么写 修改完的代码&#xff0c;但是数据库显示&#…

ASIO网络调试助手之一:简介

多年前&#xff0c;写过几篇《Boost.Asio C网络编程》的学习文章&#xff0c;一直没机会实践。最近项目中用到了Asio&#xff0c;于是抽空写了个网络调试助手。 开发环境&#xff1a; Win10 Qt5.12.6 Asio(standalone) spdlog 支持协议&#xff1a; UDP TCP Client TCP Ser…

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略&#xff0c;该策略要求请求的域名、协议和端口必须与提供资源的服务相同。…

Linux线程管理进阶:分离,等待、终止与C++11线程接口的封装实践

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f351;线程终止&#x1f34d;线程等待*多线程创建&#xff0c;传自己定义的对象示例代码&#xff1a;* &#x1f34e;线程的分…

STM32(十二):DMA直接存储器存取

DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输&#xff0c;无须CPU干预&#xff0c;节省了CPU的资源。&#xff08;运行内存SRAM、程序存储器Flash、寄存器&#xff09; 12个独立可配置的通道&…

[数据集][目标检测]井盖丢失未盖破损检测数据集VOC+YOLO格式2890张5类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2890 标注数量(xml文件个数)&#xff1a;2890 标注数量(txt文件个数)&#xff1a;2890 标注…

页面要突破手机安全区域来全屏显示(沉浸式模式显示),其他页面不需要,如何设置安全区域文字颜色

#效果图 ##思路遇到的问题 在aboutToAppear中使用window模块的 getLastWindow 和 setWindowLayoutFullScreen两个方法来处理全屏显示 设置沉浸式模式的特点&#xff1a; 在任何一个页面中设置过一次之后&#xff0c;其他页面也会跟着全屏显示 这么处理会出现问题&#xff1a…

如何使用Spoon连接data-integration-server并在服务器上执行转换

1.建立连接 2.新建转换或任务 3.右键[子服务器]&#xff0c;新建一个服务器连接(data-integration-server服务器的连接信息) 4.右键[Run configurations],新建一个执行连接,勾选相应的选项即可: 5.选择服务器运行即可! 6.最后&#xff0c;你可以通过服务器端的WEB查看执行日志…

猫眼电影字体破解(图片转码方法)

问题 随便拿一篇电影做样例。我们发现猫眼的页面数据在预览窗口中全是小方框。在当我们拿到源码以后&#xff0c;数据全是加密后的。所以我们需要想办法破解加密&#xff0c;拿到数据。 破解过程 1.源码获取问题与破解 分析 在我们刚刚请求url的时候是可以得到数据的&#xff…

【MySQL】MySQL操作介绍

MySQL操作 认识 MySQL什么是 MySQL关系型数据库的组成结构"客户端-服务器"结构 数据库的基本操作创建数据库查看数据库删除数据库使用数据库 数据类型整型浮点类型字符串类型日期类型总结 表的操作创建表查看表查看表的信息删除表 数据的基础操作插入数据指定列插入全…

Java过滤器和监听器

1. 过滤器 1.1. 过滤器 使用baseServlet的优点&#xff1a;方便进行乱码的统一处理&#xff0c;但是如果不使用servlet&#xff0c;在进行中文乱码处理时就需要在每个servlet中书写&#xff0c;比较麻烦 问题&#xff1a;重复的代码在项目中多次使用书写 解决&#xff1a;过滤…

2024 第七届“巅峰极客”网络安全技能挑战赛初赛 Web方向 题解WirteUp

EncirclingGame 题目描述&#xff1a;A simple game, enjoy it and get the flag when you complete it. 开题&#xff0c;前端小游戏&#xff0c;红点出不去就行 直接玩通关了 看看如何不玩也能拿到flag&#xff0c;flag存储在后端php文件内&#xff0c;前端找不到。 看一下…

【原创】java+springboot+mysql校园二手商品交易网设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

【MySQL】MySQL基础

目录 什么是数据库主流数据库基本使用MySQL的安装连接服务器服务器、数据库、表关系使用案例数据逻辑存储 MySQL的架构SQL分类什么是存储引擎 什么是数据库 mysql它是数据库服务的客户端mysqld它是数据库服务的服务器端mysql本质&#xff1a;基于C&#xff08;mysql&#xff09…

QT 编译报错:C3861: ‘tr‘ identifier not found

问题&#xff1a; QT 编译报错&#xff1a;C3861&#xff1a; ‘tr’ identifier not found 原因 使用tr的地方所在的类没有继承自 QObject 类 或者在不在某一类中&#xff0c; 解决方案 就直接用类名引用 &#xff1a;QObject::tr( )