【Spring DI】Spring依赖注入详解

Spring DI详解

上一章节对DI进行了初步的介绍,接下来就对DI有一个更加深刻的认识

与之IoC匹配的就是DI,DI全名为Dependency Injection,即依赖注入,既然有IoC管理,那就会有DI从Spring容器中取出来,DI就是承担这样的角色------DI 是 IoC 的一种具体实现方式 ,也能理解为:++容器通过注入依赖来实现控制反转,依赖注入是一个过程++

DI分三种方式注入

  1. 属性注入
  2. 构造方法注入
  3. Setter注入

属性注入

属性注入是通过@Autowired实现的。如果有多个属性需要逐行注入,@Autowired只对紧挨着那一行代码生效


构造方法注入

  • 如果只有一种构造方法,则执行这一种构造方法,无论是有参构造还是无参构造

    java 复制代码
    	private UserService service;
    
        public UserController(UserService userService) {
            System.out.println("执行有参构造方法");
            this.service = userService;
        }
    
        public void print() {
            service.print();
            System.out.println("do controller");
        }
    
    	// 启动项
    	UserController bean = text.getBean(UserController.class);
        bean.print();
    
    // 运行结果
    // 执行有参构造方法
    // do service
    // do controller

    执行成功,print()​拿到service对象

  • 如果有多种构造方法,则默认执行无参的构造方法

    1. 存在无参的构造方法
    java 复制代码
    	private UserService service;
        private UserRepository repository;
    
        public UserController(UserService userService,UserRepository userRepository) {
            System.out.println("执行有参构造方法1");
            this.service = userService;
            this.repository = userRepository;
        }
    
        public UserController(UserService userService) {
            System.out.println("执行有参构造方法2");
            this.service = userService;
        }
        
        public UserController() {
            System.out.println("执行无参构造方法");
        }
    
        public void print() {
            service.print();
            repository.print();
            System.out.println("do controller");
        }
    
    	// 启动项
    	UserController bean = text.getBean(UserController.class);
        bean.print();

    由于执行的是无参的构造方法, print()​方法拿不到service​和repository对象,故报错。


    1. 如果不存在无参构造方法,则直接报错。报错日志为:找不到默认的构造方法
    java 复制代码
    	private UserService service;
        private UserRepository repository;
    
        public UserController(UserService userService,UserRepository userRepository) {
            System.out.println("执行有参构造方法1");
            this.service = userService;
            this.repository = userRepository;
        }
    
        public UserController(UserService userService) {
            System.out.println("执行有参构造方法2");
            this.service = userService;
        }
        
    //    public UserController() {
    //        System.out.println("执行无参构造方法");
    //    }
    
        public void print() {
            service.print();
            repository.print();
            System.out.println("do controller");
        }
    
    	// 启动项
    	UserController bean = text.getBean(UserController.class);
        bean.print();

    可以通过@Autowired 注解来指定构造方法,此时1. 和 2. 的情况都能解决,没有无参构造方法也不会报错,这两种情况都能运行

    java 复制代码
    	private UserService service;
        private UserRepository repository;
        
        @Autowired
        public UserController(UserService userService,UserRepository userRepository) {
            System.out.println("执行有参构造方法1");
            this.service = userService;
            this.repository = userRepository;
        }
    
    
        public UserController(UserService userService) {
            System.out.println("执行有参构造方法2");
            this.service = userService;
        }
    
        public UserController() {
            System.out.println("执行无参构造方法");
        }
    
        public void print() {
            service.print();
            repository.print();
            System.out.println("do controller");
        }
    
    // 运行结果
    // 执行有参构造方法1
    // do service
    // do repository
    // do controller

@Autowired 就是明确该使用哪个构造方法的

Setter注入

Setter​ 注入和属性的Setter ​方法实现类似,只不过在设置 set​ 方法的时候加上@Autowired 注解,如下所示

java 复制代码
	// Setter 方法注入
    private UserService service;
    private UserRepository repository;

    @Autowired
    public void setUserService(UserService service) {
        this.service = service;
    }

    @Autowired
    public void setUserService(UserRepository repository) {
        this.repository = repository;
    }

    public void print() {
        service.print();
        repository.print();
        System.out.println("do controller");
    }

说了这么多,那它们的各自优缺点是什么呢?这些技术该适用在哪些场景?

