Spring source code analysis (16): refresh preparebeanfactory source code analysis

1, Foreword

In this blog, we mainly introduce the prepareBeanFactory method, the initialization preparation of the bean factory, setting some attribute values of the bean factory, which interfaces to ignore, which beans to register, and which bean post processors to set, etc. Next, let's look at the specific source code analysis, and here we also look at the Spring Attribute Editor, and if we expand an Attribute Editor.

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// Tell the internal bean factory to use the context's class loader etc.
		beanFactory.setBeanClassLoader(getClassLoader());

		// Set the SPEL expression parser to support Spring's SPEL expressions
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));

		// Add a property editor registrar. For example, a string type Address needs to be converted into an Address object, which can be used
		// Refer to the example: com.com under the spring source study module wb. spring. Sample program under propertyeditor package
		beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

		// Add the post processor of the bean. What is added here is Spring's own post processor, which is used to callback the methods in the aware interface implemented by the bean
		beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

		// The following ignoreDependencyInterface is used to set the interface to be ignored in the bean factory
		// The current environmental information can be obtained by implementing the EnvironmentAware interface.
		/**
		 * If the EnvironmentAware interface is added to the ignoreDependencyInterface, it will not be injected normally through @ Autowired where it is used
		 *   Instead, it needs to be injected through the setEnvironment method. Other interfaces below are similar
		 */
		beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
		// The parser of String type value can be obtained by implementing the embeddedvalueresolveaware interface
		beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
		// Resource loader, for example: @ Autowired ResourceLoaderAware aware; Will not be injected
		beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
		// Event publisher
		beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
		// Message resources
		beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
		// Application context information
		beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

		// Register some interfaces that can be assembled automatically. When the type is dependencyType, autowiredValue is injected. In order to solve the problem of giving priority to the implementation of that subclass when a type has multiple subclass implementations.
		// For example, the first one below, when the injection type is beanfactory, the injected value is beanfactory, and the default is DefaultListableBeanFactory
		beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
		// When the injection type is ResourceLoader, the injected value is ApplicationContext
		beanFactory.registerResolvableDependency(ResourceLoader.class, this);
		// When the injection type is ApplicationEventPublisher, the injected value is ApplicationContext
		beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
		// When the injected type is ApplicationContext, the injected value is ApplicationContext
		beanFactory.registerResolvableDependency(ApplicationContext.class, this);

		// Add a bean post processor, ApplicationListenerDetector
		// Register early post-processor for detecting inner beans as ApplicationListeners.
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

		// Detect a LoadTimeWeaver and prepare for weaving, if found.
		// If there is a bean definition with the name loadTimeWeaver in the bean factory, add the LoaderTimeWeaverAwareProcessor post processor to the bean factory
		// Supplement: 1. Add support for AspectJ. There are three types of weaving in Java: (1) weaving during compilation; (2) Weaving in during class loading; (3) Weaving in during operation. Compile time weaving refers to the Java compile time,
		//         Using a special compiler to weave the section into java classes; Weaving in the class loading period refers to weaving in the section when the bytecode is loaded into the JVM through a special class loader; Run time weaving is
		//         The section is woven by using cglib or jdk.
		//      2. aspectJ provides two ways:
		//         (1) Through a special compiler, the facet classes written in aspectJ language are woven into java classes during the compilation period;
		//         (2) Class load time weaving is through the following LoadTimeWeaving
		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 some single instance beans related to the running environment in the container
		if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
			// beanName: environment, directly put the Spring internal object from new into the single instance cache pool of Spring
			beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
		}

		if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
			// beanName: systemProperties method: system getProperties();
			beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
		}

		if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
			// beanName: systemEnvironment, method: system getEnv();
			beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
		}
	}
  • Set the loader of the bean
  • Set the SPEL expression parser to support Spring's SPEL expressions
  • Add a property editor registrar. For example, a string type Address needs to be converted into an Address object. You can use this function. Later, we will implement a custom attribute editor. Later, we will see how its attribute editor is implemented.
  • Add the post processor of the bean. What is added here is Spring's own post processor, which is used to callback the methods in the aware interface implemented by the bean
  • ignoreDependencyInterface is used to set the interface to be ignored in the bean factory.
  • Register some interfaces that can be assembled automatically. When the type is dependencyType, autowiredValue is injected. In order to solve the problem of giving priority to the implementation of that subclass when a type has multiple subclass implementations.
  • Add a bean post processor, ApplicationListenerDetector.

