本文基于 spring 4.3.13 版本
在项目中我们经常使用 spring 提供的 IOC 功能,目前主要有两种方式:xml、注解,而这两种方式的原理是不同的,xml 的注入主要依赖 BeanWrapperImpl
进行属性注入,而注解是依赖 BeanPostProcessor
进行注入。在使用 IOC 功能的时候,经常需要利用 spring 提供的类型转换功能,比如 String -> Integer
、String -> Date
、String -> Resource
。那么 spring 又是如何实现的,我们来分析一波吧。
ConfigurableBeanFactory
是 BeanFactory 的子类,提供了包括支持类型转换功能的一系列配置接口,下面的代码列举了类型转换相关的接口。
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
void setConversionService(ConversionService conversionService);
ConversionService getConversionService();
void addPropertyEditorRegistrar(PropertyEditorRegistrar registrar);
void registerCustomEditor(Class requiredType, Class propertyEditorClass);
void setTypeConverter(TypeConverter typeConverter);
TypeConverter getTypeConverter();
// other code......
}
其中 TypeConverter
在 2.5+
版本提供,默认使用 SimpleTypeConverter
实现,底层基于 jdk 提供的 PropertyEditor
,只能实现 String 与 Object 的相互转换,由于 spring 创建 bean 的时候,配置信息大多是 String 类型,因此满足大部分的类型转换场景。但是这种弊端很明显,只能满足 String 与 Object 的相互转换,因此 spring3.x 引入了 Converter
TypeConverter
提供了类型转换的接口,提供了字段、方法参数的转换接口,接口定义如下所示:
package org.springframework.beans;
import java.lang.reflect.Field;
import org.springframework.core.MethodParameter;
public interface TypeConverter {
T convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException;
T convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException;
T convertIfNecessary(Object value, Class requiredType, Field field) throws TypeMismatchException;
}
spring 默认使用 SimpleTypeConverter
实现,继承关系如下图所示:
SimpleTypeConverter
继承至 PropertyEditorRegistrySupport
,内置了几十种 java.beans.PropertyEditor
(属性编辑器),部分代码如下所示。获取的时候,根据目标类型即可确定所需要的 PropertyEditor
,例如:我需要注入 File 对象,那么根据 File.class 即可找到对应的 FileEditor
private void createDefaultEditors() {
this.defaultEditors = new HashMap, PropertyEditor>(64);
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());
if (pathClass != null) {
this.defaultEditors.put(pathClass, new PathEditor());
}
this.defaultEditors.put(Pattern.class, new PatternEditor());
// other code......
}
package org.springframework.core.convert.converter;
public interface Converter {
T convert(S source);
}
此外,spring 还提供了 GenericConverter、ConditionalConverter,利用 TypeDescriptor 对转换的类型进行了封装,功能更加强大
public interface GenericConverter {
Set getConvertibleTypes(); // 支持转换的类型对
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); // 调用该接口进行对象转换
}
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
Formatter 接口用于实现 Object <-> String
的相互转换。接口如下图所示,其中 Printer 用于将 Object 转成 String,而 Parser 用于将 String 转成 Object。这和 jdk 提供的 PropertyEditor 有些类似,但是比 jdk 更加方便,更加轻量级
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
ConversionService
是一个用于类型转换的接口,代码如下所示:
public interface ConversionService {
boolean canConvert(Class sourceType, Class targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
T convert(Object source, Class targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
默认实现是 spring-context
包的 org.springframework.format.support.FormattingConversionService
,使用了策略模式,将具体的类型转换请求,交给对应的 GenericConverter 进行类型转换。FormattingConversionService 支持添加自定义的 Converter 和 Formatter,如果 Converter 不是 GenericConverter 接口的实现类,则会使用 ConverterAdapter 适配器将 Converter 转换成 GenericConverter 实现类;添加 Formatter 也是如此。
public class FormattingConversionService extends GenericConversionService
implements FormatterRegistry, EmbeddedValueResolverAware {
public void addFormatter(Formatter formatter) {
Class fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
if (fieldType == null) {
throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" +
formatter.getClass().getName() + "]; does the formatter parameterize the generic type?" );
}
addFormatterForFieldType(fieldType, formatter);
}
// 添加 Object -> String、String -> Object 类型转换器
public void addFormatterForFieldType(Class fieldType, Formatter formatter) {
addConverter(new PrinterConverter(fieldType, formatter, this));
addConverter(new ParserConverter(fieldType, formatter, this));
}
}
public class GenericConversionService implements ConfigurableConversionService {
public void addConverter(Converter converter) {
GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class);
Assert.notNull(typeInfo, "Unable to the determine sourceType and targetType " +
" which your Converter converts between; declare these generic types." );
addConverter(new ConverterAdapter(typeInfo, converter));
}
}
在进行源码分析之前,我们先来看一下例子,代码如下所示:
"net.dwade.spring.boot.PropertyEditorTestBean">
"date" value="2018-02-18 11:00:00" />
public class PropertyEditorTestBean {
private Date date;
public void setDate(Date date) {
this.date = date;
}
@PostConstruct
public void init() {
System.out.println( date );
}
}
在没有额外设置的情况下,直接抛出异常了,异常堆栈如下所示:
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Date' for property 'date': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:306) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:588) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:615) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:216) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1577) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1536) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1276) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
怎么解决上面这个问题呢?结合前面的介绍,有很多种方案,个人列举以下解决方案:
ConfigurableBeanFactory
指定 ConversionService
,怎么指定?利用 spring 的 BeanFactoryPostProcessor
接口,默认情况下是 null
。废话不多说,上代码:public class MyBeanFactoryProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
FormattingConversionService service = new FormattingConversionService();
service.addConverter( new StringToDateConverter() );
beanFactory.setConversionService( service );
}
}
public class StringToDateConverter implements Converter<String, Date> {
private String[] patterns = {"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss.SSS"};
@Override
public Date convert(String source) {
if ( StringUtils.isEmpty( source ) ) {
return null;
}
for ( String pattern : patterns ) {
try {
return new SimpleDateFormat( pattern ).parse( source );
} catch (Exception e) {
// ignore exception, do next pattern parse.
}
}
return null;
}
}
简单的解释下,MyBeanFactoryProcessor
实现了 BeanFactoryPostProcessor
接口,可以为 ConfigurableBeanFactory
设置 ConversionService
,用于添加类型转换器 StringToDateConverter
,实现 String -> java.util.Date 类型转换。关于 BeanFactoryPostProcessor
,不清楚的童鞋请百度 / google。
org.springframework.beans.factory.config.CustomEditorConfigurer
添加 java.beans.PropertyEditor
,这种方法稍微麻烦点,主要的思路就是往 ConfigurableBeanFactory
添加自定义的 PropertyEditor,下面给出注解的配置,当然也可以利用 xml 往 spring 容器中配置 CustomEditorConfigurer@Bean
public CustomEditorConfigurer customEditorConfigurer() {
CustomEditorConfigurer editorConfig = new CustomEditorConfigurer();
PropertyEditorRegistrar registrar = new PropertyEditorRegistrar(){
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor( Date.class, new CustomDateEditor(new SimpleDateFormat( "yyyy-MM-dd" ), true) );
}};
editorConfig.setPropertyEditorRegistrars( new PropertyEditorRegistrar[]{registrar} );
return editorConfig;
}
spring 在创建 Bean 的时候,首先会使用 BeanWrapper 接口进行属性注入,默认使用 BeanWrapperImpl,BeanWrapper 接口继承至 TypeConverter,说明 BeanWrapper 提供了类型转换的功能,类图如下所示:
为了方便理解后续的类型转换原理,我们有必要了解下 BeanWrapper 对象的创建过程:
1. 利用实例化的 bean 对象构建 BeanWrapperImpl
2. 为 BeanWrapper 设置 ConversionService,默认情况下 ConfigurableBeanFactory 不会创建 ConversionService,因此 BeanWrapperImpl 中该对象为 null
3. 添加 PropertyEditor,可以通过 ConfigurableBeanFactory#addPropertyEditorRegistrar(PropertyEditorRegistrar registrar)
、ConfigurableBeanFactory#registerCustomEditor(Class
AbstractAutowireCapableBeanFactory.java
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
// 利用 InstantiationStrategy 实例化对象
Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
BeanWrapper bw = new BeanWrapperImpl(beanInstance); // 创建 BeanWrapperImpl
initBeanWrapper(bw); // 使用 ConversionService 初始化 BeanWrapper
return bw;
}
AbstractBeanFactory.java
protected void initBeanWrapper(BeanWrapper bw) {
bw.setConversionService(getConversionService()); // 获取 ConfigurableBeanFactory 的 ConversionService
registerCustomEditors(bw); // 根据 ConfigurableBeanFactory 设置的 PropertyEditorRegistrar、customEditors 注册 PropertyEditor
}
首先,我们来看下调用栈:
spring 在实例化 bean 之后,会进行属性注入,这个时候需要调用 BeanWrapper 提供的类型转换接口,实际上是调用父类 AbstractNestablePropertyAccessor#convertIfNecessary()
方法,代码如下:
protected Object convertForProperty(String propertyName, Object oldValue, Object newValue, TypeDescriptor td)
throws TypeMismatchException {
return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
}
private Object convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class requiredType,
TypeDescriptor td) throws TypeMismatchException {
return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
}
具体的转换逻辑是交给 TypeConverterDelegate
进行处理的(估且把它理解成工具类),看到这里,比较好奇的是这个 TypeConverterDelegate
对象是如何创建的,我们先看一下 BeanWrapperImpl 的继承关系图:
原来在父类 AbstractNestablePropertyAccessor
的构造方法中创建的,并且传入了 BeanWrapperImpl
对象
protected AbstractNestablePropertyAccessor() {
this(true);
}
protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) {
if (registerDefaultEditors) {
registerDefaultEditors();
}
this.typeConverterDelegate = new TypeConverterDelegate(this);
}
public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry) {
this(propertyEditorRegistry, null);
}
public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry, Object targetObject) {
this.propertyEditorRegistry = propertyEditorRegistry;
this.targetObject = targetObject;
}
OK,看到这里就清楚了,转换逻辑是由 TypeConverterDelegate#convertIfNecessary
方法完成的,代码比较长,老夫就不贴代码了,请结合下面的流程图加以理解。首先会查找自定义的 PropertyEditor
,如果有则直接进行类型转换,将 String 转成对应的类型;如果没有支持的 PropertyEditor
,并且 ConversionService
不为 null
,则尝试使用 ConversionService
进行类型转换。此时,如果仍然没有转换成功,则尝试查找内置的 PropertyEditor
,并且由 PropertyEditorRegistrySupport#createDefaultEditors()
注册内置的 PropertyEditor
以支持类型转换,这个方法里面涵括了 spring 默认支持的类型转换,包括 String -> File,String -> Resource,String -> Class 等等,具体请参考官方文档。
下图描述了 spring 创建 bean,以及利用 BeanWrapper 进行 IOC 注入的主要逻辑(查看原图):
如上图所示,在 spring 实例化 bean 之后,便会根据 BeanDefinition
定义的 PropertyValues
进行属性注入。一般而言,除了 spring 或者框架内置的 BeanDefinition
,PropertyValues
大多是空的,如果是从 xml 获取的 BeanDefinition
一般是存在 PropertyValues
的,而注解注入是不存在的(注入的原理不一样)。在进行类型转换之前,需要对定义的值进行解析,比如 SpEL
表达式;然后把属性值交给 BeanWrapper
进行转换,把转换之后的值通过 setter
方法给 bean
赋值,从而完成注入功能。
有一点需要补充的是,TypeConverterDelegate 在查找内置的 PropertyEditor 时,如果不存在对应的 PropertyEditor,则会根据 SPI 查找 PropertyEditor。比如,”mypackage.MyDomainClass” -> “mypackage.MyDomainClassEditor” 类型转换,则会尝试根据 “类名 + Editor” 加载 PropertyEditor 对应的 class,代码如下所示:
TypeConverterDelegate.java
private PropertyEditor findDefaultEditor(Class requiredType) {
PropertyEditor editor = null;
if (requiredType != null) {
// 查找内置的 PropertyEditor
editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
if (editor == null && String.class != requiredType) {
// 尝试获取 requiredType 的全类名 + Editor(SPI)
editor = BeanUtils.findEditorByConvention(requiredType);
}
}
return editor;
}
由上面可知,TypeConverterDelegate 在类型转换过程中起到很关键的作用,我们有必要聊一聊这个 TypeConverterDelegate。它没有实现任何接口,并且内部持有 PropertyEditorRegistrySupport 的引用(实际上是 BeanWrapperImpl 对象),代码如下所示:
class TypeConverterDelegate {
private final PropertyEditorRegistrySupport propertyEditorRegistry;
private final Object targetObject;
public T convertIfNecessary(Object newValue, Class requiredType, MethodParameter methodParam) throws IllegalArgumentException { //...... }
public T convertIfNecessary(Object newValue, Class requiredType, Field field) throws IllegalArgumentException { //...... }
public T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class requiredType) throws IllegalArgumentException { //...... }
public T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
//......
}
private PropertyEditor findDefaultEditor(Class requiredType) { //...... }
private Object doConvertValue(Object oldValue, Object newValue, Class requiredType, PropertyEditor editor) { //...... }
}
实际上,它代理了 BeanWrapper 的某些方法,比如查找 PropertyEditor,并且对 BeanWrapper 的转换逻辑进行了增强,应该是 代理 + 装饰
模式的结合体。TypeConverterDelegate 的类型转换逻辑,比 BeanWrapperImpl 更加复杂,增加了很多功能,比如支持 Collection、Map、数组
等类型的转换,还增加了 ConversionService
的支持
前面我们分析了基于 xml 的类型转换,主要依赖 BeanWrapper,但是基于注解的IOC注入,是基于 BeanPostProcessor 完成的,现在我们来看看 spring 是如何完成类型转换的。还是刚才的例子,只不过我们换成了注解,利用注解的方式为 Test 注入 java.util.Date 对象。
Test.java
@Component
public class Test {
@Value("2017-02-20 14:00:00")
private Date date;
@PostConstruct
public void init() {
System.out.println( date );
}
}
我们先来了解下 spring 注解注入的基本原理,核心思想是基于 InstantiationAwareBeanPostProcessor
后置处理器( BeanPostProcessor 的子类),在 bean 实例化之后、bean 初始化之前,获取 bean 对象的 @Resource
、@Autowired
、@Qualifier
、@Value
等注解,并且获取需要注入的值(包括依赖的对象,或者数据),使用反射的手段为 bean 设置属性值。大致原理就是这样,但是实现上面很复杂,比如要考虑如何支持 SpEL
,支持动态代理、类型转换,等等。由于篇幅有限,这里只简述下基本原理。
下面列出了相关的源码,感兴趣的童鞋可以进入深入分析:
- AutowiredAnnotationBeanPostProcessor:位于 spring-beans 模块,用于处理 @Autowired、@Value 注解注入
- CommonAnnotationBeanPostProcessor:位于 spring-context 模块,用于处理 @Resource、@EJB 注解注入
首先,我们来看一下方法调用栈,如下图所示:
AutowiredAnnotationBeanPostProcessor 实现了 InstantiationAwareBeanPostProcessor 接口,重写了 postProcessPropertyValues 方法,读取 @Autowired、@Value 注解,把这些注解封装成 InjectionMetadata 对象,然后调用其 inject 方法进行属性注入,而 InjectionMetadata 会遍历内部的 InjectedElement 对象(包括 Field、Method 实现的),并对其进行注入,由于我们这个 @Value 注解是标注在字段上面的,因此是 AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement 实现。注入的过程分为两步,首先,根据已有的条件获取依赖的对象,这一步是由 DefaultListableBeanFactory 完成,转换的逻辑就是在这一步完成的;然后,反射调用字段,或者方法,为 bean 赋值,从而完成注入的逻辑。关键代码如下所示:
AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement.java
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) { // 直接从缓存中取出数据
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set autowiredBeanNames = new LinkedHashSet(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
// 将依赖注入的 bean 放入 BeanFactory 的缓存中,并设置为已缓存,方便后续取用
}
}
if (value != null) { // 反射为属性赋值
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
由上面的分析可知,对注入的 value 进行解析的逻辑是交给 BeanFactory 完成的,下面我们分析下具体的实现
BeanFactory 会解析其依赖注入的值,首先需要处理原始值,因为可能会包括 SpEL 表达式;然后获取 TypeConverter 对象,并进行类型转换,返回对应类型的值。看到这里便很清晰了,对注解的类型转换仍然是交给 TypeConverter,但是与 xml 的注入不一样的是,xml 拿到的 TypeConverter 就是 BeanWrapper 自身,而注解默认是 SimpleTypeConverter。不管是 SimpleTypeConverter,还是 BeanWrapperImpl,具体的转换逻辑都是交给 TypeConverterDelegate 处理的,请参考前面的内容
DefaultListableBeanFactory.java
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Class type = descriptor.getDependencyType();
// 处理 SpEL 表达式
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
// 获取 TypeConverter,并对 value 值进行类型转换
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
// 省略若干代码
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
我们注意到 DefaultListableBeanFactory
对象的 typeConverter
默认是 null
,因此默认返回 SimpleTypeConverter
,我们看下这个 getTypeConverter
方法便一目了然,如果我们希望添加自己的类型转换器,可以为 ConfigurableBeanFactory
指定 TypeConverter
,从而达到相同的目的。
@Override
public TypeConverter getTypeConverter() {
TypeConverter customConverter = getCustomTypeConverter();
if (customConverter != null) {
return customConverter;
}
else { // 创建 SimpleTypeConverter 对象,并且指定 ConverionService 和 PropertyEditor
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
typeConverter.setConversionService(getConversionService());
registerCustomEditors(typeConverter);
return typeConverter;
}
}
protected TypeConverter getCustomTypeConverter() {
return this.typeConverter;
}