4

Spring 源码(5)BeanFactory使用的准备及自定义属性值解析器 - 玲丶蹊

 3 years ago
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.
neoserver,ios ssh client

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK