Spring 源码导读

做为Java开源世界的第一框架,Spring已经成为事实上的Java EE开发标准Spring框架最根本的使命是简化Java开发,因此学习、研究、掌握Spring框架成为每一位Java开发人员的必修课。而阅读源码则是学习Spring的最好方式之一。

Spring 里面最重要的特性就是 Ioc,可能你还会说 aop。其实 aop 的实现也是基于 ioc。Ioc (Inversion of Control),即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

关于 Spring IOC 源码分析的文章网上很多,现在我就来另辟蹊径。Spring Ioc 的对象扭转以及 涉及到的核心接口来分析一下它的源码实现。

我把 Spring Ioc 的对象转换分为以下 4 个步骤:

Resource -> BeanDefinition -> BeanWrapper -> Object

1 Resource

Resouce 其实是一个接口,代表的是资源,在计算机里面从一个地方移动到另外一个地方所需要的东西就是数据流,所以 Resource 实现了 InputStreamSource 接口,通过 InputStreamSource 接口可以获取到 Inputstream,这样就可以读取不同的 Bean 定义了。

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}

Spring 可以定义不同类型的 bean,最后都可以封装成 Resource 通过 IO 流进行读取。 Spring 可以定义类型的 bean 对象:

  • XML:这是 Spring 最开始定义 bean 的形式
  • Annotation :由于通过 XML 定义 bean 的繁琐,Spring 进行了改进可以通过 @Component 以及基于它的注解来定义 bean。例如:@Service,@Controller等等,它们都可以定义 bean ,只不过语义更加明确。
  • Class:通过 @Configuration 与 @Bean 注解定义,@Configuration 代理 xml 资源文件,而 @Bean 代替 <bean> 标签。
  • Properties/yml:通过 @EnableConfigurationProperties 与 @ConfigurationProperties 来定义 bean。这种形式在 Spring boot 自动注入里面大量使用。

2、BeanDefinition

望文生义,很显示这个是 Bean 对象的定义。 Spring 通过不同形式来定义 bean,最终会把这些定义转化成 BeanDefinition 保存在 Spring 容器当中进行依赖注入。下面我们来看一下 BeanDefinition 的接口定义。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

    ConstructorArgumentValues getConstructorArgumentValues();

    MutablePropertyValues getPropertyValues();
    
    ...
}

这个接口的定义很复杂但是,对于初始理解 spring ioc,只需要关心两个方法。

  • getConstructorArgumentValues:获取构造器注入定义的参数。
  • getPropertyValues:获取 setter 注入定义的参数。

所以 Spring 支持构造器注入与 setter 依赖注入。

1、构造器注入

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

2、setter注入

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

3、BeanWapper

其实什么是依赖注入,简单来说就是 Spring 帮我们创建对象。把创建对象写死在 Java 文件变成了通过不同的 Spring 配置可以注入不同的值。创建对象的职责由 Java 文件变成了 Spring 配置文件。

下面我就问一个简单的问题,如何创建对象。可能大家呵呵一笑,创建对象这还不简单。

1、无参构造器

Object obj = new Object();
obj.setXxx(xxx);

2、有参构造器

Object obj = new Object(xxx, yyyy);
obj.setXxx(xxx);

其实 Spring 也是这样来创建对象的,不信讲看 : (入口方法 BeanFactory#getBean)

  1. AbstractAutowireCapableBeanFactory#createBeanInstance() :通过反射 Constructor 调用配置的无参或者有参来创建对象实例。通过 BeanDefinition#getConstructorArgumentValues 获取,并返回 BeanWrapper 对象。
  2. AbstractAutowireCapableBeanFactory#populateBean():,获取到定义的 bean 生成的所有的定义的setter注入的属性(BeanDefinition#getPropertyValues),然后遍历些这些属性,通过内省获取到对象所有的 属性描述器(PropertyDescriptor),获取到,属性的 PropertyDescriptor#getWriteMethod 方法,也就是 setter 方法,依赖注入值。如果是普通属性或者一些复杂对象,比如普通属性 String, int, long 或者 classpath:*转换为 Resource 复杂对象等等,直接注入即可;对于引用类型对象,继续依赖注入直到所有的属性是普通属性为止。
  3. AbstractAutowireCapableBeanFactory#initializeBean():调用 Spring 自定义的初始化方法比如:BeanPostProcessor 扩展以及 init-method。

实例化对象返回 BeanWrapper,其实是为了依赖注入服务也就是上面的第二步。 这个接口的功能还是很复杂的,它继承了 4 个接口。

  • TypeConverter
  • PropertyEditorRegistry
  • PropertyAccessor
  • ConfigurablePropertyAccessor

下面就来分别介绍一下这些接口的功能。

3.1 TypeConverter

下面就是这个接口的定义。

public interface TypeConverter {

    <T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException;

    <T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam)
            throws TypeMismatchException;

    <T> T convertIfNecessary(Object value, Class<T> requiredType, Field field)
            throws TypeMismatchException;

}

它的作用就是自动类型转换,因为 Spring 作得太无感知了。你也许还没有感觉到它的存在。没关系,我提示一下你应该就会明白。比如,声明一个用户对象这个对象既有 String 类型的名称,又有 Int 类型的年龄。 Spring 怎么知道属性的类型呢?这个就是 Spring 的自动类型转换。关于 Spring 的自动类型转换 我在之前就已经分析过了。

3.2 PropertyEditorRegistry

这个接口主要的作用是注册属性修改器(PropertyEditor),这个是 Java 内省里面的机制。

public interface PropertyEditorRegistry {

    void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);

    void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor);

    PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath);

}

一般通过继承 java.beans.PropertyEditorSupport 来实现自定义的类型转换。在 Spring 内部有大量的实现,如下图所示:

这里写图片描述

3.3 PropertyAccessor

public interface PropertyAccessor {

    boolean isReadableProperty(String propertyName);

    boolean isWritableProperty(String propertyName);
cessor method failed

    Class<?> getPropertyType(String propertyName) throws BeansException;

    TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException;

    Object getPropertyValue(String propertyName) throws BeansException;

    void setPropertyValue(String propertyName, Object value) throws BeansException;

    void setPropertyValue(PropertyValue pv) throws BeansException;

    void setPropertyValues(Map<?, ?> map) throws BeansException;

    void setPropertyValues(PropertyValues pvs) throws BeansException;

    void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown)
            throws BeansException;

    void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
            throws BeansException;

}

PropertyAccessor 这个接口是判断对象中的某个属性是否可读/可写,并且可以定入或者读取某个属性的值。从这个接口定义我们可以看出,它的使用其实就是真正用来依赖注入的。然后调用属性操作的写入操作,完全依赖注入。

3.4 ConfigurablePropertyAccessor

public interface ConfigurablePropertyAccessor extends PropertyAccessor, PropertyEditorRegistry, TypeConverter {

    void setConversionService(ConversionService conversionService);

    ConversionService getConversionService();

    ...
    
}

这个接口的功能和 PropertyEditorRegistry 接口一样,只不过后者是通过 java 内省来进行类型自动转换,而 ConfigurablePropertyAccessor 接口是通过 Spring 自己定义的 org.springframework.core.convert.ConversionService 来作类型转换类型转换。在 Spring 中默认使用的是 DefaultConversionService 来作自动类型转换支持,并且内部还添加了很多默认的类型转换。

public class DefaultConversionService extends GenericConversionService {

    /** Java 8's java.util.Optional class available? */
    private static final boolean javaUtilOptionalClassAvailable =
            ClassUtils.isPresent("java.util.Optional", DefaultConversionService.class.getClassLoader());

    /** Java 8's java.time package available? */
    private static final boolean jsr310Available =
            ClassUtils.isPresent("java.time.ZoneId", DefaultConversionService.class.getClassLoader());

    /** Java 8's java.util.stream.Stream class available? */
    private static final boolean streamAvailable = ClassUtils.isPresent(
            "java.util.stream.Stream", DefaultConversionService.class.getClassLoader());

    public DefaultConversionService() {
        addDefaultConverters(this);
    }

    public static void addDefaultConverters(ConverterRegistry converterRegistry) {
        addScalarConverters(converterRegistry);
        addCollectionConverters(converterRegistry);

        converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
        if (jsr310Available) {
            Jsr310ConverterRegistrar.registerJsr310Converters(converterRegistry);
        }

        converterRegistry.addConverter(new ObjectToObjectConverter());
        converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
        converterRegistry.addConverter(new FallbackObjectToStringConverter());
        if (javaUtilOptionalClassAvailable) {
            converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
        }
    }

