Spring依赖注入的方式

发布时间:2024年01月03日

在这里插入图片描述

引言

在Spring中,依赖注入是IOC的主要实现方式,也是在开发过程中最常用的方式,那么你有没有考虑过Spring中到底提供了多少中依赖注入的方式呢?对于这些依赖注入的方式又有那些需要注意的地方呢?我们能不能通过对Spring框架进行扩展来对依赖注入进行扩展呢?带着上边这些问题,我们来开启今天的学习之旅🦾

摘要

本文主要介绍Spring的依赖注入,列举了几种依赖注入的方式,包括基于注解的注入、基于XML文件的注入,基于Aware接口回调的注入、基于Bean DefinitionAPI的注入、以及分组注入,对于这些依赖注入的方式分别通过代码示例展示了它们的用法。通过这篇文章的内容可以加深对Spring依赖注入的印象,同时在开发过程中能够更灵活的实现相关的功能。

正文

众所周知,Spring作为一个IOC容器,它的控制反转主要通过DI(依赖注入实现),那么在Spring中我们可以通过那些方式实现DI呢,这里对Spring的依赖注入方式做了简单整理,包括三类常用方式、三类扩展方式,每种类型有包括几种不同的具体实现方法,其中三种常用方式包括

  1. 通过xml文件注入(setter注入、构造器注入、自动绑定)

  2. 基于@Bean注解的自动注入(setter注入、构造器注入)

  3. 使用依赖注入注解(字段注入、方法注入)

三种扩展方式包括

  1. 基于 Aware接口回调注入

  2. 通过 BeanDefinition类注入

  3. 依赖注入的高级用法(@Qualifier注解限定注入、自定义注解注入)

为了方便后续测试,先定义三个类作为后续待注入的Bean,代码如下

/**
 * 人员类
 */
public class PersonEntity {
    //姓名
    private String name;
    //年龄
    private int age;
    //性别
    private Gender gender;

    public static PersonEntity bornMale(String name){
        PersonEntity res = new PersonEntity();
        res.setGender(Gender.MALE);
        res.setAge(1);
        res.setName(name);
        return res;
    }

    public static PersonEntity bornFemale(String name){
        PersonEntity res = new PersonEntity();
        res.setGender(Gender.FEMALE);
        res.setAge(1);
        res.setName(name);
        return res;
    }
    //省略getter、setter、toString方法
}

/**
 * 性别类
 */
public enum Gender {
    MALE,FEMALE;
}

/**
 * 城池类
 */
public class Fortresses {
    //名称
    private String name;
    //守将
    private PersonEntity guard;
    //兵力
    private Long troops;

    public Fortresses() {
    }

    public Fortresses(String name, PersonEntity guard,Long troops) {
        this.name = name;
        this.guard = guard;
        this.troops=troops;
    }
        
    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Fortresses{");
        sb.append("name='").append(name).append('\'');
        sb.append(", guard=").append(guard.getName());
        sb.append(", troops=").append(troops);
        sb.append('}');
        return sb.toString();
    }
    //省略getter、setter方法
}

XML配置文件注入

在Spring配置文件中存在如下配置

<?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 id="caocao" class="top.sunyog.spring.notes.entity.PersonEntity">
        <property name="name" value="曹操"/>
        <property name="age" value="28"/>
        <property name="gender" value="MALE"/>
    </bean>
</beans>

基于xml配置文件的构造函数注入写法如下

<bean id="yanzhou" class="top.sunyog.spring.notes.entity.Fortresses">
    <constructor-arg name="name" value="兖州"/>
    <!--ref指向bean的名称,自动注入bean-->
    <constructor-arg name="guard" ref="caocao"/>
    <constructor-arg name="troops" value="50000"/>
</bean>

基于xml配置文件的setter注入写法如下

<bean id="xuchang" class="top.sunyog.spring.notes.entity.Fortresses" >
    <property name="name" value="许昌"/>
    <property name="guard" ref="caocao"/>
    <property name="troops" value="3000"/>
</bean>

spring提供自动注入的功能,自动注入通过bean标签内的 autowire属性实现,常用的方式有 byName、byType,分别是按Bean名称匹配和按类型匹配,在本例种适合使用按类型匹配

<bean id="xuchang" class="top.sunyog.spring.notes.entity.Fortresses" autowire="byType">
    <property name="name" value="许昌"/>
    <property name="troops" value="3000"/>
</bean>

但是按类型匹配存在一个问题,当同类型的Bean存在多个时,是无法匹配成功的,如增加一个 PersonEntity类型的Bean

<bean id="yuanshao" class="top.sunyog.spring.notes.entity.PersonEntity">
    <property name="name" value="袁绍"/>
    <property name="age" value="30"/>
    <property name="gender" value="MALE"/>
</bean>

这是启动程序会提示如下信息(发现2个同类型的bean)

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'xuchang' defined in class path resource [config/beans-config.xml]: Unsatisfied dependency expressed through bean property 'guard'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'top.sunyog.spring.notes.entity.PersonEntity' available: expected single matching bean but found 2: caocao,yuanshao

一个简单的解决方式是,为其中一个bean添加 primary属性,如下所示

<bean id="caocao" class="top.sunyog.spring.notes.entity.PersonEntity" primary="true">
    <property name="name" value="曹操"/>
    <property name="age" value="28"/>
    <property name="gender" value="MALE"/>
</bean>

这样,所有PersonEntity类型的自动注入都会首先注入 caocao,而其他同类型的bean需要按名称获取。注意:Spring官方是不推荐这种用法的

@Bean注解注入

在Spring中事先提供如下配置

@Bean
public PersonEntity guanyu(){
    PersonEntity res = PersonEntity.bornMale("关羽");
    res.setAge(48);
    return res;
}

基于@Bean注解的setter注入写法如下

@Bean
public Fortresses xiangyang(PersonEntity guanyu){
    Fortresses res = new Fortresses();
    res.setName("襄阳");
    res.setGuard(guanyu);
    res.setTroops(5000L);
    return res;
}

基于@Bean注解的构造函数注入写法

@Bean
public Fortresses jingzhou(PersonEntity guanyu){
    return new Fortresses("荆州",guanyu,80000L);
}

注意:这里的方法参数名称是固定的,Spring默认按照方法名称通过 getBean(name)方法获取到对应的Bean,如果这时 xiangyang这个bean改成这样是会报错的

@Bean
public Fortresses xianigyang(PersonEntity caoren){
    Fortresses res = new Fortresses();
    res.setName("襄阳");
    res.setGuard(caoren);
    res.setTroops(5000L);
    return res;
}

需要明确的定义名称为 caoren的bean才能修正

@Bean
public PersonEntity caoren(){
    PersonEntity res = PersonEntity.bornMale("曹仁");
    res.setAge(45);
    return res;
}

这时通过如下代码测试

@Component
public class BeanDIComponent implements ApplicationRunner, BeanFactoryAware {
    private BeanFactory beanFactory;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.beanFactory.getBeanProvider(Fortresses.class).forEach(o-> System.out.println(o));
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory=beanFactory;
    }
}

打印结果如下

Fortresses{name='荆州', guard=关羽, troops=80000}
Fortresses{name='襄阳', guard=曹仁, troops=5000}

DI注解方式注入

在Spring中,允许通过注解标注的方式将需要的Bean注入到对应的属性中,可实现属性注入的注解有以下几种

  1. Spring框架提供的@Autowired
  2. Java提供的@Resource
  3. JSR330标准提供的@Inject,使用此注解需要引入 javax.inject依赖包,maven的写法如下
<!--jsr330-->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

以上三个注解的简单用法为(其中属性名称即是bean的名称)

@Autowired
private PersonEntity guanyu;

@Resource
private PersonEntity caoren;

@Inject
private Fortresses jingzhou;

如果属性名称与bean的名称不对应,可以通过指定Bean名称的方式实现注入

@Autowired
@Qualifier(value = "guanyu")
private PersonEntity person_1;

@Resource(name = "caoren")
private PersonEntity person_2;

@Inject
@Qualifier(value = "jingzhou")
private Fortresses city_1;

针对同类型的bean,可以使用这三个注解获取到所有bean的集合

@Autowired
//@Inject
//@Resource
private Collection<PersonEntity> persons;

除此之外,这三个注解还支持在方法上实现自动注入,使用方法类似于 @Bean,需要注意的是,这里自动注入的是方法的入参。

//自动注入
//@Inject
//@Resource
@Autowired
private void initCity2(Fortresses xiangyang){
    this.city_2=xiangyang;
}

//按自定义名称注入
//@Autowired
//@Inject
//@Qualifier(value = "jingzhou")
@Resource(name = "xiangyang")
private void iniitCity3(Fortresses city){
    this.city_3=xiangyang;
}

Aware接口回调注入

基于Aware接口回调函数也可以实现依赖注入,只是这种方法只能实现特定类型的依赖注入,本文中 BeanDIComponent类的代码中实现的 BeanFactoryAware接口就是其中一个,Spring中能够使用的Aware接口包括:

表:Spring中内置的Aware接口
接口名称说明
BeanFactoryAware获取Spring容器BeanFactory对象
ApplicationContextAware获取Spring应用上下文
EnvironmentAware获取Environment对象
ResourceLoaderAware获取资源加载器对象,ResourceLoader
BeanClassLoaderAware获取加载当前Bean Class的ClassLoader
BeanNameAware获取当前Bean的名称
MessageSourceAware获取MessageSource对象
ApplicationEventPublisherAware用于处理Spring事件
EmbeddedValueResolverAware获取StringValueResoulver对象,用于处理占位符

这里以 BeanFactoryAwareApplicationContextAware为例展示Aware接口的用法

@Component
public class BeanDIComponent implements ApplicationRunner, BeanFactoryAware, ApplicationContextAware {
    private BeanFactory beanFactory;
    private ApplicationContext context;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(this.beanFactory.getClass());
        System.out.println(this.context.getClass());
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory=beanFactory;
    }

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

启动程序后的打印结果为

class org.springframework.beans.factory.support.DefaultListableBeanFactory
class org.springframework.context.annotation.AnnotationConfigApplicationContext

注意:这里的打印结果是依赖于所使用的Spring容器的,这里启动的是Springboot项目

BeanDefinition API注入

基于上例可知,在Spring中我们可以获取到 AnnotationConfigApplicationContext这个容器类,这个类是Spring中注解配置的主要容器类,它的内部提供了Bean和BeanDefinition注册的方法,利用这两个方法也可以实现Bean的配置,可配置也就可注入

private void apiDependencyInject(){
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Fortresses.class);
    //注入名称为zhangliao的bean
    builder.addPropertyReference("guard","zhangliao");
    builder.addPropertyValue("name","合肥");
    builder.addPropertyValue("troops",3000L);
    if (context instanceof AnnotationConfigApplicationContext){
        ((AnnotationConfigApplicationContext) context).registerBeanDefinition("hefei",builder.getBeanDefinition());
    }
}

@Override
public void run(ApplicationArguments args) throws Exception {
    this.apiDependencyInject();
    this.beanFactory.getBeanProvider(Fortresses.class).forEach(o-> System.out.println(o));

}

/**
 * 配置Bean
 */
@Bean
public PersonEntity zhangliao(){
    PersonEntity res = PersonEntity.bornMale("张辽");
    res.setAge(40);
    return res;
}

输出结果为

Fortresses{name='合肥', guard=张辽, troops=3000}

ApplicationContext中可以通过 registerregisterBean方法注册Bean,本文主要介绍Bean的注入,这里就不过多介绍了

依赖注入的高级用法

对bean分组

上文中介绍了 @Qualifier注解的使用方法,实际上,@Qualifier注解有将容器中的bean分组的功能,使用 @Qualifier注解对下列bean进行分组

@Bean
@Qualifier("cao")
public PersonEntity zhangliao(){
    PersonEntity res = PersonEntity.bornMale("张辽");
    res.setAge(40);
    return res;
}

@Bean
@Qualifier("liu")
public PersonEntity guanyu(){
    PersonEntity res = PersonEntity.bornMale("关羽");
    res.setAge(48);
    return res;
}

@Bean
@Qualifier("cao")
public PersonEntity caoren(){
    PersonEntity res = PersonEntity.bornMale("曹仁");
    res.setAge(45);
    return res;
}

对于按类型获取bean的集合的功能,可以实现按分组获取,代码如下

@Autowired
@Qualifier("cao")
private Collection<PersonEntity> persons;

@Override
public void run(ApplicationArguments args) throws Exception {
    this.persons.forEach(o-> System.out.println(o));
}

输出结果为

PersonEntity{name='张辽', age=40, gender=MALE}
PersonEntity{name='曹仁', age=45, gender=MALE}

修改代码 @Qualifier("liu")后的输出结果为

PersonEntity{name='关羽', age=48, gender=MALE}

除了通过 @Qualifier 注解按名称分组之外,还支持自定义注解分组,只需要在自定义注解上使用 @Qualifier标注即可,之类新增三个自定义注解说明

  1. 创建注解类
@Qualifier
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Wei {
}


@Qualifier
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Shu {
}

@Qualifier
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Wu {
}
  1. 修改bean的配置
@Bean
@Qualifier("cao")
@Wei
public PersonEntity zhangliao(){
    PersonEntity res = PersonEntity.bornMale("张辽");
    res.setAge(40);
    return res;
}

@Bean
@Shu
@Qualifier("liu")
public PersonEntity guanyu(){
    PersonEntity res = PersonEntity.bornMale("关羽");
    res.setAge(48);
    return res;
}

@Bean
@Qualifier("cao")
@Wei
public PersonEntity caoren(){
    PersonEntity res = PersonEntity.bornMale("曹仁");
    res.setAge(45);
    return res;
}

@Bean
@Wu
public PersonEntity sunjian(){
    PersonEntity res = PersonEntity.bornMale("孙坚");
    res.setAge(30);
    return res;
}
  1. 测试代码
@Autowired
@Wu//这里只列出@Wu注解的bean,@Wei和@Shu标记的bean的行为类似
private Collection<PersonEntity> persons;

@Override
public void run(ApplicationArguments args) throws Exception {
    this.persons.forEach(o-> System.out.println(o));
}

输出结果为

PersonEntity{name='孙坚', age=30, gender=MALE}

自定义依赖注入注解

在上文中我们列出了Spring中常用的三个依赖注入注解,除此之外,Spring还支持自定义依赖注入注解。由于依赖注入注解的功能是在 AutowiredAnnotationBeanPostProcessor这个类中实现的,只要修改了这个类的功能(向其中添加我们自定义的注解)即可实现通过自定义注解实现依赖注入的功能

自定义依赖注入注解的步骤如下

  1. 自定义注解
  2. 配置 AutowiredAnnotationBeanPostProcessor这个bean

自定义注解

@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MagicalInject {
}

配置

@Configuration
public class AnnoAutowiredConfig {
    @Bean
    public AutowiredAnnotationBeanPostProcessor myInjectAnnotationBeanPostProcessor() {
        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setAutowiredAnnotationType(MagicalInject.class);
        return processor;
    }
}

使用自定义注解

@MagicalInject
@Qualifier("sunjian")
private PersonEntity person_3;

@Override
public void run(ApplicationArguments args) throws Exception {
    System.out.println(person_3);
}

自定义注解也支持按名称注入

@MagicalInject
private PersonEntity sunjian;

@Override
public void run(ApplicationArguments args) throws Exception {
    System.out.println(sunjian);
}

总结

本文介绍了Spring支持的几种依赖注入方式,其中三种常用方式,三种扩展方式。除了依赖注入之外,结合 @Qualifier注解还可以实现对spring bean进行分组,实现更加自由、多样的依赖注入。文中有两个重点内容需要关注

  1. 基于Aware接口实现的Spring内置Bean的注入
  2. 通过自定义注解扩展Spring的依赖注入

📩 联系方式
邮箱: qijilaoli@foxmail.com
掘金: 我的掘金
CSDN: 我的CSDN
微信公众号:奇迹老李
?版权声明
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问我的博客首页

文章来源:https://blog.csdn.net/qq_43408971/article/details/135366937
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。