spring源码分析(五)依赖注入

简介: 在分析初始化bean时候,我跳过了非常重要的一步,关于spring的依赖注入。这篇博客会分析spring是如何帮我们实现依赖注入的。开始之前在分析spring依赖注入之前,我先想下,如果不用spring我是如何进行注入的。

在分析初始化bean时候,我跳过了非常重要的一步,关于spring的依赖注入。这篇博客会分析spring是如何帮我们实现依赖注入的。

开始之前

在分析spring依赖注入之前,我先想下,如果不用spring我是如何进行注入的。

public class A{
    private B b;
    //省略get和set方法
}

public class B{
}

public static void main(String[] args){
    A a=new A();
    B b=new B();
    a.setB(b);
}

很简单,如果我自己注入属性,我会:
1)初始化被依赖的对象
2)调用set方法(当然也可以通过反射注入)

有了这个前提,我们分析spring源码思路就会很清晰。先找到初始化的被依赖对象初始化的地方,再找到注入的地方。

初始化依赖的bean分析

再次回到doGetBean方法

protected <T> T doGetBean(
        final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
        throws BeansException {

    //....

    try {
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        //如果要初始化的是一个抽象类,就抛异常
        checkMergedBeanDefinition(mbd, beanName, args);

        // Guarantee initialization of beans that the current bean depends on.
        String[] dependsOn = mbd.getDependsOn();
        if (dependsOn != null) {
            for (String dependsOnBean : dependsOn) {
                //查下是不是真的依赖这个类(包括直接依赖和间接依赖)
                if (isDependent(beanName, dependsOnBean)) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Circular depends-on relationship between '" + beanName + "' and '" + dependsOnBean + "'");
                }
                registerDependentBean(dependsOnBean, beanName);
                getBean(dependsOnBean);
            }
        }
    //...
    return (T) bean;
}

依旧是省略了与依赖注入无关的代码

简单解释下:
1)spring在调用doGetBean时候,检测到这个bean依赖了其他的bean,首先会检测该bean是否真的依赖了这个bean

直接依赖和间接依赖都算
比如检测a是否依赖c,a -> c,a -> b -> c都算

2)记录依赖关系(比如记录了a依赖了c,c被a依赖了)
3)初始化依赖的bean

依赖的注入

分析完了依赖的初始化后,接下来就到了spring是如何进行注入的。

首先说下位置,spring是在创建bean的过程中,注入bean的。具体位置是在创建bean之后(可以理解为new 了对象之后)

具体方法是在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法里

populateBean方法

同样,对于bean的populateBean也去掉了各种扩展逻辑(主要是BeanPostProcessor)

//核心的一步
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

//当beanDefinition配置的策略是AUTOWIRE_BY_NAME或者AUTOWIRE_BY_TYPE时候,默认是0(也就是不会走进去)
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
        mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
    MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

    // Add property values based on autowire by name if applicable.
    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
        autowireByName(beanName, mbd, bw, newPvs);
    }

    // Add property values based on autowire by type if applicable.
    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
        autowireByType(beanName, mbd, bw, newPvs);
    }

    pvs = newPvs;
}

//省略后置处理

applyPropertyValues(beanName, mbd, bw, pvs);

如果是使用的xml配置方式,比如:

<bean id="car" class="com.hdj.learn.spring.demo.Car"/>
<bean id="person" class="com.hdj.learn.spring.demo.Person">
    <property name="name" value="duanji"></property>
    <property name="car" ref="car"></property>
</bean>
<alias name="person" alias="p"/>

那么,在分析xml时候,就会将PropertyValues设置到BeanDefinition中。所以,这种方式注入逻辑就简化成这样:

PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
applyPropertyValues(beanName, mbd, bw, pvs);

这样是不是就超简单了,核心逻辑就在applyPropertyValue中

AbstractAutowireCapableBeanFactory.applyPropertyValues

spring对于注入的处理还是挺复杂的,这里先只对最简单的数据类型(比如string)进行分析。

