Spring 依赖注入的方式,你了解哪些?

前言

依赖查找和依赖注入是 Spring 实现 IoC 容器提供的两大特性,相对于依赖查找,Spring 更推崇的是使用依赖注入,本篇先对 Spring 中依赖注入的几种方式进行介绍,后续再分享其实现。

依赖注入方式

如果在 A 类中使用到了 B 类的实例,那么我们就说 A 依赖 B。依赖的位置包括字段、构造器方法参数、普通方法参数等,Spring 都对其进行了支持。Spring 针对依赖提供了自动依赖注入和手动依赖注入两种方式,下面对其进行介绍。

自动依赖注入

自动依赖注入是 Spring 早期提供的能力,使用自动依赖注入可以避免显式的配置属性和构造函数参数的依赖,Spring 中提供了四种自动依赖注入的模式。具体如下。

模式名称说明
no不进行自动依赖注入。Spring 推荐使用的默认注入方式,通过这种方式然后手动设值依赖关系可以让组件之间的关系更加清晰。
byName按照属性名称进行依赖注入。Spring 将查找和属性名称对应的 bean,然后通过 setXXX 方法设置到属性中。( 一个 bean 存在 setXXX 方法即可,并不要求类中一定存在对应的字段。 )
byType按照属性类型进行依赖注入。Spring 将查找和属性类型对应的 bean,然后通过 setXXX 方法设置到属性中。如果 Spring 查到了多个 bean 则会抛出异常。如果查不到对应的 bean 则不会发生异常。
constructor特殊的 byType ,用于构造方法参数。如果不存在对应的 bean,Spring 将抛出异常。

Spring 早期曾经有一个 autodetect 的自动依赖注入方式,用于自动探测依赖注入方式,如果存在带参数的构造方法则使用 constructor 自动依赖注入,否则使用 byType 自动依赖注入,在 Spring 3.0 已废弃。

自动依赖注入可以通过 XML 配置文件或 @Bean 注解中进行指定。
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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--autowire 可指定 no、default、byName、byType、constructor-->
    <bean class="com.zzuhkp.Bean" autowire="no"/>

</beans>

@Bean 指定自动依赖注入的示例如下。

@Configuration
public class Config {

	// 通过 @Bean 的 autowire 属性指定自动依赖注入,可以指定 Autowire.NO、Autowire.BY_NAME、Autowire.BY_TYPE
    @Bean(autowire = Autowire.NO)
    String bean() {
        return "zzuhkp";
    }

}
自动依赖注入的优势

任何事物都有两面性,那么自动依赖注入自然也有其优势和限制。自动依赖注入优点如下。

  • 自动依赖注入避免了指定属性和构造器参数。
  • 在类中添加新的依赖项时,不必修改依赖,在开发中比较有用。
自动依赖注入的限制

虽然自动依赖注入避免了显式指定依赖项,但是其仍有一些不足,因此 Spring 默认不开启自动依赖注入。其具备的一些限制如下。

  • 显式设置的属性和构造方法参数依赖将会覆盖自动依赖注入,无法装配简单的属性,如基本类型、String、Class 等。
  • 自动依赖注入将导致对象之间的关系不如显式依赖注入清晰。
  • 自动依赖注入将影响从 Spring 容器中生成文档的工具。
  • byType 类型的自动依赖注入,如果存在多个类型相同的 bean 不会影响注入数组、集合、Map 等,而注入单个 bean 对象由于歧义 Spring 则会抛出异常。

手动依赖注入

手动依赖注入相对自动依赖注入来说比较灵活,手动依赖注入可以通过 XML 配置文件、Java 注解或 Spring 提供的 API 来完成,而从具体依赖注入的方法来说又包括 setter、字段、构造器、方法、接口回调,下面分别予以说明。

setter 依赖注入

setter 依赖注入是通过调用 bean 的 setXXX 方法设置 bean 的依赖。自动依赖注入时设置注入类型为 byName 或 byType 可以达到 setter 注入的目的,而手动依赖注入则可以通过 XML 手动配置配置依赖或通过 Spring API 编程设置。

XML 手动 setter 依赖注入示例如下。

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

    <bean class="com.zzuhkp.Bean">
        <!--设置 setter 依赖注入-->
        <property name="prop" ref="prop2"/>
    </bean>

    <bean id="prop" class="com.zzuhkp.Prop">
        <property name="name" value="this is name"/>
        <property name="value" value="this is value"/>
    </bean>
  
</beans>

Spring API setter 依赖注入示例如下,和上述的 XML 配置向对应。

public class App {

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

        // 手动创建 BeanDefinition
        AbstractBeanDefinition prop = BeanDefinitionBuilder.genericBeanDefinition(Prop.class)
            // 添加 setter 注入的属性值
            .addPropertyValue("name", "this is name")
            .addPropertyValue("value", "this is value")
            // 获取 BeanDefinition
            .getBeanDefinition();
        // 向 Spring 中注入
        context.registerBeanDefinition("propBeanName", prop);

        AbstractBeanDefinition bean = BeanDefinitionBuilder.genericBeanDefinition(Bean.class)
            .addPropertyReference("prop", "propBeanName")
            .getBeanDefinition();
        context.registerBeanDefinition("beanName", bean);

        context.refresh();

        System.out.println(context.getBean("beanName"));

        context.close();
    }

}

Spring API setter 依赖注入示例中通过创建 BeanDefinition,并设置 BeanDefinition 中的属性来达到 setter 依赖注入的目的。关于 BeanDefinition 的知识参见 《掌握 Spring 必须知道的 BeanDefinition》

字段依赖注入

字段依赖注入是通过反射设置对象的字段值来实现。字段依赖注入通过在 bean 的字段上添加 @Autowired 、@Resource 或者 @Inject 来完成。关于 @Autowired 和 @Resource 的区别,可参见前面的文章 《Spring 中 @Autowired 和 @Resource 有什么区别?》

字段依赖注入的示例如下。

@Component
public class Bean {

	// @Resource
    @Autowired
    private Prop prop;


    public Prop getProp() {
        return prop;
    }

    public Bean setProp(Prop prop) {
        this.prop = prop;
        return this;
    }

    @Override
    public String toString() {
        return "Bean{" +
            "prop=" + prop +
            '}';
    }
}
构造器依赖注入

构造器依赖注入是注入 Spring Bean 实例化时需要的参数。可以指定自动依赖注入的方式为 constructor 实现,也可以指定构造器参数、通过 @Autowired 注解或者 Spring API 手动依赖注入。

通过 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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.zzuhkp.Bean">
        <!--构造器依赖注入-->
        <constructor-arg ref="prop"/>
    </bean>

    <bean id="prop" class="com.zzuhkp.Prop">
        <property name="name" value="this is name"/>
        <property name="value" value="this is value"/>
    </bean>

</beans>

通过 @Autowired 进行构造器依赖注入示例如下。

public class Bean {

    private Prop prop;

    public Bean(@Autowired Prop prop) {
        this.prop = prop;
    }
}

通过 Spring API 进行手动构造器依赖注入的示例如下。

public class App {

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

        // 手动创建 BeanDefinition
        AbstractBeanDefinition prop = BeanDefinitionBuilder.genericBeanDefinition(Prop.class)
            // 添加 setter 注入的属性值
            .addPropertyValue("name", "this is name")
            .addPropertyValue("value", "this is value")
            // 获取 BeanDefinition
            .getBeanDefinition();
        // 向 Spring 中注入
        context.registerBeanDefinition("prop", prop);

        AbstractBeanDefinition bean = BeanDefinitionBuilder.genericBeanDefinition(Bean.class)
            // 添加构造器所需的依赖
            .addConstructorArgReference("prop")
            .getBeanDefinition();
        context.registerBeanDefinition("bean",bean);

        context.refresh();

        System.out.println(context.getBean("bean"));

        context.close();
    }

}
方法依赖注入

方法依赖注入和 setter 依赖注入类似,但是不限制方法必须为 setter 方法。方法依赖注入只能通过注解完成,在方法上添加 @Autowired、@Resource 或者 @Inject 可以为只有一个参数的方法进行依赖注入。配置类中 @Bean 注解定义的 bean 则会按照 constructor 自动依赖注入的方式进行处理。

方法依赖注入的示例如下。

@Component
public class Bean {

    private Prop prop;

    public Bean(@Autowired Prop prop) {
        this.prop = prop;
    }

    // @Resource
    // @Inject
    @Autowired
    public void prop(Prop prop) {
        this.prop = prop;
    }
}
回调依赖注入

回调依赖注入依托于 Spring Bean 的生命周期,需要实现 Spring 提供的 XxxAware 接口,在 Spring 生命周期的某一阶段会调用接口中定义的方法,从而拿到方法中的参数。以 ApplicationContextAware 为例,示例如下。

@Component
public class Bean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

依赖注入的来源

和依赖查找一样,依赖注入也具有自己的来源。与依赖查找不同的是,依赖注入的来源除了 BeanDefinition、单例 bean,还包括 Spring 中一些游离的对象,这些对象非 Spring 管理,Spring 只是简单记录了其引用。此外,通过 @Value 注解,Spring 还能够注入外部化配置。有关外部化配置,可参考文章 《Spring 中的 Environment 、Profile 与 PropertySource》

游离对象的注册,感兴趣的小伙伴可查阅源码ConfigurableListableBeanFactory#registerResolvableDependency,而依赖注入最终则会委托给AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor,String,Set<String>, TypeConverter) 解析依赖对象,由于其实现相对复杂,本篇不再展开。

总结

依赖注入是我们使用 Spring 最常用的方式,依赖注入包括自动依赖注入和手动依赖注入,具体又分为 setter 依赖注入、字段依赖注入、构造器自动依赖注入、方法依赖注入、接口回调。依赖注入的来源包括 BeanDefinition、单例 bean、游离对象、外部化配置。由于篇幅问题,本篇未对依赖处理过程展开说明,后续再进行分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值