【Spring】DI(依赖注入)详解:属性注入@Autowired(超详细)、构造方法注入、Setter注入

发布于:2025-04-17 ⋅ 阅读:(36) ⋅ 点赞:(0)

1.DI(依赖注入)介绍

1.1DI是什么?

DI(Dependency Injection,依赖注入) 是 Spring 框架中实现 IoC(控制反转)的一种核心机制。如果说 IoC 是一种设计思想,告诉我们“把控制权交给容器”,那么 DI 就是具体的“施工队”,负责把依赖关系“注入”到对象中。简单来说,DI 让对象不再自己创建或查找依赖,而是由 Spring 容器主动提供

想象你在咖啡店点一杯拿铁,传统方式是你自己买咖啡豆、牛奶,回家磨粉、蒸奶、拉花;而有了 DI,就像你告诉服务员“我要一杯拿铁”,她直接把成品端给你。你不用关心原料从哪来,咖啡机怎么操作,只管享受结果。DI 在 Spring 中也是如此:容器负责准备好依赖(比如数据库连接、服务类),然后通过某种方式“送”到你的对象里。

一句话概括:DI 是 Spring 实现 IoC 的手段,通过将依赖注入到对象中,降低耦合,提升灵活性。

1.2DI和IoC的关系

DI 是 IoC 的具体实现方式之一。IoC 是一种思想,强调“控制反转”,即对象的创建和依赖管理由外部(容器)接管。而 DI 是实现这种反转的常见手段,通过“注入”方式让对象获取依赖,而不是自己去“拿”。

在 Spring 中,DI 负责:
创建对象:容器实例化 Bean。
管理依赖:根据配置(注解、XML 或 Java 配置),将所需的 Bean 注入到目标对象。
降低耦合:对象无需关心依赖的创建细节,只需声明需要什么。

生活比喻:IoC 像你决定把家务外包给管家,DI 是管家具体干活的方式,比如他帮你把洗好的衣服(依赖)放进衣柜(对象)。

1.3 什么是依赖?

在软件开发中,依赖(Dependency) 指的是一个对象(或模块)为了完成其功能,需要依赖的另一个对象(或模块)。简单来说,如果对象 A 的工作需要对象 B 的支持,那么 B 就是 A 的依赖。依赖描述了一种“需要”的关系,体现了对象之间的协作。

在 Spring 的上下文中,依赖通常是 IoC 容器管理的 Bean(对象),通过依赖注入(DI)机制,容器将这些依赖提供给需要它们的其他 Bean。这种机制让对象之间的关系更松散、更灵活。

生活化比喻:想象你在做披萨,面团、奶酪和番茄酱是你的“依赖”。没有这些材料,你没法完成披萨。传统做法是你自己去买材料(硬编码依赖),而 Spring 的 DI 就像一个助手,自动把材料送到你面前(注入依赖),你只管专注烤披萨。

示例:
UserController类中有成员变量UserService,可以说UserServiceBean是UserControllerBean的依赖

UserService类:

@Service
public class UserService {
    public void doUserService() {
        System.out.println("doUserService...");
    }
}

UserController类:

@Controller
public class UserController {
	// UserController依赖UserService
    private UserService userService;
    
    public void doUserController(){
        System.out.println("douserController...");
    }
}

2.依赖注入

2.1属性注入@Autowired

在创建成员变量的上面写@Autowried注解就是注入依赖:

UserService类:

@Service
public class UserService {
    public void doUserService() {
        System.out.println("doUserService...");
    }
}

UserController类:

@Controller
public class UserController {
	// 注入UserService依赖
	@Autowried
    private UserService userService;
    
    public void doUserController(){
    	//执行UserService的方法
    	userService.doUserService();
    	
        System.out.println("douserController...");
    }
}

获取UserController的Bean,然后执行Bean的方法:

@SpringBootApplication
public class Test1Application {
    public static void main(String[] args) {

        // 获取上下文
        ApplicationContext context = SpringApplication.run(Test1Application.class, args);

        //获取userController
        UserController userController = (UserController)context.getBean("userController");
        userController.doUserController();

    }
}

运行后,结果:
在这里插入图片描述

说明:UserController 不再自己创建 UserService,而是由 Spring 容器注入。这种方式让 UserController只关心如何使用 UserService,而不关心它从哪来。

如果不使用@Autowried会怎么样呢?
UserController类:

@Controller
public class UserController {
	// 注入UserService依赖
	@Autowried
    private UserService userService;
    
    public void doUserController(){
    	//执行UserService的方法
    	userService.doUserService();
    	
        System.out.println("douserController...");
    }
}

其他的代码不变,运行,结果:
在这里插入图片描述
NullPointer:空指针

通过调试发现,userService对象是空的
在这里插入图片描述

原因:程序运行的时候没有向UserController注入UserControllerBean。

2.2构造方法注入

2.2.1 一个参数和一个有参构造函数

UserService类:

@Service
public class UserService {
    public void doUserService() {
        System.out.println("doUserService...");
    }
}

UserController类:

@Controller
public class UserController {

    private UserService userService;

	//构造函数注入依赖
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
    }
}

获取UserController的Bean,然后执行Bean的方法:

@SpringBootApplication
public class Test1Application {
    public static void main(String[] args) {

        // 获取上下文
        ApplicationContext context = SpringApplication.run(Test1Application.class, args);

        //获取userController
        UserController userController = (UserController)context.getBean("userController");
        userController.doUserController();

    }
}

运行后,结果:
在这里插入图片描述

2.2.2 多个有参构造方法和无参构造方法

基于上面的类,再添加一个UserInfo类:

@Data
public class UserInfo {
    private int id;
    private String name;
    private int age;

}
@Configuration
public class BeanConfig {

    @Bean
    public String name1(){
        return "liming";
    }


    @Bean(name={"u1","userInfo1"})
    public UserInfo userInfo1( @Qualifier("name1") String name){
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName(name);
        userInfo.setAge(18);

        return userInfo;
    }
}

UserController类改为:

@Controller
public class UserController {

    private UserService userService;
    private UserInfo userInfo;

    public UserController(){

    }

    public UserController(UserService userService) {
        this.userService = userService;
    }

    public UserController(UserService userService, UserInfo userInfo) {
        this.userService = userService;
        this.userInfo = userInfo;
    }

    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
    }
}

其他的代码不变,运行,结果:
在这里插入图片描述
NullPointer:空指针

这是为什么呢?
答:当有多个构造函数和无参构造函数时,默认会调用无参构造函数,导致没有依赖注入。

把无参的构造函数删除,再次运行:

UserController类改为:

@Controller
public class UserController {

    private UserService userService;
    private UserInfo userInfo;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    public UserController(UserService userService, UserInfo userInfo) {
        this.userService = userService;
        this.userInfo = userInfo;
    }

    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
    }
}

运行后还会报错

为什么呢?
当有多个构造函数时会调用无参构造函数,如果没有无参构造函数则会报错。

解决方法
使用注解指定使用哪个构造函数:

@Controller
public class UserController {

    private UserService userService;
    private UserInfo userInfo;
	
	@Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public UserController(UserService userService, UserInfo userInfo) {
        this.userService = userService;
        this.userInfo = userInfo;
    }

    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
    }
}

运行结果:
在这里插入图片描述

2.3 Setter注入

Setter方法注入需要使用注解@Autowired:

UserService类:

@Service
public class UserService {
    public void doUserService() {
        System.out.println("doUserService...");
    }
}

UserController类:

@Controller
public class UserController {
    private UserService userService;

    //Setter方法注入
    @Autowired
    public void setUserService(UserService userService){
        this.userService = userService;
    }


    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
    }
}

运行结果:
在这里插入图片描述

