Spring 原始碼(5)BeanFactory使用的準備及自定義屬性值解析器

玲丶蹊發表於2022-04-20

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="abc@qq.com" password="123456" username="redwinter-name"/>
	<redwinter:dl id ="redwinter123456"  email="123456-abc@qq.com" 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);
	}
}

輸出日誌:

abc@qq.com
123456-abc@qq.com
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,這篇就到這裡,下一篇文章繼續。

相關文章