

Spring 源码(5)BeanFactory使用的准备及自定义属性值解析器 - 玲丶蹊
source link: https://www.cnblogs.com/redwinter/p/16167214.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

BeanFactory 使用前的准备
上一篇文章 https://www.cnblogs.com/redwinter/p/16165878.html 介绍了自定义标签的使用,完成了AbstractApplicationContext#refresh
第二个方法 的介绍,本文将继续介绍Spring
源码的重要方法AbstractApplicationContext#refresh
方法的第三个方法:prepareBeanFactory
,准备BeanFactory
。
源码如下:
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
// 设置类加载器
beanFactory.setBeanClassLoader(getClassLoader());
// 设置Spel 表达式解析器,用于属性填充时对值进行表达式解析
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
// 扩展点,添加一个属性编辑器的注册器,也可以使用 CustomEditorConfigurer 进行设置
// 后面在进行属性填充的时候会调用这个属性编辑器进行属性的解析
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// Configure the bean factory with context callbacks.
// 扩展点,添加一个BeanPostProcessor 这里添加这个进行处理,使用前置处理器执行下面忽略的六个Aware接口
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// 由于上面设置了这六个接口,因此需要忽略掉,不让Spring使用自动装配进行Bean的装配,而是使用BeanPostProcessor
// 的后置处理器的前置方法进行调用,因为如果不忽略,那么自定义的Bean中就会使用Setter注入进行装配,
// spring 这样做是为了统一的进行处理在Bean增强的时候
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.
// aop织入 编译器织入、运行期织入、类加载织入
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.
// 注册环境信息
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());
}
}
这个方法中主要做了以下事情:
- 设置BeanFactory的类加载器。
- 设置Bean的SPEL表达式的解析器,其作用是对值进行表达式的解析,比如在属性填充时,针对值是Properties或者String类型的时候就会使用el表达式进行解析。
- 设置属性编辑器的注册器,作用是对属性进行解析,比如在属性填充时,针对字符串String类型的时候进行类型转换,就可以自定义属性编辑器针对性的进行解析操作。
- 添加一些内置的BeanPostProcessor用于后面对象初始化时调用。
- 设置环境信息,系统属性,系统环境变量等。
这个方法预留了一些扩展点,比如可以添加自定义的属性编辑器,添加自定义的BeanPostProcessor等。
定制Bean的属性解析器
我们知道在Bean
的初始化时是分为两步,一步是属性填充,一步是初始化,在属性填充的时候,Spring
会针对属性进行解析,如果属性值对应的类型和传入的值类型不一致,就会进行值的自定义解析,前提是你自定义了属性解析器,否则就会报错:报值的类型转换失败。
接下来我们自定义一个属性解析器,比如我现在有个类CustomUser
,其中有个属性类型是Address
,还有个属性类型是Date
,但是我在定义Bean
的时候我把address
属性设置为xxx_xxx_xxx
,表示xxx省xxx市xxx区(县),date属性设置为yyyy-MM-dd HH:mm:ss
格式的日期,我要让Spring
帮我解析出正确的值出来,话不多说,上代码。
编写CustomUser类以及Address类
- Address类
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class Address {
private String province;
private String city;
private String town;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getTown() {
return town;
}
public void setTown(String town) {
this.town = town;
}
// 为了验证重写toString方法
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
", town='" + town + '\'' +
'}';
}
}
- CustomUser类
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class CustomUser {
private String name;
private Address address;
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "CustomUser{" +
"name='" + name + '\'' +
", address=" + address +
", date=" + date +
'}';
}
}
编写Address解析器和注册器
- 解析器(编辑器)
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class AddressPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
/**
* 自定义属性编辑器,将属性解析成自定义对象,比如传入的是一个字符串,可以解析成另外一个对象
*/
String[] arr = text.split("_");
Address address = new Address();
address.setProvince(arr[0]);
address.setCity(arr[1]);
address.setTown(arr[2]);
// 设置值
setValue(address);
}
}
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class AddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Address.class,new AddressPropertyEditor());
}
}
编写Date解析器和注册器
- 解析器(编辑器)
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class DatePropertyEditor extends PropertyEditorSupport {
private final DateFormat dateFormat;
public DatePropertyEditor(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (!StringUtils.hasText(text)) {
System.out.println("日期类型的属性不能为空!");
return;
}
try {
Date date = dateFormat.parse(text);
setValue(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
/**
* @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
* @since 1.0
**/
public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class,new DatePropertyEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
}
}
配置注册器到Spring容器中
配置注册器有两种方式,一种是直接在定制BeanFactory
的方法中添加注册器,一种是在Spring
配置文件中添加
我在Spring
容器中先配置CustomUser
的信息以及Date
日期类型的注册器:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:redwinter="http://www.redwinter.com/schema/redwinter"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.redwinter.com/schema/redwinter http://www.redwinter.com/schema/redwinter.xsd
">
<!--自定义标签-->
<redwinter:dl id ="redwinter" email="[email protected]" password="123456" username="redwinter-name"/>
<redwinter:dl id ="redwinter123456" email="[email protected]" password="123456" username="redwinter-name"/>
<!--自定义属性编辑器的解析器-->
<bean class="com.redwinter.test.CustomUser">
<property name="name" value="冬玲记忆"/>
<property name="address" value="四川省_成都市_郫都区"/>
<property name="date" value="2022-04-19 19:50:20"/>
</bean>
<!--配置自定义的编辑器注册器-->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<bean class="com.redwinter.test.DatePropertyEditorRegistrar"/>
</list>
</property>
</bean>
</beans>
另外还有个Address
的注册器我配置在定制BeanFactory
的方法中:
@Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 扩展点 设置不去处理循环依赖或者beanDefinition覆盖
super.setAllowBeanDefinitionOverriding(true);
super.setAllowCircularReferences(true);
super.customizeBeanFactory(beanFactory);
// 添加一个自定义的属性编辑器的注册器
beanFactory.addPropertyEditorRegistrar(new AddressPropertyEditorRegistrar());
}
好了,配置完成,运行试试:
public class BeanCreate {
@Test
public void classPathXml() {
// ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
ClassPathXmlApplicationContext context = new MyClassPathXmlApplicationContext("classpath:spring-test.xml");
Redwinter redwinter = (Redwinter) context.getBean("redwinter");
System.out.println(redwinter.getEmail());
Redwinter redwinter123456 = (Redwinter) context.getBean("redwinter123456");
System.out.println(redwinter123456.getEmail());
CustomUser bean = context.getBean(CustomUser.class);
System.out.println(bean);
}
}
输出日志:
[email protected]
[email protected]
CustomUser{name='冬玲记忆', address=Address{province='四川省', city='成都市', town='郫都区'}, date=Tue Apr 19 19:50:20 CST 2022}
说明配置没有问题,输出了想要的结果。
这里有个疑问为什么你知道在编写注册器和解析器的时候需要实现这些类呢?
其实原因很简单,根据源码我们知道Spring
默认添加了一个ResourceEditorRegistrar
注册器,进去ResourceEditorRegistrar
类中发现,他实现了PropertyEditorRegistrar
接口,然后重写了registerCustomEditors
方法,在这个方法中他添加了很多编辑器:
@Override
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
方法发现最终是将这些编辑器加入到了PropertyEditorRegistry
接口的默认实现类PropertyEditorRegistrySupport
类的customEditors
属性中,而且你还在这个类中发现,有很多的编辑器是默认加载进去的:
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);
}
}
所以为什么我们在定义Bean
属性的时候这些默认的属性会自动帮你转换出来,就是这个原因。那么注册器的编写我们也可以直接实现PropertyEditorRegistrar
这个接口,然后重写registerCustomEditors
方法把自定义的编辑器加入即可。
那编辑器怎么实现呢?
编辑器的话自然也就很简单了,随便点击一个编辑器看下他是怎么实现的,你就可以实现出来了,最终发现这些编辑器都是继承了PropertyEditorSupport
这个类,而PropertyEditorSupport
这个类实现了PropertyEditor
这个接口,那这么方法实现哪个呢?
不着急看源码:
private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
try {
editor.setValue(oldValue);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
}
// Swallow and proceed.
}
// 调动转换方法,这里就会调用到自定义的属性编辑器中,执行自定义的逻辑转换
editor.setAsText(newTextValue);
return editor.getValue();
}
源码明确写了使用编辑器调用setAsText
方法进行新值的转换,然后再去获取getValue
得到值,那么说明只需要重写setAsText
方法并且将转换的值调用setValue
方法即可。
所以我们直接继承PropertyEditorSupport
类,然后重写setAsText
方法即可实现属性值的解析和转换。
接下来就是分析AbstractApplicationContext#refresh
方法的第四个方法和第五个方法,第四个方法postProcessBeanFactory
是一个空方法,留给子类实现,第五个方法invokeBeanFactoryPostProcessors
是执行BeanFactoryPostProcessor
,这篇就到这里,下一篇文章继续。
Recommend
-
45
I see a lot of questions asking about the difference between BeanFactory and ApplicationContext. Along with that, I get the q...
-
42
Mi&Jack Blog Copyright © 江湖迈杰的博客 2020 Theme on
-
10
深入剖析 Spring 核心数据结构:BeanFactory 2020-06-26 ...
-
6
如果你的简历上这么写,熟练掌握Spring,并对Spring源码有一定的见解(或者是读过Spring源码),那么无法避免的就是要问几个问题。 IOC的实现原理是什么? AOP底层是如何实现的? 因为这些基本上都是面试必然要准备的题目,所以如果你能...
-
6
Spring 的 BeanFactory 和 FactoryBean 傻傻分不清?发布于 今天 14:00 ApplicationContext接口,它由BeanFactory接口派生而来。ApplicationContext包含Bean...
-
4
Difference between ApplicationContext and BeanFactory in Spring frameworkSkip to content...
-
0
BeanFactory 实现 犀牛的博客 姑苏城外一茅屋,万树...
-
6
spring boot 程序入口@SpringBootApplicationpublic class Application {
-
6
Spring 中 BeanFactory 和 FactoryBean 有何区别? 以下内容基于 Spring6.0.4。 这也是 Spring 面试时一道经典的面试问题,今天我们来聊一聊这个话...
-
6
相关代码提交记录:https://github.com/linweiwang/spring-framework-5.3.33
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK