Spring 依赖注入概述、使用以及原理解析

news2024/11/27 8:53:05

前言

源码在我github的guide-spring仓库中,可以克隆下来 直接执行。

我们本文主要来介绍依赖注入的使用示例及其原理

依赖注入

什么是依赖注入

依赖注入(Dependency Injection,简称DI)是一种设计模式,它用于实现对象之间的松散耦合。在依赖注入中,一个对象不再负责创建或查找它所依赖的对象,而是将这些依赖关系通过外部传递进来,外部指的就是 IoC 容器,IoC 容器负责对象的创建、管理和注入,我们也常说 DI 是实现 IoC 的一种具体技术。这种方式有助于提高代码的可维护性、可测试性,同时降低了组件之间的耦合度。

依赖注入的方式

注入的方式有以下几种:

  • setter方法注入
  • 构造器注入
  • 字段注入
  • 方法注入
  • 接口回调注入
setter方法和构造器注入方式

其中setter方法注入和构造器注入在xml配置手动注入时,比较常见,使用方法可以参考下面的示例代码:

xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="user" class="com.markus.spring.ioc.overview.domain.User">
        <!-- 属性注入 -->
        <property name="id" value="1"/>
        <property name="username" value="markus zhang"/>
    </bean>
    
    <bean id="user-by-constructor" class="com.markus.spring.ioc.overview.domain.User">
        <!-- 构造器注入 -->
        <constructor-arg index="0" value="2"/>
        <constructor-arg index="1" value="luna"/>
    </bean>
</beans>

Java 代码

package com.markus.spring.dependency.injection;

import com.markus.spring.ioc.overview.domain.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: markus
 * @date: 2023/12/24 10:57 AM
 * @Description: setter方法和构造器注入 示例
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class SetterAndConstructorInjectionDemo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-inject.xml");

        User user = context.getBean("user", User.class);
        System.out.println(user);

        User userByConstructor = context.getBean("user-by-constructor", User.class);
        System.out.println(userByConstructor);

        context.close();
    }
}

控制台

image-20231224105957921

接口回调注入

字段和方法注入的方式我们放在下面的自动绑定来说。接下来先把接口回调的注入方式介绍一下。

准备一个Java Bean

我们在原本 User 这个 Java Bean 的基础上,让其实现 BeanNameAware 接口并重写其 setBeanName 方法,这样就可以在 IoC 创建该 Bean 的时候完成 beanName 属性值的回调注入。

package com.markus.spring.ioc.overview.domain;

import org.springframework.beans.factory.BeanNameAware;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * @Author: zhangchenglong06
 * @Date: 2023/11/27
 * @Description:
 */
public class User implements BeanNameAware {
    private Long id;
    private String username;

    private String beanName;

    public User() {
        System.out.println("开始初始化");
    }

    public User(Long id, String username) {
        this.id = id;
        this.username = username;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

编写一个示例代码

package com.markus.spring.dependency.injection;

import com.markus.spring.ioc.overview.domain.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: markus
 * @date: 2023/12/24 11:06 AM
 * @Description: 接口回调注入 示例
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class InterfaceCallbackInjectionDemo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-inject.xml");

        User user = context.getBean("user", User.class);
        System.out.println("User Bean Name is [ " + user.getBeanName() + " ]");

        context.close();
    }
}

控制台

image-20231224110830000

手动注入的弊端

前面我们了解通过属性注入、构造器注入的使用方式,看似比较简单、方便,但试想,如果有注入集合场景的时候,我们通过手动注入应该如何实现呢?下面来掩饰一下:

xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="user" class="com.markus.spring.ioc.overview.domain.User">
        <!-- 属性注入 -->
        <property name="id" value="1"/>
        <property name="username" value="markus zhang"/>
    </bean>

    <bean id="user-by-constructor" class="com.markus.spring.ioc.overview.domain.User">
        <!-- 构造器注入 -->
        <constructor-arg index="0" value="2"/>
        <constructor-arg index="1" value="luna"/>
    </bean>

    <bean id="user-list-holder" class="com.markus.spring.ioc.overview.domain.UserListHolder">
        <property name="users">
            <list>
                <ref bean="user"/>
                <ref bean="user-by-constructor"/>
                <!-- 依次增加 -->
            </list>
        </property>
    </bean>
</beans>

示例代码

package com.markus.spring.dependency.injection;

import com.markus.spring.ioc.overview.domain.User;
import com.markus.spring.ioc.overview.domain.UserListHolder;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: markus
 * @date: 2023/12/24 10:57 AM
 * @Description: setter方法和构造器注入 示例
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class SetterAndConstructorInjectionDemo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-inject.xml");

        UserListHolder userListHolder = context.getBean("user-list-holder", UserListHolder.class);
        System.out.println(userListHolder);

        context.close();
    }
}

控制台

image-20231224140538059

我们的目的就是将容器中的所有 User 类型的 Bean 注入到 UserListHolder 中,试想如果容器中不断的增加新的 User 类型的 Bean,那么通过手动注入的方式则会每次都需要去修改 UserListHolder 这个 Bean,这非常的不便,下面我们就来介绍下 Spring 框架提供的 自动绑定(Autowiring)特性。

自动绑定

使用和优点

我们先来看下官方对于自动绑定的说明:原文链接

image-20231224130641241

上面解释了自动装配的功能以及其优势和自动绑定的方式。大致意思是说:Spring IoC 容器可以自动绑定协作 Bean 之间的关系。我们可以让 Spring 通过查找 ApplicationContext 中的内容将其自动解析注入到你的 Bean 中。自动装配有以下优点:

  • 自动装配可以显著减少手动配置需要的属性或者构造器的参数
  • 自动装配可以随着对象的发展自动更新配置,例如如果需要向类中添加依赖项,则无需修改配置即可满足该依赖项。

也给出了基于 XMl 配置元信息场景下,如何使用自动绑定以及自动绑定的几种模式:

  • no : 默认状态,无自动绑定
  • byName : 通过属性名进行自动绑定
  • byType : 通过类型进行自动绑定
  • constructor : 和 byType 相似,不同的是 constructor 是应用与构造器参数上

我们回到上面在手动注入的弊端中所提到的例子,我们通过自动绑定稍加改造即可完成即便容器中增加多少或者删除多少 User 类型的 Bean 配置都不需要更改 UserListHolder 的配置完成其内容的自动更新。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="user" class="com.markus.spring.ioc.overview.domain.User">
        <!-- 属性注入 -->
        <property name="id" value="1"/>
        <property name="username" value="markus zhang"/>
    </bean>

    <bean id="user-by-constructor" class="com.markus.spring.ioc.overview.domain.User">
        <!-- 构造器注入 -->
        <constructor-arg index="0" value="2"/>
        <constructor-arg index="1" value="luna"/>
    </bean>

    <bean id="user-list-holder" class="com.markus.spring.ioc.overview.domain.UserListHolder"
          autowire="byType"> <!--通过类型 自动绑定-->
<!--        <property name="users">-->
<!--            <list>-->
<!--                <ref bean="user"/>-->
<!--                <ref bean="user-by-constructor"/>-->
<!--                &lt;!&ndash; 依次增加 &ndash;&gt;-->
<!--            </list>-->
<!--        </property>-->
    </bean>
</beans>

再次执行下上面的程序,依然正常执行。

我们通过自定绑定可以注入很多类型,包括数组、集合、Map以及外部化配置,下面我们用 @Autowired 和 @Value 注解完成这些功能。

package com.markus.spring.dependency.injection;

import com.markus.spring.ioc.overview.domain.User;
import com.markus.spring.ioc.overview.domain.UserHolder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;

import java.util.Collection;
import java.util.Map;
import java.util.Optional;

/**
 * @Author: zhangchenglong06
 * @Date: 2023/12/18
 * @Description: @Autowired 注解注入示例
 */
@Configuration
@PropertySource("classpath:/META-INF/application.properties")
public class AtAutowiredAnnotationInjectionDemo {

    @Autowired
    private Optional<User> optionalUser;

    @Autowired
    private ObjectFactory<User> userObjectFactory;

    /**
     * @Autowired 注入流程
     * 1. 先按照名称查找
     * 2. 再按照类型查找
     */
    @Autowired
    private User user1;

    @Autowired
    private Collection<User> users;

    @Autowired
    private Map<String, User> userMap;

    @Autowired
    private User[] userArrays;

    private User userFromMethodInjection;

    @Autowired
    public void setUserFromMethodInjection(User user) {
        this.userFromMethodInjection = user;
    }

    @Autowired
    private UserHolder userHolder;

    @Value("${my_site}")
    private String mySite;


    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AtAutowiredAnnotationInjectionDemo.class);

        context.refresh();

        AtAutowiredAnnotationInjectionDemo demo = context.getBean(AtAutowiredAnnotationInjectionDemo.class);
        System.out.println("demo.optionalUser : " + demo.optionalUser.get());
        System.out.println("demo.userObjectFactory : " + demo.userObjectFactory.getObject());
        System.out.println("demo.user : " + demo.user1);
        System.out.println("demo.users : " + demo.users);
        System.out.println("demo.userMap : " + demo.userMap);

        System.out.print("demo.userArrays : [ ");
        for (User userArray : demo.userArrays) {
            System.out.print(userArray.getBeanName() + " ");
        }
        System.out.println("]");
        System.out.println("demo.userFromMethodInjection : " + demo.userFromMethodInjection);
        System.out.println("demo.userHolder : " + demo.userHolder);
        System.out.println("demo.mySite : " + demo.mySite);

        context.close();
    }

    @Bean("user1")
    public User user1() {
        User user = new User();
        user.setId(1L);
        user.setUsername("Markus Zhang");
        return user;
    }

    @Bean("user2")
    @Primary
    public User user2() {
        User user = new User();
        user.setId(2L);
        user.setUsername("Luna");
        return user;
    }

    @Bean
    public UserHolder userHolder(@Autowired User user) {
        UserHolder userHolder = new UserHolder();
        userHolder.setUser(user);
        return userHolder;
    }
}

我们来看下控制台

image-20231224142821275

缺点

一项技术的落地,有它顺应时代的诉求,也一定会有它的局限性,我们通过官网来叙述下 自动绑定(Autowiring) 有什么限制或者缺点:原文链接

image-20231224143958183

上面大概描述了 自动绑定的缺点和官方给你的建议:

  • 缺点
    • 显示注入比自动绑定优先级更高,它总会覆盖自动绑定的属性
    • 不能注入简单属性,例如原语类型 String、int等,这是框架设计的限制(ps: 不过这些类型我们可以通过 @Value 实现,前面给出了例子)
    • 自动绑定没有显示绑定精确,尽管 Spring 已经很小心地处理以避免歧义,但还是会有意想不到的结果
    • 绑定信息不会以可视化的形式展示出来
    • 多个 BeanDefinition 的情况下,在注入 集合、数组、Map等场景时问题不大,但是在注入单个 Bean 时,可能会产生问题,如果 Spring 在进行注入时找不到唯一 Bean 则会抛出异常
  • 建议
    • 避免使用自动绑定,采用显示绑定
    • 如果某个 Bean 不想被注入到其他 Bean 中,则可通过 autowired-candidate 属性设置为 false 来跳过注入
    • 在注入单个 Bean 的场景中,我们可以把目标 Bean 的 primary 属性设置为 true
    • 通过注解驱动实现更精细粒度的控制

依赖注入原理

Bean 生命周期

在说依赖注入前,我们先来整体看下 Spring Bean 的生命周期,然后再来说依赖注入发生在哪个阶段,接着再去对应源码处解析其原理

Bean生命周期-doGetBean

上图是 Bean 整个生命周期图,我们放大来看构造器注入和 setter 方法注入所在的环节:

image-20231224150625305

构造器注入

我们把 Spring 源码拷贝出来:

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
  // ... 其他流程省略
  // Candidate constructors for autowiring?
  Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
  if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
      mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
    return autowireConstructor(beanName, mbd, ctors, args);
  }

  // No special handling: simply use no-arg constructor.
  return instantiateBean(beanName, mbd);
}

可以看到 Spring 在判断是否进行 构造器注入 的条件有以下几种(满足其一即可):

  • 构造器列表不为空
  • BeanDefinition 的 autowireMode 设置为 AUTOWIRE_CONSTRUCTOR
  • BeanDefinition 指定了构造器参数
  • args 参数不为空(ps: 这个通常为空)

否则不进行特殊处理,直接使用无参构造器进行实例化(通过 cglib 或者 jdk 动态代理),这块不进行细节介绍了。