2, ResourceEditorRegistrar property editor

We can see that it implements the PropertyEditorRegistrar interface. There is only one method, registerCustomEditors, which is used to register our property editor. Registered here are some Spring default attribute editors.

public class ResourceEditorRegistrar implements PropertyEditorRegistrar {

	private final PropertyResolver propertyResolver;

	private final ResourceLoader resourceLoader;


	/**
	 * Create a new ResourceEditorRegistrar for the given {@link ResourceLoader}
	 * and {@link PropertyResolver}.
	 * @param resourceLoader the ResourceLoader (or ResourcePatternResolver)
	 * to create editors for (usually an ApplicationContext)
	 * @param propertyResolver the PropertyResolver (usually an Environment)
	 * @see org.springframework.core.env.Environment
	 * @see org.springframework.core.io.support.ResourcePatternResolver
	 * @see org.springframework.context.ApplicationContext
	 */
	public ResourceEditorRegistrar(ResourceLoader resourceLoader, PropertyResolver propertyResolver) {
		this.resourceLoader = resourceLoader;
		this.propertyResolver = propertyResolver;
	}


	/**
	 * Populate the given {@code registry} with the following resource editors:
	 * ResourceEditor, InputStreamEditor, InputSourceEditor, FileEditor, URLEditor,
	 * URIEditor, ClassEditor, ClassArrayEditor.
	 * <p>If this registrar has been configured with a {@link ResourcePatternResolver},
	 * a ResourceArrayPropertyEditor will be registered as well.
	 * @see org.springframework.core.io.ResourceEditor
	 * @see org.springframework.beans.propertyeditors.InputStreamEditor
	 * @see org.springframework.beans.propertyeditors.InputSourceEditor
	 * @see org.springframework.beans.propertyeditors.FileEditor
	 * @see org.springframework.beans.propertyeditors.URLEditor
	 * @see org.springframework.beans.propertyeditors.URIEditor
	 * @see org.springframework.beans.propertyeditors.ClassEditor
	 * @see org.springframework.beans.propertyeditors.ClassArrayEditor
	 * @see org.springframework.core.io.support.ResourceArrayPropertyEditor
	 */
	@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));
		}
	}

	/**
	 * Override default editor, if possible (since that's what we really mean to do here);
	 * otherwise register as a custom editor.
	 */
	private void doRegisterEditor(PropertyEditorRegistry registry, Class<?> requiredType, PropertyEditor editor) {
		if (registry instanceof PropertyEditorRegistrySupport) {
			((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor);
		}
		else {
			registry.registerCustomEditor(requiredType, editor);
		}
	}

}

Now, if we need to implement a custom attribute editor, how can we do it? For example, we now need to parse the string into our corresponding address. In the actual project requirements, for example, we need to convert the string into our localDate.

Here, we first need to make clear when these attribute editors are called. Let's take a look at the source code of the createbainstance method.

	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Resolve Class type of Bean
		Class<?> beanClass = resolveBeanClass(mbd, beanName);

		// If the Bean is not public and does not allow shared access, throw an exception directly
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}

		/**
		 * Create a bean instance through the specified callback method, spring 5 Methods added after version 0.
		 *  It can be extended by implementing beanfactoryprocessor interface, setting custom suppliers and instantiating objects through custom suppliers
		 */
		// For reference: [example code: com.wb.spring.supplier.supplierbeanfactoryprocessor]
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}

		/**
		 * If the current bean specifies the corresponding factory method, create the bean instance through the factory method
		 *   The bottom layer will get the factory method [static factory method | instantiation method] -- >, then parse the method input parameters -- >, and then execute a reflection call to create an instance -- > encapsulated as a wrapper object
		 *
		 *   fixme: The implementation of this method is about 230 lines of code
		 */
		if (mbd.getFactoryMethodName() != null) {
			// To create an instance through the factory method, please refer to: [example code: com.wb.spring.factorymethod.instancefactory method, com.wb.spring.factorymethod.StaticFactoryMethod]
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut when re-creating the same bean...
		boolean resolved = false;
		boolean autowireNecessary = false;
		// The prototype will take this branch when it is acquired many times
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				// Because a class may have multiple constructors, you need to determine the constructor to be called finally according to the parameters configured in the configuration file or the parameters passed in.
				// Because the judgment process will be compared, spring will cache the parsed and determined constructors into the resolvedconstructorfactorymethod field in BeanDefinition.
				// When the same is created next time, it is directly obtained from the cached value of the property resolvedconstructorfactorymethod in RootBeanDefinition to avoid re parsing
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					// Indicates whether the parameters of the constructor have been resolved properly
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		if (resolved) {
			// If the parameters of the constructor have been resolved properly
			if (autowireNecessary) {
				// The instance is created through the constructor
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				// Use the default bean object creation strategy to create bean objects * * * [policy design pattern is used here]***
				return instantiateBean(beanName, mbd);
			}
		}

		/** Infer the construction method and obtain the candidate constructor used to create the bean object */
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		// Obtained the constructor 𞓜 the injection mode is to use the constructor | the constructor with parameters is specified in the bean definition | the input parameter args [value list corresponding to the parameter list] when creating the bean object is not null
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			// Create bean objects using constructors and input parameters
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		/** Gets the default constructor specified to create the bean object */
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}

		/**
		 * If none of the above is true: there is no callback method to create the bean & & there is no factory method & & the parameters of the constructor are not resolved & & there is no pre specified default constructor
		 *   The default policy is used to create the bean object
		 */
		return instantiateBean(beanName, mbd);
	}