2.4三种注入优缺点分析

  1. 属性注⼊

    • 优点: 简洁,使⽤⽅便;
    • 缺点:
      (1)只能⽤于 IoC 容器,如果是非 IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常)
      (2)不能注⼊⼀个Final修饰的属性
  2. 构造函数注⼊(Spring 4.X推荐)

    • 优点:
      (1)可以注⼊final修饰的属性
      (2)注⼊的对象不会被修改
      (3)依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
      (4)通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
    • 缺点:
      注⼊多个对象时, 代码会⽐较繁琐
  3. Setter注⼊(Spring 3.X推荐)

    • 优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
    • 缺点:
      (1)不能注⼊⼀个Final修饰的属性
      (2)注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.

3.@Autowired存在问题

3.1同一类型存在多个bean

当同⼀类型存在多个bean时, 使⽤@Autowired会存在问题:

BeanConfig类中有Bean方法:

@Configuration
public class BeanConfig {

    @Bean
    public String name1(){
        return "liming";
    }


    @Bean(name={"u1","userInfo1"})
    public UserInfo userInfo1( @Qualifier("name1") String name){
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName(name);
        userInfo.setAge(18);

        return userInfo;
    }

    @Bean
    public UserInfo userInfo2(){
        UserInfo userInfo =new UserInfo();
        userInfo.setId(2);
        userInfo.setName("张三");
        userInfo.setAge(20);

        return userInfo;
    }
}

UserService类:

@Service
public class UserService {
    public void doUserService() {
        System.out.println("doUserService...");
    }
}

UserController类:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private UserInfo userInfo;


    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
        System.out.println(userInfo);
    }
}

取出Bean,并执行方法:

@SpringBootApplication
public class Test1Application {
    public static void main(String[] args) {

        // 获取上下文
        ApplicationContext context = SpringApplication.run(Test1Application.class, args);

        //获取userController
        UserController userController = (UserController)context.getBean("userController");
        userController.doUserController();

    }
}

运行结果:
在这里插入图片描述

怎么解决呢?

3.2 解法1:属性名和需要使用的对象名保持一致

UserController类:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private UserInfo userInfo1; //Bean的名字是userInfo


    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
        System.out.println(userInfo1);
    }
}

运行结果:
在这里插入图片描述

3.3 解法2:@Primary

使用@Primary注解,指定该Bean是默认的

@Configuration
public class BeanConfig {

    @Bean
    public String name1(){
        return "liming";
    }


    @Primary  //指定该Bean是默认的
    @Bean(name={"u1","userInfo1"})
    public UserInfo userInfo1( @Qualifier("name1") String name){
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName(name);
        userInfo.setAge(18);

        return userInfo;
    }

    @Bean
    public UserInfo userInfo2(){
        UserInfo userInfo =new UserInfo();
        userInfo.setId(2);
        userInfo.setName("张三");
        userInfo.setAge(20);

        return userInfo;
    }
}

运行结果:
在这里插入图片描述

3.4解法3:@Qualifier

使⽤@Qualifier注解:

UserController类:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @Qualifier("userInfo1") //指定名字为userInfo1的Bean注入
    private UserInfo userInfo1; //Bean的名字是userInfo


    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
        System.out.println(userInfo1);
    }
}

运行结果:
在这里插入图片描述
userInfo的内容为什么是null呢?

@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤

修改后的UserController类:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

	@Autowired
    @Qualifier("userInfo1") //指定名字为userInfo1的Bean注入
    private UserInfo userInfo1; //Bean的名字是userInfo


    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
        System.out.println(userInfo1);
    }
}

运行结果:
在这里插入图片描述
注:
@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤

3.5 解法4:@Resource

使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @Resource(name="userInfo1")
    private UserInfo userInfo1;


    public void doUserController(){
        userService.doUserService();
        System.out.println("douserController...");
        System.out.println(userInfo1);
    }
}