下面我们进入到 autowireConstructor 方法,看看里面做了什么:

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

		BeanWrapperImpl bw = new BeanWrapperImpl();
		this.beanFactory.initBeanWrapper(bw);

		Constructor<?> constructorToUse = null;
		ArgumentsHolder argsHolderToUse = null;
		Object[] argsToUse = null;

		if (explicitArgs != null) {
			argsToUse = explicitArgs;
		}
		else {
			// ... 其他细节忽略

			// 需要去解析构造器
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			ConstructorArgumentValues resolvedValues = null;

			int minNrOfArgs;
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			}
			else {
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
				resolvedValues = new ConstructorArgumentValues();
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}

      // 以参数个数的多少对构造器进行排序(顺序从多到少)
			AutowireUtils.sortConstructors(candidates);
			int minTypeDiffWeight = Integer.MAX_VALUE;
			Set<Constructor<?>> ambiguousConstructors = null;
			LinkedList<UnsatisfiedDependencyException> causes = null;

      // 遍历 构造器,找到最合适的构造器进行实例化
			for (Constructor<?> candidate : candidates) {

				int parameterCount = candidate.getParameterCount();

				if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
					// 找到了最适合的构造器,后面不需要再找了
					break;
				}
				if (parameterCount < minNrOfArgs) {
					continue;
				}

				ArgumentsHolder argsHolder;
				Class<?>[] paramTypes = candidate.getParameterTypes();
				if (resolvedValues != null) {
          // ...  省略细节
          // 核心方法,是解析当前候选构造器参数对应的参数值
          argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
              getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
          
          // ... 省略细节
				}
        // ... 省略

				int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
        // 如果这个构造器最近接匹配的话,就选择这个构造器进行实例化
				// Choose this constructor if it represents the closest match.
				if (typeDiffWeight < minTypeDiffWeight) {
					constructorToUse = candidate;
					argsHolderToUse = argsHolder;
					argsToUse = argsHolder.arguments;
					minTypeDiffWeight = typeDiffWeight;
					ambiguousConstructors = null;
				}
				// ... 
			}

			// ... 省略其他细节
		}
		bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
	}

总结一下:

  • 获取候选构造器集合
  • 对构造器集合进行排序(根据参数数量从多到少进行排序)
  • 遍历构造器集合,依次解析构造器的参数对应的值(核心方法 createArgumentArray)
  • 解析出值以后,通过反射计算出类型差异权重(Spring 设计出来的一个算法,有兴趣的可以了解下 org.springframework.util.MethodInvoker#getTypeDifferenceWeight)
  • 如果差异结果最匹配,就选择这个构造器进行实例化

我们再深入到createArgumentArray方法中去看下

private ArgumentsHolder createArgumentArray(
			String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
			BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
			boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException {
  	// ... 省略细节

		for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
			if (valueHolder != null) {
		  	// ... 省略细节
			}
			else {
      	// ... 省略细节
				try {
          // 核心在这,依次解析构造器中的参数值
					Object autowiredArgument = resolveAutowiredArgument(
							methodParam, beanName, autowiredBeanNames, converter, fallback);
					args.rawArguments[paramIndex] = autowiredArgument;
					args.arguments[paramIndex] = autowiredArgument;
					args.preparedArguments[paramIndex] = autowiredArgumentMarker;
					args.resolveNecessary = true;
				}
				catch (BeansException ex) {
					throw new UnsatisfiedDependencyException(
							mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex);
				}
			}
    	// ... 省略细节
		}

  	// ... 省略细节
		return args;
	}

我们可以看到createArgumentArray方法中主要做的事情就是遍历构造器的参数,依次解析其参数值,我们再往下看一下resolveAutowiredArgument方法,看下它是如何解析出具体值的。

protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
			@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {

		// ... 省略细节
		try {
      // 这块就是最最最最最最最核心的函数了,我们在揭幕到这
			return this.beanFactory.resolveDependency(
					new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
		}
		// ... 省略细节
	}

可以看到 依赖注入实现原理中最最最最核心的函数 org.springframework.beans.factory.config.AutowireCapableBeanFactory#resolveDependency(xxx) 露出水面了😄,我们这里先不深入解析,点到为止。

总结一下:构造器注入发生在 Bean 生命周期中 实例化 Bean 阶段,也就是 createBeanInstance 函数中,这个函数主要目的就是选择出 Bean 对象中最合适的构造器完成对 Bean 的实例化,在选择构造器的过程中,会有一步判断 用户是否选择进行构造器自动绑定的逻辑,如果用户选择了构造器自动绑定,那么 Spring 框架会通过反射获取该类的所有构造器,选择出最适合的构造器进行实例化。而在选择构造器的过程中,会对构造器的参数进行解析,解析出来以后,通过对比差异(Spring 实现了一套类型与参数值的差异算法,有兴趣的同学可以深入了解下org.springframework.util.MethodInvoker#getTypeDifferenceWeight)来决定最终用来实例化的构造器。而在解析参数值时,我们探究到 resolveDependency这个函数,通过这个函数可以解析出来当前参数所对应的值。

构造器注入原理就如上所述了。

setter方法注入

同样,我们也先把实现setter方法注入的入口函数先拷贝出来

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
  // ... 省略细节

  PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

  int resolvedAutowireMode = mbd.getResolvedAutowireMode();
  if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
    MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
    // Add property values based on autowire by name if applicable.
    if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
      autowireByName(beanName, mbd, bw, newPvs);
    }
    // Add property values based on autowire by type if applicable.
    if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
      autowireByType(beanName, mbd, bw, newPvs);
    }
    pvs = newPvs;
  }
  // ... 省略细节
}

我们省略其他的细节,重点看下这个函数里面对于自动绑定的处理,可以看到,框架通过判断 BeanDefinition 的 autowiredMode 属性来决定执行 autowireByName 还是 autowireByType 函数。

autowiredByName

protected void autowireByName(
    String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

  String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
  // 获取属性名
  for (String propertyName : propertyNames) {
    if (containsBean(propertyName)) {
      // 通过属性名称进行依赖查找
      Object bean = getBean(propertyName);
      pvs.add(propertyName, bean);
      registerDependentBean(propertyName, beanName);
      if (logger.isTraceEnabled()) {
        logger.trace("Added autowiring by name from bean name '" + beanName +
            "' via property '" + propertyName + "' to bean named '" + propertyName + "'");
      }
    }
    else {
      if (logger.isTraceEnabled()) {
        logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
            "' by name: no matching bean found");
      }
    }
  }
}

autowiredByType

protected void autowireByType(
    String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

  TypeConverter converter = getCustomTypeConverter();
  if (converter == null) {
    converter = bw;
  }

  Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
  String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
  for (String propertyName : propertyNames) {
    try {
      PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
      // Don't try autowiring by type for type Object: never makes sense,
      // even if it technically is a unsatisfied, non-simple property.
      if (Object.class != pd.getPropertyType()) {
        MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
        // Do not allow eager init for type matching in case of a prioritized post-processor.
        boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
        DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
        // 解析依赖值
        Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
        if (autowiredArgument != null) {
          pvs.add(propertyName, autowiredArgument);
        }
        for (String autowiredBeanName : autowiredBeanNames) {
          registerDependentBean(autowiredBeanName, beanName);
          if (logger.isTraceEnabled()) {
            logger.trace("Autowiring by type from bean name '" + beanName + "' via property '" +
                propertyName + "' to bean named '" + autowiredBeanName + "'");
          }
        }
        autowiredBeanNames.clear();
      }
    }
    catch (BeansException ex) {
      throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
    }
  }
}

通过类型注入中,我们也看到了这个核心函数resolveDependency,通过其将属性参数值解析出来并完成注入。

总结一下:setter方法注入有两种方式,一是通过名称注入;二是通过类型注入。在通过名称注入的方式中,框架采用依赖查找完成;在通过类型注入的方式中,框架采用解析依赖完成。

依赖来源

前面我们解释了构造器注入setter方法注入两种方式的原理,追溯到底层我们发现,两者都会通过org.springframework.beans.factory.config.AutowireCapableBeanFactory#resolveDependency(xxx)这个函数获取解析后的参数值(setter方法通过名称注入的时候,采用的依赖查找除外)。

这里我们可以得到一个结论:依赖注入的来源主要来源于 getBean(xxx) 和 resolveDependency(xxx) 两处,接下来我们将深入探究这两个来源。

getBean(xxx)的来源

getBean就是依赖查找,追溯getBean(xxx)的来源等价于追溯依赖查找的来源。

依赖查找的来源包括:

  • BeanDefinition
    • 用户配置
    • Spring 内建
  • 单例对象
    • 用户使用 API 注册
    • Spring 内建

用户配置的 BeanDefinition 元信息我们就不介绍了,对于 Spring 内建 BeanDefinition 的注册,如下图所示:

org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

image-20231224190557360

我们再来看下 Spring 内建单例对象

org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory

image-20231224190909487

org.springframework.context.support.AbstractApplicationContext#initMessageSource

org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster

org.springframework.context.support.AbstractApplicationContext#initLifecycleProcessor

image-20231224191117105

最终,依赖查找的来源这两项:

  • org.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinition
  • org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton

resolveDependency(xxx)的来源

针对 resolveDependency 依赖来源,我们直接从这个入口开始