Then go to instantiateBean.

	protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
		try {
			Object beanInstance;
			final BeanFactory parent = this;
			if (System.getSecurityManager() != null) {
				beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
						getInstantiationStrategy().instantiate(mbd, beanName, parent),
						getAccessControlContext());
			}
			else {
				/**
				 * The default creation strategy is cglibsubclassing instantiationstrategy
				 *   That is, Cglib is used to create bean objects
				 */
				beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
			}
			/**
			 * Wrap the created bean object and assign the created beanInstance to the wrappedObject property of BeanWrapper
			 *  When obtaining, use BeanWrapper's getWrappedInstance [Object getWrappedInstance();] Get the real bean object
			 */
			BeanWrapper bw = new BeanWrapperImpl(beanInstance);
			/**
			 * Initialize the bean object after the wrapper
			 *   (1) Register a type converter to convert a value of a specific type to a desired type
			 *   (2) Register a customized Attribute Editor
			 */
			initBeanWrapper(bw);
			return bw;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
		}
	}

Then register the type converter in initBeanWrapper, convert the value of a specific type to a desired type, and register the customized Attribute Editor.

	protected void initBeanWrapper(BeanWrapper bw) {
		// Set the default type converter: used to convert some specific types of values to a desired type, such as the converter that converts the Date type of string to Date type
		bw.setConversionService(getConversionService());
		// Register a customized Attribute Editor
		registerCustomEditors(bw);
	}


	protected void registerCustomEditors(PropertyEditorRegistry registry) {
		PropertyEditorRegistrySupport registrySupport =
				(registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null);
		if (registrySupport != null) {
			registrySupport.useConfigValueEditors();
		}
		if (!this.propertyEditorRegistrars.isEmpty()) {
			for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
				try {
					registrar.registerCustomEditors(registry);
				}
				catch (BeanCreationException ex) {
					Throwable rootCause = ex.getMostSpecificCause();
					if (rootCause instanceof BeanCurrentlyInCreationException) {
						BeanCreationException bce = (BeanCreationException) rootCause;
						String bceBeanName = bce.getBeanName();
						if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) {
							if (logger.isDebugEnabled()) {
								logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() +
										"] failed because it tried to obtain currently created bean '" +
										ex.getBeanName() + "': " + ex.getMessage());
							}
							onSuppressedException(ex);
							continue;
						}
					}
					throw ex;
				}
			}
		}
		if (!this.customEditors.isEmpty()) {
			// Register custom value converters
			this.customEditors.forEach((requiredType, editorClass) ->
					registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
		}
	}