三种注入的优缺点

  • 属性注入

    • 优点:简洁,使用方便

    • 缺点:

      不能注入final修饰的属性 ------++​final​++ ​++的属性有要求,一定需要初始化。要么在属性注入的时候进行初始化,要么在构造方法中进行初始化,但这都违背了注入的初衷:只想从Spring容器中取出来,不想手动初始化,否则我用++ ​++​@Autowired​++ ​++就没意义了++

  • 构造方法注入

    • 优点:

      可以注入final修饰的属性 ------++可以在构造方法中进行初始化,也是解决++ ​++​final​++ ​++必须初始化的要求++

      注入的对象不会被修改,除非有set方法再对对象修改,否则初始化后就定好了

      通用性好:构造方法是JDK支持的,所以更换任何框架都是使用的

      **依赖对象在使用前一定被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载的阶段就会被执行------**​++从控制台也能简单看出来,先执行构造方法后再运行服务器++

    • 缺点:注入多个对象时,代码比较繁琐

  • Setter注入

    • 优点:方便在类实例后,重新对该对象进行配置或注入

    • 缺点:

      不能注入final修饰的属性

      注入对象可能会被修改,因为提供了setter方法,就有可能被多次调用修改的风险 ------++这也对应了构造方法的优点:注入的对象不会被修改++

@Autowired存在的问题

当我们写的代码变多,我们不禁有一个问题:

当同一个类型存在多个Bean​时,使用@Autowired会存在问题,具体示例场景代码给出了,那我们该怎么解决呢?

java 复制代码
@Service
public class UserService {
    @Autowired
    private Student ss;

    public void print() {
        System.out.println(ss);
        System.out.println("do service");
    }
}

@Component
public class StudentComponent {
    @Bean("bbb")
    public Student s1 () {
        return new Student("lili",9);
    }

    @Bean("ccc")
    public Student s2 () {
        return new Student("Jack",25);
    }
}

能看到报错原因是,非唯一的Bean对象,Spring无法分辨该把哪个对象注入

如何在不删除Bean对象的前提下,解决上述问题呢?Spring给了三种注解

  • @Primary
  • @Qualifier
  • @Resouce
  1. @Primary​:当存在多个相同类型的Bean​注入时,加上@Primary,确认默认的实现

    java 复制代码
    @Service
    public class UserService {
        @Autowired
        private Student ss;
    
        public void print() {
            System.out.println(ss);
        }
    }
    
    @Component
    public class StudentComponent {
        @Bean
        @Primary // 指定该Bean为默认Bean的实现
        public Student s1 () {
            return new Student("lili",9);
        }
    
        @Bean
        public Student s2 () {
            return new Student("Jack",25);
        }
    }
    
    // 输出结果
    // Student(name=lili, age=9)
  2. @Qualifier​:指定当前Bean​的对象,在@Qualifier​的value​属性注入Bean​的名字 (默认也是value,内部只有一个String value)

    不能单独使用,需要配合@Autowired

    java 复制代码
    @Service
    public class UserService {
        @Autowired
        @Qualifier("s2")
        private Student ss;
    
        public void print() {
            System.out.println(ss);
        }
    }
    
    @Component
    public class StudentComponent {
        @Bean
        public Student s1 () {
            return new Student("lili",9);
        }
    
        @Bean
        public Student s2 () {
            return new Student("Jack",25);
        }
    }
    // 输出结果
    // Student(name=Jack, age=25)
  3. @Resouce​:按照Bean​的名称进行注入,通过@Qualifier​里的name​属性注入Bean的名字

    java 复制代码
    @Service
    public class UserService {
    //    @Autowired
    //    @Qualifier("s2")
        @Resource(name = "bbb")
        private Student ss;
    
        public void print() {
            System.out.println(ss);
        }
    }
    
    @Component
    public class StudentComponent {
        // 作用是一样的
    //    @Bean({"bbb","ccc"})
    //    @Bean(value = {"bbb","ccc"})
        @Bean("bbb")
        public Student s1 () {
            return new Student("lili",9);
        }
    
        @Bean
        public Student s2 () {
            return new Student("Jack",25);
        }
    }
    
    // 输出结果
    // Student(name=lili, age=9)
    // 注意,使用重命名则原来方法命名就会失效,@Resource(name = "s1")会报错

那既然@Autowired 与 @Resouce都能起到依赖注入的功能,那它们有什么区别?

@Autowired 与 @Resouce 的区别

  1. @Autowired 与 @Resouce 的区别

    • @Autowired​ 是Spring框架提供的注解,@Resouce 是JDK提供的注解


    • @Autowired​默认是按照类型注入,@Resouce​除了匹配类型,默认按照名称注入。相比于@Autowired​ ,@Resouce​ 支持更多的参数设置,例如name​设置,根据名称来获取Bean