// org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

  InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
  try {
    // 针对 @Value 注解的使用场景,例如 @Value("${xxx:defaultValue}")
    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 converter = (typeConverter != null ? typeConverter : getTypeConverter());
      try {
        return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
      }
      catch (UnsupportedOperationException ex) {
        // A custom TypeConverter which does not support TypeDescriptor resolution...
        return (descriptor.getField() != null ?
            converter.convertIfNecessary(value, type, descriptor.getField()) :
            converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
      }
    }

    // 解析 字段类型为 Array、Collection、Map等的场景
    Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
    if (multipleBeans != null) {
      return multipleBeans;
    }

    // 按照类型从 数据源中进行 查找
    Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
    if (matchingBeans.isEmpty()) {
      if (isRequired(descriptor)) {
        raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
      }
      return null;
    }

    String autowiredBeanName;
    Object instanceCandidate;

    // 当有多个 匹配到的 Bean 情况,决定选择哪个作为首选的 Bean 进行注入
    if (matchingBeans.size() > 1) {
      autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
      if (autowiredBeanName == null) {
        if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
          return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
        }
        else {
          // In case of an optional Collection/Map, silently ignore a non-unique case:
          // possibly it was meant to be an empty collection of multiple regular beans
          // (before 4.3 in particular when we didn't even look for collection beans).
          return null;
        }
      }
      instanceCandidate = matchingBeans.get(autowiredBeanName);
    }
    else {
      // We have exactly one match.
      Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
      autowiredBeanName = entry.getKey();
      instanceCandidate = entry.getValue();
    }

    if (autowiredBeanNames != null) {
      autowiredBeanNames.add(autowiredBeanName);
    }
    if (instanceCandidate instanceof Class) {
      instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
    }
    Object result = instanceCandidate;
    if (result instanceof NullBean) {
      if (isRequired(descriptor)) {
        raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
      }
      result = null;
    }
    if (!ClassUtils.isAssignableValue(type, result)) {
      throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
    }
    return result;
  }
  finally {
    ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
  }
}

总结一下,这个函数主要做了一下事情:

  • 解析字段被 @Value 标注时,做一个解析并返回(解析的过程是,如果有显示的属性配置,则采用配置值,没有配置则采用默认值)
  • 解析字段为数组、集合或者Map的场景,从数据源中按照类型查找出来并设置到当前字段中去
  • 解析字段非上述情况时,从数据源中同样按照类型查找出来,并选择出首选的依赖返回。

上面说的数据源比较神秘,我们就深入到 determineAutowireCandidate 方法来一探究竟,解开其神秘的面纱

protected Map<String, Object> findAutowireCandidates(
			@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
  // 按照类型从容器中查找相关的候选 Bean 名称
  String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
      this, requiredType, true, descriptor.isEager());
  Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
  // 从 可解析依赖 中 查找是否有该 类型的 依赖,如果有也被考虑进 result
  for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
    Class<?> autowiringType = classObjectEntry.getKey();
    if (autowiringType.isAssignableFrom(requiredType)) {
      Object autowiringValue = classObjectEntry.getValue();
      autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
      if (requiredType.isInstance(autowiringValue)) {
        result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
        break;
      }
    }
  }
  // 根据候选的 Bean 名称,通过其 BeanDefinition 判断是否可以加入到 result
  for (String candidate : candidateNames) {
    if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
      addCandidateEntry(result, candidate, descriptor, requiredType);
    }
  }
  
  // 结果为空
  if (result.isEmpty()) {
    // 判断字段不是多 Bean 的场景
    boolean multiple = indicatesMultipleBeans(requiredType);
    // Consider fallback matches if the first pass failed to find anything...
    DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
    for (String candidate : candidateNames) {
      if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
          (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
        addCandidateEntry(result, candidate, descriptor, requiredType);
      }
    }
    // 还为空 并且 字段不为多 Bean 的场景
    if (result.isEmpty() && !multiple) {
      // Consider self references as a final pass...
      // but in the case of a dependency collection, not the very same bean itself.
      for (String candidate : candidateNames) {
        if (isSelfReference(beanName, candidate) &&
            (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
            isAutowireCandidate(candidate, fallbackDescriptor)) {
          addCandidateEntry(result, candidate, descriptor, requiredType);
        }
      }
    }
  }
  return result;
}