When initializing like this, our BeanWrapper contains our customized and system default property editors. When do we call the corresponding conversion method after having these property editors? In fact, it should be when the property is valued. Let's take a look at the applyPropertyValues source code of populateBean.

	protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
		// If the attribute value is empty, it is returned directly
		if (pvs.isEmpty()) {
			return;
		}
		if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {
			((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
		}

		MutablePropertyValues mpvs = null;
		List<PropertyValue> original;

		if (pvs instanceof MutablePropertyValues) {
			mpvs = (MutablePropertyValues) pvs;
			// Judge whether the attribute value needs type conversion
			if (mpvs.isConverted()) {
				// Shortcut: use the pre-converted values as-is.
				try {
					/** Directly fill in the attribute value, and call the setter method for assignment */
					bw.setPropertyValues(mpvs);
					return;
				}
				catch (BeansException ex) {
					throw new BeanCreationException(
							mbd.getResourceDescription(), beanName, "Error setting property values", ex);
				}
			}
			// Gets the collection of attribute values that need to be populated
			original = mpvs.getPropertyValueList();
		}
		else {
			original = Arrays.asList(pvs.getPropertyValues());
		}
		// Get custom attribute type converter
		TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;
		}
		BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

		// Create a deep copy, resolving any references for values.
		List<PropertyValue> deepCopy = new ArrayList<>(original.size());
		boolean resolveNecessary = false;
		for (PropertyValue pv : original) {
			// If the attribute value has been converted
			if (pv.isConverted()) {
				deepCopy.add(pv);
			}
			else {
				// Attribute name
				String propertyName = pv.getName();

				// Attribute value
				Object originalValue = pv.getValue();

				// Attribute value after parsing
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);

				// Attribute value after conversion. You can convert attribute values by customizing ConversionService
				Object convertedValue = resolvedValue;
				boolean convertible = bw.isWritableProperty(propertyName) &&
						!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
				if (convertible) {
					// If you need to convert attribute values, you can convert attribute values through the custom attribute editor CustomerEditor
					convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
				}
				// Possibly store converted value in merged bean definition,
				// in order to avoid re-conversion for every created bean instance.
				if (resolvedValue == originalValue) {
					if (convertible) {
						pv.setConvertedValue(convertedValue);
					}
					deepCopy.add(pv);
				}
				else if (convertible && originalValue instanceof TypedStringValue &&
						!((TypedStringValue) originalValue).isDynamic() &&
						!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
					pv.setConvertedValue(convertedValue);
					deepCopy.add(pv);
				}
				else {
					resolveNecessary = true;
					deepCopy.add(new PropertyValue(pv, convertedValue));
				}
			}
		}
		if (mpvs != null && !resolveNecessary) {
			mpvs.setConverted();
		}

		try {
			// Set attribute value
			bw.setPropertyValues(new MutablePropertyValues(deepCopy));
		}
		catch (BeansException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Error setting property values", ex);
		}
	}


	@Nullable
	private Object convertForProperty(
			@Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) {

		if (converter instanceof BeanWrapperImpl) {
			// Execute custom editor
			return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName);
		}
		else {
			PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
			MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
			return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam);
		}
	}

	@Nullable
	public Object convertForProperty(@Nullable Object value, String propertyName) throws TypeMismatchException {
		CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults();
		PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName);
		if (pd == null) {
			throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
					"No property '" + propertyName + "' found");
		}
		TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
		if (td == null) {
			td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
		}
		// Execute custom editor
		return convertForProperty(propertyName, null, value, td);
	}

	@Nullable
	protected Object convertForProperty(
			String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td)
			throws TypeMismatchException {

		// Execute custom editor
		return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
	}

	@Nullable
	private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue,
			@Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td)
			throws TypeMismatchException {

		Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
		try {
			// Execute custom editor
			return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
		}
		catch (ConverterNotFoundException | IllegalStateException ex) {
			PropertyChangeEvent pce =
					new PropertyChangeEvent(getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
			throw new ConversionNotSupportedException(pce, requiredType, ex);
		}
		catch (ConversionException | IllegalArgumentException ex) {
			PropertyChangeEvent pce =
					new PropertyChangeEvent(getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
			throw new TypeMismatchException(pce, requiredType, ex);
		}
	}

	@Nullable
	public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
			@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

		// Custom editor for this type?
		PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

		ConversionFailedException conversionAttemptEx = null;

		// No custom editor but custom ConversionService specified?
		ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
		if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
			TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
			if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
				try {
					return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
				}
				catch (ConversionFailedException ex) {
					// fallback to default conversion logic below
					conversionAttemptEx = ex;
				}
			}
		}

		Object convertedValue = newValue;

		// Value not of required type?
		if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
			if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
					convertedValue instanceof String) {
				TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
				if (elementTypeDesc != null) {
					Class<?> elementType = elementTypeDesc.getType();
					if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
						convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
					}
				}
			}
			if (editor == null) {
				editor = findDefaultEditor(requiredType);
			}
			// Execute custom editor
			convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
		}

		boolean standardConversion = false;

		if (requiredType != null) {
			// Try to apply some standard type conversion rules if appropriate.

			if (convertedValue != null) {
				if (Object.class == requiredType) {
					return (T) convertedValue;
				}
				else if (requiredType.isArray()) {
					// Array required -> apply appropriate conversion of elements.
					if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
						convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
					}
					return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
				}
				else if (convertedValue instanceof Collection) {
					// Convert elements to target type, if determined.
					convertedValue = convertToTypedCollection(
							(Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
					standardConversion = true;
				}
				else if (convertedValue instanceof Map) {
					// Convert keys and values to respective target type, if determined.
					convertedValue = convertToTypedMap(
							(Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
					standardConversion = true;
				}
				if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
					convertedValue = Array.get(convertedValue, 0);
					standardConversion = true;
				}
				if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
					// We can stringify any primitive value...
					return (T) convertedValue.toString();
				}
				else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
					if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
						try {
							Constructor<T> strCtor = requiredType.getConstructor(String.class);
							return BeanUtils.instantiateClass(strCtor, convertedValue);
						}
						catch (NoSuchMethodException ex) {
							// proceed with field lookup
							if (logger.isTraceEnabled()) {
								logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
							}
						}
						catch (Exception ex) {
							if (logger.isDebugEnabled()) {
								logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
							}
						}
					}
					String trimmedValue = ((String) convertedValue).trim();
					if (requiredType.isEnum() && trimmedValue.isEmpty()) {
						// It's an empty enum identifier: reset the enum value to null.
						return null;
					}
					convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
					standardConversion = true;
				}
				else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
					convertedValue = NumberUtils.convertNumberToTargetClass(
							(Number) convertedValue, (Class<Number>) requiredType);
					standardConversion = true;
				}
			}
			else {
				// convertedValue == null
				if (requiredType == Optional.class) {
					convertedValue = Optional.empty();
				}
			}

			if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
				if (conversionAttemptEx != null) {
					// Original exception from former ConversionService call above...
					throw conversionAttemptEx;
				}
				else if (conversionService != null && typeDescriptor != null) {
					// ConversionService not tried before, probably custom editor found
					// but editor couldn't produce the required type...
					TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
					if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
						return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
					}
				}

				// Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
				StringBuilder msg = new StringBuilder();
				msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
				msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
				if (propertyName != null) {
					msg.append(" for property '").append(propertyName).append("'");
				}
				if (editor != null) {
					msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
							"] returned inappropriate value of type '").append(
							ClassUtils.getDescriptiveType(convertedValue)).append("'");
					throw new IllegalArgumentException(msg.toString());
				}
				else {
					msg.append(": no matching editors or conversion strategy found");
					throw new IllegalStateException(msg.toString());
				}
			}
		}

		if (conversionAttemptEx != null) {
			if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
				throw conversionAttemptEx;
			}
			logger.debug("Original ConversionService attempt failed - ignored since " +
					"PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
		}

		return (T) convertedValue;
	}

	@Override
	@Nullable
	public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
		Class<?> requiredTypeToUse = requiredType;
		if (propertyPath != null) {
			if (this.customEditorsForPath != null) {
				// Check property-specific editor first.
				PropertyEditor editor = getCustomEditor(propertyPath, requiredType);
				if (editor == null) {
					List<String> strippedPaths = new ArrayList<>();
					addStrippedPropertyPaths(strippedPaths, "", propertyPath);
					for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editor == null;) {
						String strippedPath = it.next();
						editor = getCustomEditor(strippedPath, requiredType);
					}
				}
				if (editor != null) {
					return editor;
				}
			}
			if (requiredType == null) {
				requiredTypeToUse = getPropertyType(propertyPath);
			}
		}
		// No property-specific editor -> check type-specific editor.
		return getCustomEditor(requiredTypeToUse);
	}

	@Nullable
	private PropertyEditor getCustomEditor(@Nullable Class<?> requiredType) {
		if (requiredType == null || this.customEditors == null) {
			return null;
		}
		// Check directly registered editor for type.
		PropertyEditor editor = this.customEditors.get(requiredType);
		if (editor == null) {
			// Check cached editor for type, registered for superclass or interface.
			if (this.customEditorCache != null) {
				editor = this.customEditorCache.get(requiredType);
			}
			if (editor == null) {
				// Find editor for superclass or interface.
				for (Iterator<Class<?>> it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) {
					Class<?> key = it.next();
					if (key.isAssignableFrom(requiredType)) {
						editor = this.customEditors.get(key);
						// Cache editor for search type, to avoid the overhead
						// of repeated assignable-from checks.
						if (this.customEditorCache == null) {
							this.customEditorCache = new HashMap<>();
						}
						this.customEditorCache.put(requiredType, editor);
					}
				}
			}
		}
		return editor;
	}

	@Nullable
	private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
			@Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {

		Object convertedValue = newValue;

		if (editor != null && !(convertedValue instanceof String)) {
			// Not a String -> use PropertyEditor's setValue.
			// With standard PropertyEditors, this will return the very same object;
			// we just want to allow special PropertyEditors to override setValue
			// for type conversion from non-String values to the required type.
			try {
				editor.setValue(convertedValue);
				Object newConvertedValue = editor.getValue();
				if (newConvertedValue != convertedValue) {
					convertedValue = newConvertedValue;
					// Reset PropertyEditor: It already did a proper conversion.
					// Don't use it again for a setAsText call.
					editor = null;
				}
			}
			catch (Exception ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
				}
				// Swallow and proceed.
			}
		}

		Object returnValue = convertedValue;

		if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
			// Convert String array to a comma-separated String.
			// Only applies if no PropertyEditor converted the String array before.
			// The CSV String will be passed into a PropertyEditor's setAsText method, if any.
			if (logger.isTraceEnabled()) {
				logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
			}
			convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
		}

		if (convertedValue instanceof String) {
			if (editor != null) {
				// Use PropertyEditor's setAsText in case of a String value.
				if (logger.isTraceEnabled()) {
					logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]");
				}
				String newTextValue = (String) convertedValue;
				// Custom Edit
				return doConvertTextValue(oldValue, newTextValue, editor);
			}
			else if (String.class == requiredType) {
				returnValue = convertedValue;
			}
		}

		return returnValue;
	}

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