先看下方法的签名

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs);

这里要注意的是PropertyValues属性,之前也说过,PropertyValues就像是一个map,对于上述配置的xml,PropertyValues可以说,把我们注入需要的东西都准备好了。

name 需要注入 我们配的value “duanji”
car需要注入引用 car

img_8d91e30ff737e528bdb12cf7c8e1618f.png

同样的对于applyPropertyValues这里省略了其他逻辑(只分析最简单的String的注入)

String propertyName = pv.getName();  //注入的field的变量名,也就是property配置的name
Object originalValue = pv.getValue(); // 注入的值 TypedStringValue
// 1.1 resolveValueIfNessary
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);  
Object convertedValue = resolvedValue;
boolean convertible = bw.isWritableProperty(propertyName) &&
        !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
    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));
}

resolveValueIfNessary

并不是我们往xml配置的值就直接可以使用的

比如这样的配置:

<bean id="car" class="com.hdj.learn.spring.demo.Car">
    <property name="cname" value="宝马"/>
</bean>
<bean id="person" class="com.hdj.learn.spring.demo.Person">
    <property name="name" value="duanji"></property>
    <property name="car" ref="car"></property>
    <property name="carName" value="#{car.cname}"/>
</bean>

这个时候,我们希望往carName里注入的值,就不是#{car.cname}而是宝马

resolveValueIfNessary就是做这件事的。这里我们依旧截取很小一段,只是分析String类型
BeanDefinitionValueResolver.resolveValueIfNessary

else if (value instanceof TypedStringValue) {
    // Convert value to target type here.
    TypedStringValue typedStringValue = (TypedStringValue) value;
    //转换以及替换逻辑
    Object valueObject = evaluate(typedStringValue);
    try {
        Class<?> resolvedTargetType = resolveTargetType(typedStringValue);
        if (resolvedTargetType != null) {
            return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
        }
        else {
            return valueObject;
        }
    }
    catch (Throwable ex) {
        // Improve the message by showing the context.
        throw new BeanCreationException(
                this.beanDefinition.getResourceDescription(), this.beanName,
                "Error converting typed String value for " + argName, ex);
    }
}

evaluate的具体细节这里就不分析了,大概就是判断下字符串里的#{} ,然后执行spel表达式。

setPropertyValues

到了这一步,就剩下最后的设置值了

具体的流程比较长,这里就贴出最后执行set方法的地方(核心逻辑在这里org.springframework.beans.BeanWrapperImpl.BeanPropertyHandler#setValue)


img_e2adb20f1fd9848e03afc720c7de344a.png

很简单,就是反射。反射往对象设置对应的值。

总结

spring的依赖注入还是很复杂的,这里只是分析了下大概的流程。并且简单说明了String是如何注入的。

大概流程是:
1)doGetBean里发现有依赖需要注入,初始化这些依赖
2)populateBean里对依赖进行处理(字符串的处理,依赖的对象的处理)
3)通过反射方式,为对象设置值

问题

本篇文章只是进行了下粗略的分析,关于很多细节都没有深入。
比如 autowireMode 又比如 使用注解的方式是如何进行依赖注入的(注解方式是通过PostProcessor方式进行的注入)这些会在下面几篇博客里深入的分析。

目录
相关文章
|
4月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
144 2
|
4月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
92 1
|
4天前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
2月前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
55 6
|
3月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
2月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
112 7
|
3月前
|
Java 数据库 数据安全/隐私保护
轻松掌握Spring依赖注入:打造你的登录验证系统
本文以轻松活泼的风格,带领读者走进Spring框架中的依赖注入和登录验证的世界。通过详细的步骤和代码示例,我们从DAO层的创建到Service层的实现,再到Spring配置文件的编写,最后通过测试类验证功能,一步步构建了一个简单的登录验证系统。文章不仅提供了实用的技术指导,还以口语化和生动的语言,让学习变得不再枯燥。
67 2
|
3月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
85 2
|
4月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
101 9
|
4月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
70 1

热门文章

最新文章