总结一下,这个函数主要做了以下事情:

  • 先从 IoC 容器中查找指定类型的候选 Bean 集合
  • 实例化一个 Map<String,Object> 的集合result,用来存储该类型对应的 候选依赖
  • 可解析依赖 里查找符合条件的候选并添加进 result 中
  • 候选 Bean 集合中找到 非自引用(候选Bean 指向 当前Bean 或者 候选Bean 的 工厂Bean 执行当前Bean)、并且BeanDefinition#autowire-candidate为true的候选Bean加入到result中
  • 结果为空的兜底,经符合一定条件的候选 Bean 加入到 result 中
  • 将 result 返回,得到最终的依赖源

综上,我们可以得出一个结论,依赖注入的来源包括两方面,分别如下:

  • 依赖查找的来源
  • 可解析依赖 resolvableDependencies,源码位于 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolvableDependencies

本文总结

本篇,我们介绍了依赖注入的使用方式和其注入原理,并追根溯源定位的依赖的来源,分析了依赖查找的来源以及依赖注入的来源。大家可以结合本篇文章进入到源码进行研读,印象会更深刻。针对 @Autowired 注解的使用和原理分析我放在了另一篇一文搞懂Spring @Autowired注解的使用及其原理,有兴趣的也可以去了解阅读下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1333425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL学习笔记008】多表查询

1、多表关系 概述&#xff1a;项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系&#xff0c;基本上可分为三种&a…

在linux操作系统Centos上安装服务器相关软件

如果您的服务器没有图形界面(GUI),您可以通过命令行(终端)来安装和配置Tomcat、JDK和MySQL等软件。以下是在没有图形界面GHome的 Linux 系统上安装这些软件的基本步骤: 对于CentOS Stream 9,您可以按照以下步骤在命令行上安装Tomcat、JDK 和 MySQL 数据库: 1. 安装JD…

设计模式--迭代器模式

实验18&#xff1a;迭代器模式 本次实验属于模仿型实验&#xff0c;通过本次实验学生将掌握以下内容&#xff1a; 1、理解迭代器模式的动机&#xff0c;掌握该模式的结构&#xff1b; 2、能够利用迭代器模式解决实际问题。 [实验任务]&#xff1a;JAVA和C常见数据结构迭代…

基于遗传算法特征选择及单层感知机模型的IMDB电影评论文本分类案例

基于遗传算法特征选择及单层感知机模型的IMDB电影评论文本分类案例 1.数据载入及处理2.感知机模型建立3.模型训练4.遗传算法进行特征选择注意 5.联系我们 1.数据载入及处理 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dat…

线程的同步与互斥

抢票的例子 竞争过程 进程A被切走 进程B被切走 结论&#xff1a; 互斥 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); mutex: 指向要初始化的互斥锁的指针。attr: 用于设置互斥锁属性的指针&#xff0c;通常可以传入 NULL 以使用默认属性…

【贪心】最小生成树Kruskal算法Python实现

文章目录 [toc]问题描述最小生成树的性质证明 Kruskal算法时间复杂性Python实现 个人主页&#xff1a;丷从心 系列专栏&#xff1a;贪心算法 问题描述 设 G ( V , E ) G (V , E) G(V,E)是无向连通带权图&#xff0c; E E E中每条边 ( v , w ) (v , w) (v,w)的权为 c [ v ] …

【图神经网络 · 科研笔记5】异构信息网络,利用注意力选择元路径;利用进化邻域和社群实现自监督动态图嵌入,交叉监督对比学习;近期科研思维导图小汇总;

记录部分科研文献阅读相关内容【划重点】,主题“图神经网络”,仅学习使用。 🎯作者主页: 追光者♂🔥 🌸个人简介: 📝[1] CSDN 博客专家📝 🏆[2] 人工智能领域优质创作者🏆 🌟[3] 2023年城市之星领跑者TOP1(哈尔滨)🌿 🌿[4] 2022年度…

maven下载jar包失败

配置国内镜像 设置国内的仓库,比如: <!--阿里仓库--><mirror><id>alimaven</id><name>aliyun maven</name><url>https://maven.aliyun.com/repository/public/</url><mirrorOf>central</mirrorOf></mirror>…

CnosDB如何确保多步操作的最终一致性?

背景 在时序数据库中&#xff0c;资源的操作是一个复杂且关键的任务。这些操作通常涉及到多个步骤&#xff0c;每个步骤都可能会失败&#xff0c;导致资源处于不一致的状态。例如&#xff0c;一个用户可能想要在CnosDB集群中删除一个租户&#xff0c;这个操作可能需要删除租户…

Alnet网络分析与demo实例

