beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); //接着进入ResourceEditorRegistrar 其中this是resourceLoader getEnvironment()返回的是 ConfigurableEnvironment //查看resourceLoader 到底是干嘛用的,根据注释可看成这个接口主要主要提供加载资源,class path或者file system等方式 //那么这里我们具体使用的是什么方式呢? 就需要去查看我们的当前容器的具体实现类的具体实现方法 Resource getResource(String location) //以下是我们的接口源码 /** * Strategy interface for loading resources (e.. class path or file system * resources). An {@link org.springframework.context.ApplicationContext} * is required to provide this functionality, plus extended * {@link org.springframework.core.io.support.ResourcePatternResolver} support. * *
{@link DefaultResourceLoader} is a standalone implementation that is * usable outside an ApplicationContext, also used by {@link ResourceEditor}. * *
Bean properties of type Resource and Resource array can be populated * from Strings when running in an ApplicationContext, using the particular * context's resource loading strategy. * * @author Juergen Hoeller * @since 10.03.2004 * @see Resource * @see org.springframework.core.io.support.ResourcePatternResolver * @see org.springframework.context.ApplicationContext * @see org.springframework.context.ResourceLoaderAware */ public interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:" */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; /** * Return a Resource handle for the specified resource location. *
The handle should always be a reusable resource descriptor, * allowing for multiple {@link Resource#getInputStream()} calls. *
*
*- Must support fully qualified URLs, e.g. "file:C:/test.dat". *
- Must support classpath pseudo-URLs, e.g. "classpath:test.dat". *
- Should support relative file paths, e.g. "WEB-INF/test.dat". * (This will be implementation-specific, typically provided by an * ApplicationContext implementation.) *
Note that a Resource handle does not imply an existing resource; * you need to invoke {@link Resource#exists} to check for existence. * @param location the resource location * @return a corresponding Resource handle (never {@code null}) * @see #CLASSPATH_URL_PREFIX * @see Resource#exists() * @see Resource#getInputStream() */ Resource getResource(String location); /** * Expose the ClassLoader used by this ResourceLoader. *
Clients which need to access the ClassLoader directly can do so * in a uniform manner with the ResourceLoader, rather than relying * on the thread context ClassLoader. * @return the ClassLoader * @see org.springframework.util.ClassUtils#getDefaultClassLoader() */ @Nullable ClassLoader getClassLoader(); }
分析以下它的接口继承关系如下图,顶层接口为propertyResolver,通过源码可知该,接口用于根据任何底层源解析属性。而具体的实现类是使用的StandardEnvironment
/**
* Interface for resolving properties against any underlying source.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see Environment
* @see PropertySourcesPropertyResolver
*/
public interface PropertyResolver {
/**
* Return whether the given property key is available for resolution,
* i.e. if the value for the given key is not {@code null}.
*/
boolean containsProperty(String key);
/**
* Return the property value associated with the given key,
* or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @see #getProperty(String, String)
* @see #getProperty(String, Class)
* @see #getRequiredProperty(String)
*/
@Nullable
String getProperty(String key);
/**
* Return the property value associated with the given key, or
* {@code defaultValue} if the key cannot be resolved.
* @param key the property name to resolve
* @param defaultValue the default value to return if no value is found
* @see #getRequiredProperty(String)
* @see #getProperty(String, Class)
*/
String getProperty(String key, String defaultValue);
/**
* Return the property value associated with the given key,
* or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @see #getRequiredProperty(String, Class)
*/
@Nullable
T getProperty(String key, Class targetType);
/**
* Return the property value associated with the given key,
* or {@code defaultValue} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @param defaultValue the default value to return if no value is found
* @see #getRequiredProperty(String, Class)
*/
T getProperty(String key, Class targetType, T defaultValue);
/**
* Return the property value associated with the given key (never {@code null}).
* @throws IllegalStateException if the key cannot be resolved
* @see #getRequiredProperty(String, Class)
*/
String getRequiredProperty(String key) throws IllegalStateException;
/**
* Return the property value associated with the given key, converted to the given
* targetType (never {@code null}).
* @throws IllegalStateException if the given key cannot be resolved
*/
T getRequiredProperty(String key, Class targetType) throws IllegalStateException;
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value are ignored and passed through unchanged.
* @param text the String to resolve
* @return the resolved String (never {@code null})
* @throws IllegalArgumentException if given text is {@code null}
* @see #resolveRequiredPlaceholders
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String)
*/
String resolvePlaceholders(String text);
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
* property values as resolved by {@link #getProperty}. Unresolvable placeholders with
* no default value will cause an IllegalArgumentException to be thrown.
* @return the resolved String (never {@code null})
* @throws IllegalArgumentException if given text is {@code null}
* or if any placeholders are unresolvable
* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, boolean)
*/
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
由上一篇文章源码可知,往容器里面设置了一个 ResourceEditorRegistrar 这个类,我们把他称作属性编辑注册商。其源码如下
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
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. *
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); } } }
接着我们看什么地方调用了它,看下面的调用图,可以发现是在AbstractBeanFactory.doGetBean 这里进行了调用。这个方法是我们在进行从容器中或者bean是会调用方法。也就是说明容器在refresh()的时候只是设置了一个默认的属性编辑注册商。
这个注册商里面有许多自带的属性编辑器。而真正执行把属性编辑器注册进容器的地方是,在我们往容器获取bean对象,时才进行的注册。
我们接着往下看,既然是一个属性编辑器,那Spring是否给我们提供了扩展功能,也就是我们可以自己定义一个属性编辑器,然后将我们的编辑器也给AbstractBeanFactory.doGetBean这个方法调用呢?
答案是Spring支持自定义属性编辑器,我们可以通过继承PropertyEditorSupport这个类。这个类我们怎么知道的呢,其实在属性编辑注册商ResourceEditorRegistrar,这个类里面的这个方法registerCustomEditors当中就有许多的定义好的属性编辑器,他们的父类都是PropertyEditorSupport。所以我们定义一个属性编辑器,然后实现这个接口中的方法即可。 现在我们写好了自定义的属性编辑器,接下来我们需要将这个编辑器registry.registerCustomEditor(requiredType, editor)像这样将它设置进去,然后让AbstractBeanFactory.doGetBean这个方法进行调用。
这里其实Spring为我们做好扩展类的org.springframework.beans.factory.config.CustomEditorConfigurer 。这个类里面有个方法 postProcessBeanFactory。这个方法调用图如下,可以发现是在refresh中调用的。
我们接着分析以下CustomEditorConfigurer这个类源码如下,可以发现postProcessBeanFactory方法有两种方式,我们先看简单的一种,也就是我们自定义属性编辑器,只需要将它注入到customEditors这个属性中即可
第二种方式也就是我们自定义一个属性编辑注册商(需要和Spring默认的注册商继承同一个类或者接口),将他注入propertyEditorRegistrars这个属性中即可。
public class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered { protected final Log logger = LogFactory.getLog(getClass()); private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered @Nullable private PropertyEditorRegistrar[] propertyEditorRegistrars; @Nullable private Map
, Class> customEditors; public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return this.order; } /** * Specify the {@link PropertyEditorRegistrar PropertyEditorRegistrars} * to apply to beans defined within the current application context. *
This allows for sharing {@code PropertyEditorRegistrars} with * {@link org.springframework.validation.DataBinder DataBinders}, etc. * Furthermore, it avoids the need for synchronization on custom editors: * A {@code PropertyEditorRegistrar} will always create fresh editor * instances for each bean creation attempt. * @see ConfigurableListableBeanFactory#addPropertyEditorRegistrar */ public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) { this.propertyEditorRegistrars = propertyEditorRegistrars; } /** * Specify the custom editors to register via a {@link Map}, using the * class name of the required type as the key and the class name of the * associated {@link PropertyEditor} as value. * @see ConfigurableListableBeanFactory#registerCustomEditor */ public void setCustomEditors(Map
, Class> customEditors) { this.customEditors = customEditors; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (this.propertyEditorRegistrars != null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar); } } if (this.customEditors != null) { this.customEditors.forEach(beanFactory::registerCustomEditor); } } }
接下来我们看一个有趣的事情,就是我们在往CustomEditorConfigurer这个对象里面设置进customEditors属性和propertyEditorRegistrars属性的时候。我们都会先创建一个CustomEditorConfigurer对象。然后再设置这两属性进去。
那么spring是怎么发现我们创建的这个CustomEditorConfigurer对象的。通过调试我们最终发现底层这个方法可以根据接口来获取得到他的实现类的名称,前提需要实现类的对象注入到容器当中。然后容器根据这个名称来获得这个对象。从而动态的调用。
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);