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

前言

源码在我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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://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;}@Overridepublic 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/utilhttp://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 {@Autowiredprivate Optional<User> optionalUser;@Autowiredprivate ObjectFactory<User> userObjectFactory;/*** @Autowired 注入流程* 1. 先按照名称查找* 2. 再按照类型查找*/@Autowiredprivate User user1;@Autowiredprivate Collection<User> users;@Autowiredprivate Map<String, User> userMap;@Autowiredprivate User[] userArrays;private User userFromMethodInjection;@Autowiredpublic void setUserFromMethodInjection(User user) {this.userFromMethodInjection = user;}@Autowiredprivate 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")@Primarypublic User user2() {User user = new User();user.setId(2L);user.setUsername("Luna");return user;}@Beanpublic 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);// 从 可解析依赖 中 查找是否有该 类型的 依赖,如果有也被考虑进 resultfor (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 判断是否可以加入到 resultfor (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.mzph.cn/news/577573.shtml

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

相关文章

嵌入式 C 语言大神的进阶之路

C语言可以说是一种"古老"的编程语言&#xff0c;也是目前嵌入式中主流的编程语言&#xff0c;没有C语言就没有今天的各种嵌入式系统以及操作系统等等。 C语言虽然说是编程开发的基础&#xff0c;那到底你掌握到了什么程度呢&#xff1f; 下面我们一起看看C语言熟练到…

第十三节TypeScript 元组

1、简介 我们知道数组中元素的数据类型一般都是相同的&#xff08;any[]类型的数组可以不同&#xff09;&#xff0c;如果存储的元素类型不同&#xff0c;则需要使用元组。 元组中允许存储不同类型的元素&#xff0c;元组可以作为参数传递给函数。2、创建元组的语法格式&#x…

python:改进型鳟海鞘算法(SSALEO)求解23个基本函数

一、改进型鳟海鞘算法SSALEO 改进型鳟海鞘算法&#xff08;SSALEO&#xff09;由Mohammed Qaraad等人于2022年提出。 参考文献&#xff1a;M. Qaraad, S. Amjad, N. K. Hussein, S. Mirjalili, N. B. Halima and M. A. Elhosseini, "Comparing SSALEO as a Scalable Larg…

阻抗控制中的弹簧与阻尼影响分析

阻抗控制是一种机器人控制方法&#xff0c;通过调整机器人的阻抗来实现对机器人的精准控制。在阻抗控制中&#xff0c;弹簧和阻尼是两个重要的参数&#xff0c;它们对机器人的性能和稳定性有很大的影响。 弹簧代表机器人的刚度和弹性&#xff0c;而阻尼代表机器人的阻尼特性&a…

DRF从入门到精通四(视图基类、GenericAPIView的视图扩展类、视图子类、视图集父类、子类)

文章目录 前言一、视图基类APIView基类GenericAPIView通用视图基类 二、GenericAPIView的视图拓展类1.ListModelMixin2.CreateModelMixin3.RetrieveModelMixin4.UpdateModelMixin5.DestroyModelMixin 三、GenericAPIView的视图子类ListCreateAPIViewRetrieveUpdateDestroyAPIVi…

中庸 原文与译文

《中庸》是中国古代论述人生修养境界的一部道德哲学专著&#xff0c;是儒家经典著作之一&#xff0c;原属《礼记》第三十一篇&#xff0c;相传为战国时期子思所作。 其内容肯定“中庸”是道德行为的最高标准&#xff0c;认为“至诚”则达到人生的最高境界&#xff0c;并提出“…

C语言中关于if else的理解

if else我们可以理解为 if(条件1) //如果条件1成立 语句1&#xff1b; //执行语句1 else //如果条件1不成立 语句2; //执行语句2 这是一个经典的if els…

大数据技术学习笔记(十一)—— Flume

目录 1 Flume 概述1.1 Flume 定义1.2 Flume 基础架构 2 Flume 安装3 Flume 入门案例3.1 监控端口数据3.2 实时监控单个追加文件3.3 实时监控目录下多个新文件3.4 实时监控目录下的多个追加文件 4 Flume 进阶4.1 Flume 事务4.2 Flume Agent 内部原理4.3 Flume 拓扑结构4.3.1 简单…

1861_什么是H桥

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1861_什么是H桥 H桥电路可以…

蓝桥杯c/c++程序设计——数位排序

数位排序【第十三届】【省赛】【C组】 题目描述 小蓝对一个数的数位之和很感兴趣&#xff0c;今天他要按照数位之和给数排序。 当两个数各个数位之和不同时&#xff0c;将数位和较小的排在前面&#xff0c;当数位之和相等时&#xff0c;将数值小的排在前面。 例如&#xff0…

reactive和TypeScript标注数据类型-ts使用方法

一、vite项目中<script setup lang"ts"> : lang"ts" 是表明支持ts校验&#xff08;ts 全称typescript,是es6语法&#xff0c;是javascript的超集强类型编程语言&#xff0c;类似java&#xff0c;定义变量类型后&#xff0c;赋值类型不一致&#xff0…

创建一台可以安装linux系统的虚拟机的流程

1、打开vmware-->点击左上角文件-->新建虚拟机-->自定义 2、默认选择&#xff0c;直接下一步 3、选中稍后安装操作系统&#xff0c;然后下一步 4、选中Linux&#xff0c;然后下拉框选择CentOS7(64位) 5、设置虚拟机名称及存储位置 6、设置虚拟机处理器数量及核心数 7、…

选择排序之C++实现

描述 选择排序&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法。它的基本思想是&#xff1a;每一轮从待排序的数据中选择最小&#xff08;或最大&#xff09;的一个元素&#xff0c;然后与待排序数据的第一个元素交换位置。对剩余未排序的数据重复这个过程&a…

【【IIC模块Verilog实现---用IIC协议从FPGA端读取E2PROM】】

IIC模块Verilog实现–用IIC协议从FPGA端读取E2PROM 下面是 design 设计 I2C_dri.v module IIC_CONTROL #(parameter SLAVE_ADDR 7b1010000 , // E2PROM 从机地址parameter CLK_FREQ 26d50_000_000 , // 50MHz 的时钟频率parameter …

Ensp dhcp全局地址池(配置命令 + 实例)

使用DHCP的好处&#xff1a;减少管理员的工作量、避免输入错误的可能、避免ip冲突 DHCP报文类型&#xff1a; DHCP DISCOVER:客户端用来寻找DHCP服务器 DHCP OFFER:DHCP服务器用来响应DHCP DISCOVER报文&#xff0c;此报文携带了各种配置信息 DHCP REQUEST:客户端配置请求确…

Python机器学习 – 用最小二乘法实现散点图

Python机器学习 – 用最小二乘法实现散点图 Machine Learning in Python – Implement Scatter Plot with Least Squares By JacksonML 1. 最小二乘法定义 最小二乘法是由A.M.Legendre&#xff08;勒让德&#xff09;先生最早提出的。他在1805年&#xff0c;通过《计算彗星轨…

3. 结构型模式 - 组合模式

亦称&#xff1a; 对象树、Object Tree、Composite 意图 组合模式是一种结构型设计模式&#xff0c; 你可以使用它将对象组合成树状结构&#xff0c; 并且能像使用独立对象一样使用它们 问题 如果应用的核心模型能用树状结构表示&#xff0c; 在应用中使用组合模式才有价值。 …

ISP 状态机轮转和bubble恢复机制学习笔记

1 ISP的中断类型 ISP中断类型 SOF: 一帧图像数据开始传输 EOF: 一帧图像数据传输完成 REG_UPDATE: ISP寄存器更新完成(每个reg group都有独立的这个中断) EPOCH: ISP某一行结尾(默认20)就会产生此中断 BUFFER DONE: 一帧图像数据ISP完全写到DDR了 2 ISP驱动状态机 通过camer…

三菱PLC开关量防抖滤波功能块

开关量防抖滤波功能块梯形图和SCL代码请参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/134936233https://rxxw-control.blog.csdn.net/article/details/134936233三菱PLC防抖滤波的另一种写法如下 https://rxxw-control.blog.csdn.net/article/det…

用CHAT了解更多知识点

问CHAT&#xff1a;什么是硅基生命和碳基生命&#xff1f; CHAT回复&#xff1a;硅基生命和碳基生命是两种理论性的生物体类型&#xff0c;这些生物体主要是由硅或碳元素以及其他元素构成的。 碳基生命是我们当前所熟知的生命形式。碳元素能够形成稳定且复杂的分子&#xff0c;…