参考自 up主的b站链接&#xff1a;霹雳吧啦Wz的个人空间-霹雳吧啦Wz个人主页-哔哩哔哩视频这位大佬的博客 Fun_机器学习,pytorch图像分类,工具箱-CSDN博客 数据集下载 http://download.tensorflow.org/example_images/flower_photos.tgz 包含 5 中类型的花&#xff0c;每种…

嵌入式开发——PWM高级定时器

学习目标 加强掌握PWM开发流程理解定时器与通道的关系掌握多通道配置策略掌握互补PWM配置策略掌握定时器查询方式掌握代码抽取优化策略掌握PWM调试方式学习内容 需求 点亮8个灯,采用pwm的方式。 定时器 通道 <

Netty-4-网络编程模式

我们经常听到各种各样的概念——阻塞、非阻塞、同步、异步&#xff0c;这些概念都与我们采用的网络编程模式有关。 例如&#xff0c;如果采用BIO网络编程模式&#xff0c;那么程序就具有阻塞、同步等特质。 诸如此类&#xff0c;不同的网络编程模式具有不同的特点&#xff0c…

【大数据】NiFi 的基本使用

NiFi 的基本使用 1.NiFi 的安装与使用1.1 NiFi 的安装1.2 各目录及主要文件 2.NiFi 的页面使用2.1 主页面介绍2.2 面板介绍 3.NiFi 的工作方式3.1 基本方式3.2 选择处理器3.3 组件状态3.4 组件的配置3.4.1 SETTINGS&#xff08;通用配置&#xff09;3.4.2 SCHEDULING&#xff0…

博弈论:理解决策背后的复杂动态

1.基本概念 博弈论是一门研究具有冲突和合作元素决策制定的数学理论。它不仅适用于经济学&#xff0c;还广泛应用于政治学、心理学、生物学等领域。博弈论的核心在于分析参与者&#xff08;称为“玩家”&#xff09;在特定情境下的策略选择&#xff0c;以及这些选择如何影响最…

工资发放 C语言xdoj92

题目描述&#xff1a; 公司财务要发工资现金&#xff0c;需要提前换取100元、50元、20元、10元、5元和1元的人民币&#xff0c; 请输入工资数&#xff0c;计算张数最少情况下&#xff0c;各自需要多少张。 输入格式&#xff1a;共一行&#xff0c;输入一个正整数。 输出格式&am…

游戏软件提示d3dcompiler_43.dll的五个解决方法,亲测靠谱

在使用电脑进行工作&#xff0c;玩游戏的时候&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是“D3DCompiler_43.dll丢失”的提示。D3DCompiler_43.dll是一个非常重要的动态链接库文件。它是由DirectX SDK提供的&#xff0c;用于编译和优化DirectX着色器代码的…

50 个具有挑战性的概率问题 [04/50]:尝试直至首次成功

一、说明 你好&#xff0c;我最近对与概率相关的问题产生了兴趣。我偶然发现了 Frederick Mosteller 所著的《五十个具有挑战性的概率问题及其解决方案》这本书。我认为创建一个系列来讨论这些可能作为面试问题出现的迷人问题会很有趣。每篇文章仅包含 1 个问题&#xff0c;使其…

【Qt之Quick模块】6. QML语法详解_1 基础语法与三种导入语句

前言 通过以上1-5文档的介绍&#xff0c;Quick与QML的概念及QML语法、类型、文件作用等已叙述个大概&#xff0c;接下来是对QML语法进行展开来说。 其实&#xff0c;学习任何一门语言或者做任何一件事情&#xff0c;并不用一开始就要求尽善尽美&#xff0c;做个无懈可击&…

【Python从入门到进阶】45、Scrapy框架核心组件介绍

接上篇《44、Scrapy的基本介绍和安装》 上一篇我们学习了Scrapy框架的基础介绍以及环境的搭建&#xff0c;本篇我们来学习一下Scrapy框架的核心组件的使用。 下面的核心组件的介绍&#xff0c;仍是基于这幅图的机制&#xff0c;大家可以再回顾一下&#xff1a; 注&#xff1a;…

数学的雨伞下:理解世界的乐趣

这本书没有一个公式&#xff0c;却讲透了数学的本质&#xff01; 《数学的雨伞下&#xff1a;理解世界的乐趣》。一本足以刷新观念的好书&#xff0c;从超市到对数再到相对论&#xff0c;娓娓道来。对于思维空间也给出了一个更容易理解的角度。 作者&#xff1a;米卡埃尔•洛奈…