运行结果:
在这里插入图片描述

4.常见面试题

@Autowird 与 @Resource的区别
(1)@Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
(2)@Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说, @Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。

Autowired装配顺序:
在这里插入图片描述

5.总结

5.1Spring, Spring Boot 和Spring MVC的关系以及区别

Spring: 简单来说, Spring 是⼀个开发应⽤框架,什么样的框架呢,有这么⼏个标签:轻量级、⼀站式、模块化,其⽬的是⽤于简化企业级应⽤程序开发.

Spring的主要功能: 管理对象,以及对象之间的依赖关系, ⾯向切⾯编程, 数据库事务管理, 数据访问, web框架⽀持等.
但是Spring具备⾼度可开放性, 并不强制依赖Spring, 开发者可以⾃由选择Spring的部分或者全部, Spring可以⽆缝继承第三⽅框架, ⽐如数据访问框架(Hibernate 、JPA), web框架(如Struts、 JSF)

Spring MVC: Spring MVC是Spring的⼀个⼦框架, Spring诞⽣之后, ⼤家觉得很好⽤, 于是按照MVC模式设计了⼀个 MVC框架(⼀些⽤Spring 解耦的组件), 主要⽤于开发WEB应⽤和⽹络接⼝,所以, Spring MVC 是⼀个Web框架.

Spring MVC基于Spring进⾏开发的, 天⽣的与Spring框架集成. 可以让我们更简洁的进⾏Web层开发, ⽀持灵活的 URL 到⻚⾯控制器的映射, 提供了强⼤的约定⼤于配置的契约式编程⽀持, ⾮常容易与其他视图框架集成,如 Velocity、FreeMarker等

Spring Boot: Spring Boot是对Spring的⼀个封装, 为了简化Spring应⽤的开发⽽出现的,中⼩型企业,没有成本研究⾃⼰的框架, 使⽤Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发
⼈员更加专注于Spring应⽤的开发,⽽⽆需过多关注XML的配置和⼀些底层的实现.
Spring Boot 是个脚⼿架, 插拔式搭建项⽬, 可以快速的集成其他框架进来.

⽐如想使⽤SpringBoot开发Web项⽬, 只需要引⼊Spring MVC框架即可, Web开发的⼯作是 SpringMVC完成的, ⽽不是SpringBoot, 想完成数据访问, 只需要引⼊Mybatis框架即可.
Spring Boot只是辅助简化项⽬开发的, 让开发变得更加简单, 甚⾄不需要额外的web服务器, 直接⽣成jar包执⾏即可.

最后⼀句话总结: Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的⼀个 MVC 框架,⽽Spring Boot 是基于Spring的⼀套快速开发整合包.

这三者专注的领域不同,解决的问题也不⼀样, 总的来说,Spring 就像⼀个⼤家族,有众多衍⽣产品, 但他们的基础都是Spring, ⽤⼀张图来表⽰他们三个的关系:

5.2 Bean 的命名

1)五⼤注解存储的bean
① 前两位字⺟均为⼤写, bean名称为类名
② 其他的为类名⾸字⺟⼩写
③ 通过 value属性设置@Controlller(values="name")

2)@Bean 注解存储的bean
① bean名称为⽅法名
②通过name属性设置@Bean(name={"name1","name"})

5.3常见面试题

1)三种注⼊⽅式的优缺点(参考上⾯内容)

2)常⻅注解有哪些? 分别是什么作⽤? web url映射: @RequestMapping
参数接收和接⼝响应: @RequestParam, @RequestBody, @ResponseBody
bean的存储: @Controller, @Service, @Repository, @Component, @Configuration, @Bean
bean的获取: @Autowired, @Qualifier, @Resource

3)@Autowired 和@Resource 区别

4)说下你对Spring, SpringMVC, Springboot的理解(参考上⾯内容)


网站公告

今日签到

点亮在社区的每一天
去签到