常见注解有哪些?分别是什么作用?

学到这里,我们能对注解做一个简单的梳理总结

复制代码
web URL 映射:@RequestMapping

参数接收和接口响应:@RequestParam,@RequestBody,@ResponseBody

Bean 的存储:@Controller,@Service,@Repository,@Configuration,@Component,@Bean

Bean 的获取:@Autowired,@Qualifier,@Resource

@Autowired的装配顺序

为了能更加了解@Autowired的装配顺序,特意做图来更加形象地描述

觉得up画的还不错的可以点个小小的赞b( ̄▽ ̄)d

学到这里应该对Spring也有了多少的认识,但还是不是很了解它们之间的关系,我们可以做一个简单的梳理概括,Spring其实也很简单~!接着看下去吧,坚持看到这里已经很厉害了!

Spring、SpringMVC、SpringBoot之间的关系与区别

Spring在不同的角度回答也不同,分Spring和SpringFramework

  • Spring:是Spring家族生态
  • SpringFramework:是核心容器
  1. Spring (Spring Framework)

    它是整个家族的核心容器

    • 核心能力: IoC 和 AOP
    • 作用: 由Spring负责管理 Java 对象的生命周期,让对象之间解耦。如果没有 Spring,程序员需要手创建对象需要手动new Object(),极其难以维护
  2. Spring MVC

    它是 Spring 框架中的一个Web 模块

    • 核心能力: 基于 Servlet 规范,实现了 MVC(Model-View-Controller)设计模式
    • 核心组件: DispatcherServlet(前端控制器)
    • 作用: 专门解决 WEB 开发的问题。它负责拦截用户发来的浏览器请求,分发给对应的 Java 方法处理,并返回数据或页面
    • 注意: 它是 Spring 的一部分,不是独立于 Spring 存在的。
  3. Spring Boot

    对Spring的一种封装,是Spring的脚手架,它集成了Spring内的各种功能,并且是一套 "约定大于配置"的工具集

    它没有创造新的技术(底层还是 Spring MVC, Spring Core),但它通过依赖管理和自动配置,把 Spring 家族原本零散的功能,打包成了一个可以直接运行的"脚手架",让开发更专注于Spring应用的开发,无需过多关心XML的配置和底层的实现

    • 核心能力: 自动配置 + 起步依赖(Starter)+ 内嵌服务器(Tomcat/Jetty)

      快速搭建结构,保持稳定,SpringBoot强的地方在于版本管理,有一个父级配置文件,里面写死了几百种常用库的最佳兼容版本号,写代码时,引入依赖就不需要再写版本号,默认会使用SpringBoot规定的最佳版本号

    • 作用:

      • 简化配置: 以前用 Spring + SpringMVC,需要配置 web.xml, applicationContext.xml 等一堆文件。Spring Boot 通过扫描你的 jar 包,自动帮你把这些都配好了

      • 简化部署: 它把 Tomcat 这种 Web 服务器直接塞进了 jar 包里,你运行 java -jar 就能启动网站,不用再去独立安装 Tomcat

    🥱如何理解 "约定大于配置"

    不仅仅把核心的功能打包好,而且默认规定了一套规则,程序员开发过程中的约定,大部分都遵守这套规则,如果没有特殊的要求,那就按照默认的规则执行如:

    • web应用的端口号默认是8080

    • 代码写在哪里?只要你的代码放在主启动类(Main Application)所在的包或者子包下面,我就能扫描到

    • 静态资源(图片/JS/CSS)放哪里?Spring Boot 约定只要你把文件扔在 src/main/resources/static 文件夹里,我就直接对外开放访问

    总结:约定大于配置 = 系统自带一套"最佳实践"的默认值

    • 如果没特殊需求,就可以开箱即用
    • 有特殊需求:在application.properties里手动配置,灵活度高

一句话总结:Spring MVC 和 Spring Boot 都属于Spring,Spring MVC 是 Spring的一个MVC 框架,Spring Boot 则是基于 Spring 的一套快速开发整合包(脚手架)

‍Spring DI部分到这就结束了,希望看到这里对你有所帮助,让我们变得更强!

相关推荐
苏三说技术44 分钟前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎2 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode2 小时前
Redis 在生产项目的使用
前端·后端
用户559822481222 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode2 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战2 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha2 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn2 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425912 小时前
ShardingJDBC
后端
行者全栈架构师2 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端