【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部分到这就结束了,希望看到这里对你有所帮助,让我们变得更强!

相关推荐
Unstoppable2243 分钟前
八股训练营第 35 天 | volatile 关键字的作用有那些?volatile 与synchronized 的对比?JDK8 有哪些新特性?
java·八股·volatile
Lisonseekpan1 小时前
HTTP请求方法全面解析:从基础到面试实战
java·后端·网络协议·http·面试
N***p3651 小时前
IDEA搭建SpringBoot,MyBatis,Mysql工程项目
spring boot·intellij-idea·mybatis
xiegwei1 小时前
spring security oauth2 集成异常处理
数据库·spring·spring security
南部余额1 小时前
深入理解 SpringBoot 核心:自动配置原理、ImportSelector与配置加载机制
java·spring boot·自动配置原理·importselector
zhixingheyi_tian1 小时前
TestDFSIO 之 热点分析
android·java·javascript
步步为营DotNet1 小时前
深入解读CancellationToken:.NET异步操作的精准控制
java·前端·.net
无奈何杨1 小时前
业务接入风控决策,挑战验证与结果同步
后端
曹牧1 小时前
Java中使用List传入Oracle的IN查询
java·oracle·list