Spring:DI思想的详细图解

什么是DI

DI 就是依赖注入,,简单来讲,其实就是,把 IoC 容器中的 bean 对象取出来直接放到某个类的属性中,不再需要我们自己手的的new对象。例如下面代码,

在 Controller 包中创建了一个 SayHiContreoller 类,在 ServiceDemo 包中创建了一个SayHiService 类,现在,想要在 SayHiController 类中的 sayHi 方法里,调用 SayHiService 类中的sayHi()方法,所以,就需要手动的New一个SayHiService对象,然后再调用sayHi()方法,那么,现在不再手动的new对象了,而使用依赖注入的方式。

1.手动创建对象的方式

在这里插入图片描述

2.使用DI的方式

下图 Ioc 容器在创建 SayHiController 对象时,需要 SayHiService 对象,所以,通过 DI 的方式提供给 SayHiController 运行时所需要的资源(SayHiService对象)

在这里插入图片描述

关于依赖注入,不仅仅只有 @Autowired 一个注解,Spring 给我们提供了三种方式,如下

依赖注入的三种方式

  • 属性注入(Filed Injection)
  • 构造方法注入(Constructor Injection)
  • Setter 注入(Setter Injection)

1.属性注入

“属性注入” 就是使用 @Autowired 注解,也就是上面我们演示的,将SayHiService 类注入到 SayHiController 类中。

SayHiController 类的代码实现:

@RestController
public class SayHiController {
   
    @Autowired
    private SayHiService service;
    
    public void sayHi() {
        service.sayHi();
    }
}

SayHiService 类的代码实现:

@Service
public class SayHiService {
    public void sayHi() {
        System.out.println("hello SayHiService");
    }
}

获取 SayHiController 中的 sayHi()

@SpringBootApplication
public class SpringDemo7Application {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ConfigurableApplicationContext context = SpringApplication.run(SpringDemo7Application.class, args);
        //从上下文中获取到SayHiController对象
        SayHiController bean = (SayHiController)context.getBean(SayHiController.class);
        bean.sayHi();
    }

}

在这里插入图片描述

去掉 @AutoWired 注解,再次运行,就会出现空指针异常

在这里插入图片描述

2.构造方法注入

构造方法注入是在类的构造方法中实现注入的,如下代码:

@RestController
public class SayHiController {

    private SayHiService service;
    @Autowired
    public SayHiController(SayHiService service) {
        this.service = service;
    }

    public void sayHi() {
        service.sayHi();
    }
}

在这里插入图片描述

注意:如果类中只有一个构造方法,@Autowired 注解可以省略,但如果有多个注解,那么就要使用 @Autowired 注解明确的指定使用哪个构造方法,如下代码演示:

3.Setter 注入

Setter 注入和属性的 Setter 方法类似,只不过要在set方法上使用 @Autowired 注解修饰,代码如下:

@RestController
public class SayHiController {

    private SayHiService service;
    @Autowired
    public void setService(SayHiService service) {
        this.service = service;
    }

    public void sayHi() {
        service.sayHi();
    }
}

在这里插入图片描述

同样,如果不加 @Autowired 注解就会报空指针异常

在这里插入图片描述

三种注入的优缺点:

  • 属性注入

    • 优点:简洁,适用方便,直接适用 @Autowired 修饰即可

    • 缺点:

      • 只能用于 IoC 容器,如果是非 Ioc 容器则不可用,并且只有在使用的时候才会出现空指针异常

      • 不能注入一个 final 修饰的属性,如下图:

        在这里插入图片描述

        但是,如果想要注入一个 final 修饰的属性,有一个要求,需要满足以下任意一个条件:

        1,在属性声明时要完成初始化,但是,如果声明时就已经初始化了,就不需要再进行注入了呀,所以这条等于没用

        2,要在构造方法中进行赋值,如果在构造方法中进行赋值的话,直接使用构造函数注入就行了呀,所以这条也没用。

        所以,这条缺点与下面的构造方法注入对比来讲。

  • 构造方法注入

    • 优点:
      • 可以注入 final 修饰的属性,因为它本来就是在构造方法中进行赋值的
      • 注入的对象不会被修改,因为依赖对象在使用前就已经被初始化了
      • 依赖对象在使用前一定会被初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会被执行的
      • 通用性好,构造方法是JDK支持的,所以更换任何框架,它都是适用的
    • 缺点:
      • 注入多个对象时,代码会比较繁琐
  • Setter 注入

    • 优点:方便在类实例化之后,重新对该对象进行配置或注入
    • 缺点:
      • 不能注入一个 final 修饰的属性
      • 注入对象可能会被改变,因为setter方法可能会给多次调用,就有被修改的风险

@Autowired 存在的问题

使用方法注解对同一个类型创建多个 bean对象时,使用 @Autowired 就会报错,代码如下:

创建一个 User 类

@Data
public class User {
    private String name;
    private int age;
}

创建一个 BeanConfig 类, 使用方法注解在该类中创建两个 User 类型的对象

@Service
public class BeanConfig {
    @Bean
    public User user1() {
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("李四");
        user.setAge(20);
        return user;
    }
}

注入Bean对象

@RestController
public class SayHiController {
    @Autowired
    private User user;

    public void sayHi() {
        System.out.println(user);
    }
}

在使用 @Autowired 进行注入时,就会报出以下错误,表示有多个“User”类型的bean

在这里插入图片描述

如果将变量名 user 改成 user1 或者 user2 就可以运行成功

在这里插入图片描述

通过上述的验证,就可以发现,当同样类型存在多个bean对象时,就可能会出错,而 @Autowired 是先根据 bean 名称来获取bean对象,如果匹配到了,则成功,如果没有匹配到,就会根据类型进行匹配,如果匹配到多个,就会报错。但是通常不会修改变量名来获取某个bean,而是通过其它的手段来指定bean的名称。解决方法如下:

解决方案

Spring 为我们提供了以下几种方案:

  • @Primary
  • @Qualifier
  • @Resource

使用 @Primary 注解:当存在多个相同类型的bean对象时,加上 @Primary 注解来确定默认的 bean 对象

@Service
public class BeanConfig {
    
    @Primary //指定bean为默认bean的实现
    @Bean
    public User user1() {
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("李四");
        user.setAge(20);
        return user;
    }
}

在这里插入图片描述

使用 @Qualifier 注解:指定当前要注入的bean的名称,在 @Qualifier 的 value 属性上,指定注入bean的名称

注意,@Qualifier 注解不能单独使用,要配合 @Autowired 注解使用,如下图:

@RestController
public class SayHiController {
    
    @Qualifier("user2")
    @Autowired
    private User user;
    public void sayHi() {
        System.out.println(user);
    }
}

在这里插入图片描述

使用 @Resource 注解:按照 bean 的名称进行注入。通过 @Resource 的name属性来指定要注入的bean的名称,代码如下:

@RestController
public class SayHiController {
    
    @Resource(name = "user2")
    private User user;
    public void sayHi() {
        System.out.println(user);
    }
}

在这里插入图片描述

而且,@Resource 注解是属于 JDK 里的,而不是属于 Spring 的,Spring 只是通过其他方式进行了实现,所以,就可以知道在Spring之前就有了注解,如下图:

在这里插入图片描述

@Autowired 和 @Resource 的区别

1.@Autowired 是 Spring 提供的,@Resource 是 JDK 提供的

2.@Autowired 是先根据名称来获取bean对象,如果匹配到了,则成功,如果没有匹配到,就会根据类型进行匹配,如果匹配到多个,检查有没有通过 @Qualifier指定名称,如果没有,则报错,@Resource 是按照名称注入的,相比 @Autowired 来说,@Resource 支持更多的参数设置,例如name设置,根据名称获取Bean

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值