The above is how the Spring Attribute Editor calls our custom attribute editor. First find the corresponding attribute editor to the last call. The source code of this piece is deep. You can debug and follow it.

Well, finally, we're going to implement our custom attribute editing.

3, Custom attribute editor

First, we have an address and a customer class, as follows:

public class Address {
	private String district;
	private String city;
	private String province;

	public String getDistrict() {
		return district;
	}

	public void setDistrict(String district) {
		this.district = district;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getProvince() {
		return province;
	}

	public void setProvince(String province) {
		this.province = province;
	}

	public String toString() {
		return this.province + "province" + this.city + "city" + this.district + "area";
	}
}

public class Customer {
	private String name;
	private Address address;

	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;
	}

}

Then there is a property editor that inherits our ProperEditorSupport interface.

/**
 * @author maoqichuan
 * @date 2022 16:11, February 21
 */
public class AddressProperEditor  extends PropertyEditorSupport {
	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		try {
			String[] adds = text.split("-");
			Address address = new Address();
			address.setProvince(adds[0]);
			address.setCity(adds[1]);
			address.setDistrict(adds[2]);
			this.setValue(address);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

With a custom attribute editor, we still need a registrar, as follows:

/**
 * @author maoqichuan
 * @date 2022 16:17, February 21
 */
public class AddressProperEditorRegistrar implements PropertyEditorRegistrar {
	@Override
	public void registerCustomEditors(PropertyEditorRegistry registry) {
		registry.registerCustomEditor(Address.class,new AddressProperEditor());
	}
}

Finally, the propertyeditor XML file.

<?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="customer" class="com.mashibing.propertyEditor.Customer">
        <property name="name" value="Jack" />
        <property name="address" value="Zhejiang-Hangzhou-West Lake" />
    </bean>
    <!--The first way-->
    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="propertyEditorRegistrars">
            <list>
                <bean class="com.mqc.selfpropertyeditor.AddressProperEditorRegistrar"></bean>
            </list>
        </property>
    </bean>
    <!--The second way-->
    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <entry key="com.mqc.selfpropertyeditor.Address">
                    <value>com.mqc.selfpropertyeditor.AddressProperEditor</value>
                </entry>
            </map>
        </property>
    </bean>
</beans>

Keywords: Spring

Added by Tom_LR on Mon, 21 Feb 2022 10:48:32 +0200