spring源码深度解析—容器的功能扩展(上)
在之前的博文中我们一直以BeanFactory接口以及它的默认实现类XmlBeanFactory为例进行分析,但是Spring中还提供了另一个接口ApplicationContext,用于扩展BeanFactory中现有的功能。
ApplicationContext和BeanFactory两者都是用于加载Bean的,但是相比之下,ApplicationContext提供了更多的扩展功能,简而言之:ApplicationContext包含BeanFactory的所有功能。通常建议比优先使用ApplicationContext,除非在一些限制的场合,比如字节长度对内存有很大的影响时(Applet),绝大多数“典型的”企业应用系统,ApplicationContext就是需要使用的。
那么究竟ApplicationContext比BeanFactory多了哪些功能?首先我们来看看使用两个不同的类去加载配置文件在写法上的不同如下代码:
使用BeanFactory方式加载XML.
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
使用ApplicationContext方式加载XML.
ApplicationContext bf = new ClassPathXmlApplicationContext("beanFactoryTest.xml");
接下来我们就以ClassPathXmlApplicationContext作为切入点,开始对整体功能进行分析。首先看下其构造函数:
public ClassPathXmlApplicationContext() {
}
public ClassPathXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent)
throws BeansException {
this(configLocations, true, parent);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
设置路径是必不可少的步骤,ClassPathXmlApplicationContext中可以将配置文件路径以数组的方式传入,ClassPathXmlApplicationContext可以对数组进行解析并进行加载。而对于解析及功能实现都在refresh()中实现。下图是ClassPathXmlApplicationContext类层次图:
1. 设置配置路径
在ClassPathXmlApplicationContext中支持多个配置文件以数组方式同时传入,以下是设置配置路径方法代码:
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;
}
}
其中如果给定的路径中包含特殊符号,如${var},那么会在方法resolvePath中解析系统变量并替换
2. 扩展功能
设置了路径之后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说refresh函数中包含了几乎ApplicationContext中提供的全部功能,而且此函数中逻辑非常清晰明了,使我们很容易分析对应的层次及逻辑,我们看下方法代码:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//准备刷新的上下文 环境
/* * 初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证。 * 在某种情况下项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响着系统 * 的正确性,那么ClassPathXmlApplicationContext为我们提供的这个准备函数就显得非常必要, * 它可以在Spring启动的时候提前对必须的变量进行存在性验证。 */
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//初始化BeanFactory,并进行XML文件读取
/* * ClassPathXMLApplicationContext包含着BeanFactory所提供的一切特征,在这一步骤中将会复用 * BeanFactory中的配置文件读取解析及其他功能,这一步之后,ClassPathXmlApplicationContext * 实际上就已经包含了BeanFactory所提供的功能,也就是可以进行Bean的提取等基础操作了。 */
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//对beanFactory进行各种功能填充
/* * @Qualifier与@Autowired等注解正是在这一步骤中增加的支持 */
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//子类覆盖方法做额外处理
/* * Spring之所以强大,为世人所推崇,除了它功能上为大家提供了便利外,还有一方面是它的 * 完美架构,开放式的架构让使用它的程序员很容易根据业务需要扩展已经存在的功能。这种开放式 * 的设计在Spring中随处可见,例如在本例中就提供了一个空的函数实现postProcessBeanFactory来 * 方便程序猿在业务上做进一步扩展 */
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//激活各种beanFactory处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用实在getBean时候
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//为上下文初始化Message源,即不同语言的消息体,国际化处理
initMessageSource();
// Initialize event multicaster for this context.
//初始化应用消息广播器,并放入“applicationEventMulticaster”bean中
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//留给子类来初始化其它的Bean
onRefresh();
// Check for listener beans and register them.
//在所有注册的bean中查找Listener bean,注册到消息广播器中
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//初始化剩下的单实例(非惰性的)
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
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 Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
我们简单的分析下代码的步骤:
(1)初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证。
在某种情况下项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响着系统的正确性,那么ClassPathXmlApplicationContext为我们提供的这个准备函数就显得非常必要,他可以在spring启动的时候提前对必须的环境变量进行存在性验证。
(2)初始化BeanFactory,并进行XML文件读取。
之前提到ClassPathXmlApplicationContext包含着对BeanFactory所提供的一切特征,那么这一步中将会复用BeanFactory中的配置文件读取解析其他功能,这一步之后ClassPathXmlApplicationContext实际上就已经包含了BeanFactory所提供的功能,也就是可以进行Bean的提取等基本操作了。
(3)对BeanFactory进行各种功能填充
@Qualifier和@Autowired应该是大家非常熟悉的注解了,那么这两个注解正是在这一步骤中增加支持的。
(4)子类覆盖方法做额外处理。
spring之所以强大,为世人所推崇,除了它功能上为大家提供了遍历外,还有一方面是它完美的架构,开放式的架构让使用它的程序员很容易根据业务需要扩展已经存在的功能。这种开放式的设计在spring中随处可见,例如本利中就提供了一个空的函数实现postProcessBeanFactory来方便程序员在业务上做进一步的扩展。
(5)激活各种BeanFactory处理器
(6)注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean时候
(7)为上下文初始化Message源,及对不同语言的小西天进行国际化处理
(8)初始化应用消息广播器,并放入“applicationEventMulticaster”bean中
(9)留给子类来初始化其他的bean
(10)在所有注册的bean中查找listener bean,注册到消息广播器中
(11)初始化剩下的单实例(非惰性的)
(12)完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人。
接下来我们就详细的讲解每一个过程
3. 环境准备
prepareRefresh函数主要是做些准备工作,例如对系统属性及环境变量的初始化及验证,其方法体如下;
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
初看这个函数貌似没有什么用,因为最后两句代码才是关键,但是却没有什么逻辑处理,initPropertySources是空的,没有什么逻辑,而getEnvironment().validaterequiredProperties也因为没有需要验证的属性而没有做任何处理。其实这个函数用好了作用还是挺大的,那么该怎么用呢,我们先探索下各个函数的作用。
(1)initPropertySources,这正符合Spring的开放式结构设计,给用户最大扩展Spring的能力。用户可以根据自身的需要重写initPropertySourece方法,并在方法中进行个性化的属性处理及设置。
(2)validateRequiredProperties则是对属性进行验证,那么如何验证呢?举个融合两句代码的小例子来理解。
例如现在有这样一个需求,工程在运行过程中用到的某个设置(例如VAR)是从系统环境变量中取得的,而如果用户没有在系统环境变量中配置这个参数,那工程不会工作。这一要求也许有各种各样的解决办法,在Spring中可以这么做,可以直接修改Spring的源码,例如修改ClassPathXmlApplicationContext。当然,最好的办法是对源码进行扩展,可以自定义类如下代码:
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext{
public MyClassPathXmlApplicationContext(String.. configLocations){
super(configLocations);
}
protected void initPropertySources(){
//添加验证要求
getEnvironment().setRequiredProterties("VAR");
}
}
自定义了继承自ClassPathXmlApplicationContext的MyClassPathXmlApplicationContext,并重写了initPropertySources方法,在方法中添加了个性化需求,那么在验证的时候也就是程序走到getEnvironment().validateRequiredProperties()代码的时候,如果系统并没有检测到对应VAR的环境变量,将抛出异常。当然我们还需要在使用的时候替换掉原有的ClassPathXmlApplicationContext,如下代码:
public static void main(Stirng[] args){
ApplicationContext bf = new MyClassPathXmlApplicationContext("myTest.xml");
User user = (User)bf.getBean("testBean");
}
4. 加载BeanFactory
obtainFreshBeanFactory方法从字面理解是获取beanFactory.ApplicationContext是对BeanFactory的扩展,在其基础上添加了大量的基础应用,obtainFreshBeanFactory正式实现beanFactory的地方,经过这个函数后ApplicationContext就有了BeanFactory的全部功能。我们看下此方法的代码:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//初始化BeanFactory,并进行XML文件读取,并将得到的BeanFactory记录在当前实体的属性中
refreshBeanFactory();
//返回当前实体的beanFactory属性
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
继续深入到refreshBeanFactory方法中,方法的实现是在AbstractRefreshableApplicationContext中:
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建DefaultListableBeanFactory
/* * 以前我们分析BeanFactory的时候,不知道是否还有印象,声明方式为:BeanFactory bf = * new XmlBeanFactory("beanFactoryTest.xml"),其中的XmlBeanFactory继承自DefaulltListableBeanFactory; * 并提供了XmlBeanDefinitionReader类型的reader属性,也就是说DefaultListableBeanFactory是容器的基础。必须 * 首先要实例化。 */
DefaultListableBeanFactory beanFactory = createBeanFactory();
//为了序列化指定id,如果需要的话,让这个BeanFactory从id反序列化到BeanFactory对象
beanFactory.setSerializationId(getId());
//定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖以及设置
//@Autowired和Qualifier注解解析器QualifierAnnotationAutowireCandidateResolver
customizeBeanFactory(beanFactory);
//加载BeanDefiniton
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
//使用全局变量记录BeanFactory实例。
//因为DefaultListableBeanFactory类型的变量beanFactory是函数内部的局部变量,
//所以要使用全局变量记录解析结果
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
我们详细分析下上面代码的步骤:
(1)创建DefaultListableBeanFactory
以前我们分析BeanFactory的时候,不知道是否还有印象,声明方式为:BeanFactory bf = new XmlBeanFactory(“beanFactoryTest.xml”),其中的XmlBeanFactory继承自DefaulltListableBeanFactory;,并提供了XmlBeanDefinitionReader类型的reader属性,也就是说DefaultListableBeanFactory是容器的基础。必须首先要实例化,这里就是创建DefaultListableBeanFactory的步骤。
(2)指定序列号ID
(3)定制BeanFactory
(4)加载BeanDefinition
(5)使用全局变量保存BeanFactory
因为DefaultListableBeanFactory类型的变量beanFactory是函数内部的局部变量, 所以要使用全局变量记录解析结果
4.1 定制BeanFactory
customizeBeanFactory方法在基本容器的基础上,增加了是否允许覆盖是否允许扩展的设置并提供了主机@Qualifierh额@Autowired支持。我们看到源码:
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
//如果属性sllowBeanDefinitionOverring不为空,设置给beanFactory对象相应属性,
//此属性的含义:是否允许覆盖同名称的不同定义的对象
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//如果属性allowCircularReferences不为空,设置给beanFactory对象相应属性,
//此属性的含义:是否允许循环依赖
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
对于允许覆盖和允许依赖的设置这里只判断了是否为空,如果不为空姚进行设置,但是并没有看到在哪里设置,究竟在哪里设置?这就是spring中使用的子类覆盖方式,如下代码我们自己实现一个子类:
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext{
... ...
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory){
super.setAllowBeanDefinitionOverriding(fa;se);
super.setAllowCircularReferences(false);
super.customizeBeanFactory(beanFactory);
}
}
4.2 加载BeanDefinition
在第一步中提到了将ClassPathXmlApplicationContext与XMLBeanFactory创建的对比,除了初始化DefaultListableBeanFactory外,还需要XmlBeanDefinitionReader来读取XML,那么在loadBeanDefinitions方法中首先要做的就是初始化XmlBeanDefinitonReader,我们跟着到loadBeanDefinitions(beanFactory)方法体中,我们看到的是在AbstractXmlApplicationContext中实现的,具体代码如下:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
在初始化了DefaultListableBeanFactory和XmlBeanDefinitionReader后,就可以进行配置文件的读取了。继续进入到loadBeanDefinitions(beanDefinitionReader)方法体中,代码如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
因为在XmlBeanDefinitionReader中已经将之前初始化的DefaultListableBeanFactory注册进去了,所以XmlBeanDefinitionReader所读取的BeanDefinitionHolder都会注册到DefinitionListableBeanFactory中,也就是经过这个步骤,DefaultListableBeanFactory的变量beanFactory已经包含了所有解析好的配置。
5. 功能扩展
如上图所示prepareBeanFactory(beanFactory)就是在功能上扩展的方法,而在进入这个方法前spring已经完成了对配置的解析,接下来我们详细分析下次函数,进入方法体:
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
//设置beanFactory的classLoader为当前context的classloader
beanFactory.setBeanClassLoader(getClassLoader());
//设置beanFactory的表达式语言处理器,Spring3增加了表达式语言的支持,
//默认可以使用#{bean.xxx}的形式来调用相关属性值
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
//为beanFactory增加了一个的propertyEditor,这个主要是对bean的属性等设置管理的一个工具
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
//设置了几个忽略自动装配的接口
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 interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
//设置了几个自动装配的特殊规则
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// Register early post-processor for detecting inner beans as ApplicationListeners.
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
// Detect a LoadTimeWeaver and prepare for weaving, if found.
//增加对AspectJ的支持
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
// Register default environment beans.
//添加默认的系统环境bean
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
详细分析下代码发现上面函数主要是在以下方法进行了扩展:
(1)对SPEL语言的支持
(2)增加对属性编辑器的支持
(3)增加对一些内置类的支持,如EnvironmentAware、MessageSourceAware的注入
(4)设置了依赖功能可忽略的接口
(5)注册一些固定依赖的属性
(6)增加了AspectJ的支持
(7)将相关环境变量及属性以单例模式注册
接下来我们想法分析下以上步骤。
5.1 增加对SPEL语言的支持
Spring表达式语言全称为“Spring Expression Language”,缩写为“SpEL”,类似于Struts 2x中使用的OGNL语言,SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用。
SpEL使用#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL,使用格式如下:
<util:properties id="database" location="classpath:db.properties">
</util:properties>
<bean id="dbcp" class="org.apache.commons.dbcp.BasicDataSource">
<property name="username" value="#{database.user}"></property>
<property name="password" value="#{database.pwd}"></property>
<property name="driverClassName" value="#{database.driver}"></property>
<property name="url" value="#{database.url}"></property>
</bean>
上面只是列举了其中最简单的使用方式,SpEL功能非常强大,使用好可以大大提高开发效率。在源码中通过代码beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver()),注册语言解析器,就可以对SpEL进行解析了,那么之后是在什么地方调用这个解析器的呢?
之前说beanFactory中说过Spring在bean进行初始化的时候会有属性填充的一步,而在这一步中Spring会调用AbstractAutowireCapabelBeanFactory类的applyPropertyValues来进行属性值得解析。同时这个步骤中一般通过AbstractBeanFactory中的evaluateBeanDefinitionString方法进行SpEL解析,方法代码如下:
protected Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) {
if (this.beanExpressionResolver == null) {
return value;
}
Scope scope = (beanDefinition != null ? getRegisteredScope(beanDefinition.getScope()) : null);
return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}
当调用这个方法时会判断是否有语言解析器,如果存在则调用语言解析器,解析的过程在Spring的expression包里。
5.2 增加属性注册编辑器
在spring的DI注入的时候可以把普通属性注入进来,但是像Date类型是无法被识别的,如下我们先定义一个简单的bean;
public class User {
private String name;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
在配置文件中需要对User的birthday进行注入,如下:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.yhl.myspring.demo.applicationcontext.User">
<property name="name" value="user"/>
<property name="birthday" value="2018-04-18"/>
</bean>
</beans>
如果直接按照上面的使用方式使用的话就会报错,类型转换异常,因为在User中的birthday是Date类型,而在XML配置文件中配置的是string类型,在spring中有两种方式解决:
5.2.1 自定义属性编辑器
使用自定义属性编辑器,继承PropertyEditorSupport,重写setAsText方法,具体步骤如下:
(1)编写自定义编辑器
public class MyPropertyEditor extends PropertyEditorSupport {
private String format = "yyyy-MM-dd";
public void setFormat(String format) {
this.format = format;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat sdf = new SimpleDateFormat(format);
try {
Date date = sdf.parse(text);
this.setValue(date);
} catch (ParseException e) {
e.printStackTrace();
}
//super.setAsText(text);
}
}
(2)将自定义属性编辑器注册到spring中
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date">
<bean class="com.yhl.myspring.demo.applicationcontext.MyPropertyEditor">
<property name="format" value="yyyy-MM-dd"></property>
</bean>
</entry>
</map>
</property>
</bean>
如上述所示在配置文件中引入CustomEditorConfigurer的bean,并加入自定义的属性编辑器,其中key为属性编辑器对应的类,经过这样的配置后当spring在注入属性时如果遇到Date类型属性时会自动调用自定义的属性编辑器进行解析,并用解析结果代替配置属性进行注入。
5.2.2 注册spring自带的属性编辑器CustomDateEditor
也可以通过注册spring自带的CustomDateEditor编辑器来实现。实现步骤如下:
(1)定义属性编辑器
public class DataPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),true));
}
}
(2)然后注册到spring中
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<bean class="com.yhl.myspring.demo.applicationcontext.DataPropertyEditorRegistrar"></bean>
</list>
</property>
</bean>
通过在配置文件中将自定义的DataPropertyEditorRegistrar注册到CustomEditorConfigurer的propertyEditorRegistrars属性中,可以与方式1有相同的效果。
我们了解了自定义属性编辑器的使用,但是这跟我们要分析的核心代码beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()))好像并无关联,因为在自定义属性编辑器注册时用的是PropertyEditorRegistrar的registerCustomEditor方法,而这里用到的是ConfigurableListableBeanFactory的addPropertyEditorRegistrar方法,为了弄清楚具体逻辑,我们就深入的来看下ResourceEditorRegistrar的内部实现,在其实现中我们最关心的是registerCustomEditors方法了,具体看下代码:
public void registerCustomEditors(PropertyEditorRegistry registry) {
ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
doRegisterEditor(registry, Resource.class, baseEditor);
doRegisterEditor(registry, ContextResource.class, baseEditor);
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
doRegisterEditor(registry, Path.class, new PathEditor(baseEditor));
doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor));
doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));
ClassLoader classLoader = this.resourceLoader.getClassLoader();
doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));
if (this.resourceLoader instanceof ResourcePatternResolver) {
doRegisterEditor(registry, Resource[].class,
new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
}
}
上述代码中都使用了doRegisterEditor这个方法,在其方法内部使用了我们之前提到的自定义属性的关键代码:registry.registerCustomEditor(requiredType, editor);我们继续看下registerCustomEditors方法的功能,发现其无非就是注册一系列常用类型的属性编辑器,编辑器注册后,一旦一个bean中存在一些类型的属性转换,spring就会调用对应的属性类型编辑器对属性进行转换后使用。
分析到这里我们看到ResourceEditorRegistrar的registerCustomEditors方法实现了一些批量注册的功能,但是beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()))仅仅是注册了ResourceEditorRegistrar的实例,而并没有调用ResourceEditorRegistrar的registerCustomEditors方法进行注册,那具体是在什么时候进行注册呢?我们进一步的查看ResourceEditorRegistrar的registerCustomEditors方法的调用层次结构,如下图说是:
发现在AbstractBeanFactory中的registerCustomEditors方法调用过,我们继续跟踪这个方法的调用层次,如下图:
在上图的方法调用层次中我们看到了一个非常熟悉的方法initBeanWrapper,这个是在bean初始化时使用的方法,此方法主要是在将BeanDefinition转换为BeanWrapper后用于属性的填充,看到这里后逻辑就非常的清晰了。在bean初始化后会调用ResourceEditorRegistrar的registerCustomEditors进行属性编辑器的批量注册,注册后在属性填充环节就可以让spring使用这些编辑器对属性进行编辑了。
上面我们提到了BeanWrapper,在spring中用于封装bean的就是BeanWrapper类型,而BeanWrapper类型继承了PropertyEditorRegistry类型,也就是我们之前反复看到的方法参数PropertyEditorRegistry registry,其实大部分情况下都是BeanWrapper,而BeanWrapper在spring中的默认实现是BeanWrapperImpl,而BeanWrapperImpl除了实现了BeanWrapper外还继承了PropertyEditorRegistrySupport,在PropertyEditorRegistrySupport中有这样一个方法:
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
// Simple editors, without parameterization capabilities.
// The JDK does not contain a default editor for any of these target types.
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
this.defaultEditors.put(Path.class, new PathEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());
// Default instances of collection editors.
// Can be overridden by registering custom instances of those as custom editors.
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
// Default editors for primitive arrays.
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
// The JDK does not contain a default editor for char!
this.defaultEditors.put(char.class, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
// The JDK does not contain default editors for number wrapper types!
// Override JDK primitive number editors with our own CustomNumberEditor.
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
// Only register config value editors if explicitly requested.
if (this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}
5.3 添加ApplicationContextAwareProcessor处理器
在上面对属性编辑器进行了分析后接下来我们继续分析方法beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)),此方法的目的是用来注册BeanPostProcessor的,主要的逻辑还是在ApplicationContextAwareProcessor中实现的,ApplicationContextAwareProcessor实现了BeanPostProcessor接口,我们看下之前说过,在bean实例化的时候,也就是Spring激活bean的init-method的前后,会调用BeanProcessor的postProcessBeforeInitialization方法和postProcessAfterInitialization方法。同样,对于ApplicationContextAwareProcessor也有这两个方法。我们看下这两个方法在ApplicationContextAwareProcessor中具体代码:
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareInterfaces(bean);
return null;
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
我们可以看到postProcessAfterInitialization在其中没有做过多的处理,那我们就重点看下postProcessBeforeInitialization方法,其方法内部使用了invokeAwareInterfaces方法,继续进入到方法体内:
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
从invokeAwareInterfaces方法中,我们可以看出来,实现这些Aware接口的bean在被初始化之后,可以取得一些对应的资源。
5.4 设置忽略依赖
Spring将ApplicationContextAwareProcessor注册后,在invokeAwareInterfaces方法中间调用的Aware类已经不是普通的bean了,如ResourceLoaderAware,ApplicationEventPublisherAware等,那么当然需要在Spring做bean的依赖注入的时候忽略它们。而ignoreDependencyInterface的作用正是如此。
以下是设置忽略的方法:
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
5.5 注册依赖
Spring中有忽略依赖的功能,也有注册依赖的功能。以下是注册依赖的代码:
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
当注册了依赖解析后,例如当注册了对BeanFactory.class的解析后,当bean的属性注入的时候,一旦检测到属性为BeanFactory类型便会将beanFactory的实例注入进去。