    public static void addCollectionConverters(ConverterRegistry converterRegistry) {
        ConversionService conversionService = (ConversionService) converterRegistry;

        converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
        converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));

        converterRegistry.addConverter(new ArrayToArrayConverter(conversionService));
        converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService));
        converterRegistry.addConverter(new MapToMapConverter(conversionService));

        converterRegistry.addConverter(new ArrayToStringConverter(conversionService));
        converterRegistry.addConverter(new StringToArrayConverter(conversionService));

        converterRegistry.addConverter(new ArrayToObjectConverter(conversionService));
        converterRegistry.addConverter(new ObjectToArrayConverter(conversionService));

        converterRegistry.addConverter(new CollectionToStringConverter(conversionService));
        converterRegistry.addConverter(new StringToCollectionConverter(conversionService));

        converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
        converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService));

        if (streamAvailable) {
            converterRegistry.addConverter(new StreamConverter(conversionService));
        }
    }

    private static void addScalarConverters(ConverterRegistry converterRegistry) {
        converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());

        converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
        converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());

        converterRegistry.addConverter(new StringToCharacterConverter());
        converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter());

        converterRegistry.addConverter(new NumberToCharacterConverter());
        converterRegistry.addConverterFactory(new CharacterToNumberFactory());

        converterRegistry.addConverter(new StringToBooleanConverter());
        converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());

        converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
        converterRegistry.addConverter(Enum.class, String.class,
                new EnumToStringConverter((ConversionService) converterRegistry));

        converterRegistry.addConverter(new StringToLocaleConverter());
        converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());

        converterRegistry.addConverter(new StringToCharsetConverter());
        converterRegistry.addConverter(Charset.class, String.class, new ObjectToStringConverter());

        converterRegistry.addConverter(new StringToCurrencyConverter());
        converterRegistry.addConverter(Currency.class, String.class, new ObjectToStringConverter());

        converterRegistry.addConverter(new StringToPropertiesConverter());
        converterRegistry.addConverter(new PropertiesToStringConverter());

        converterRegistry.addConverter(new StringToUUIDConverter());
        converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter());
    }

    private static final class Jsr310ConverterRegistrar {

        public static void registerJsr310Converters(ConverterRegistry converterRegistry) {
            converterRegistry.addConverter(new StringToTimeZoneConverter());
            converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
            converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
        }
    }

}

关于 Spring 的自动类型转换 我在之前就已经分析过了。和 java 内省的原理是一样的。

3.5 BeanWrapper

public interface BeanWrapper extends ConfigurablePropertyAccessor {

    Object getWrappedInstance();

    Class<?> getWrappedClass();

    PropertyDescriptor[] getPropertyDescriptors();

    PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;

    ...

}

这个接口就挺简单了,通过实现了上面的几个接口具有了依赖注入、类型转换注册(java 内省或者 Spring 自定义的 自动类型转换)。然后这个接口的主要的作用就是通过调用 getWrappedInstance 方法获取到当前实例对象,提供给属性的 writer 方法进行依赖注入。

writeMethod.invoke(getWrappedInstance(), value);

4、总结

让我们再来看一下 Spring 的对象扭转过过程:

Resource -> BeanDefinition -> BeanWrapper -> Object

相信基于以上的讲解,大家对于上面的过程能够理解 Spring IOC 的项目过程。在 Spring 进行 依赖注入的时候,首先把这种资源转化成 Resource 抽象,通过里面的 IO 流读取定义的 bean。然后再转化成 BeanDefinitioin,里面定义了包括构造器注入,以及 setter 注入的定义。最后通过 BeanWrapper 这个接口,首先获取定义的构造器注入属性,通过反射中的 Constructor 来创建对象。基于这个对象,通过 java 里面的内省机制获取到定义属性的属性描述器(PropertyDescriptor),调用属性的写入方法完成依赖注入,最后再调用 Spring 的自定义初始化逻辑,主要包括以下扩展:

  • BeanPostProcess,Spring aop 就是基于此扩展。
  • Init-method,可以在 bean 标签通过 init-method 定义,也可以实现 InitializingBean
  • XXXAware,Spring 容器感知类,可以在 bean 里面获取到 Spring 容器的内部属性。

希望通过这篇文章,大家对 Spring IOC 有一个宏观上面的认识。这样就不会在复杂源码的逻辑中迷失。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容