[sping] spring core - 依赖注入

[sping] spring core - 依赖注入

所有代码实现基于 Spring Boot3,core 的概念很宽广,这里的 core concept 主要指的就是 Inversion of Control 和 Dependency Injection,其他的按照进度应该是会被放到其他的 section 记录

之前有写过 IoC 和 DI,不过是基于 XML 实现的,新版课程把所有 XML 相关的部分全都移除掉了,只留下了基于注解的实现,所以这里也会根据更新的课程继续学下去

本篇笔记主要以依赖注入为主,下篇笔记会将其他相关的注解补完

一些 boot 相关的内容在 [spring] spring boot 简述,devtool 配置,actuator 简述,即 properties 配置 有提,包括热更新之类的,这里不多赘述

一些 boot 的注解相关的内容在 [maven] 创建 spring boot 项目及使用 Jenkins 运行 maven 中有提到,这里主要就是学 maven 的时候过了一遍 spring boot 的结构,因为先学的 maven,所以在写 boot 的时候就没重复

Spring Container

在开始讲过 Spring Container 的东西(其实主要就是 Dependency Injection)之前,先捋一下几个概念:

依赖反转原则,Dependence Inversion Principle(DIP) 一种原则,可以理解成一种比较高层的指导方针
控制反转,Inversion of Contro(IoC) 一种通用术语,比较常见用来形容 DIP 的术语,针对不同的容器实现会不太一样
在 Spring 框架中,两个还满常混用的
依赖注入,Dependency Injection(DI) 实现 IoC/DIP 的具体方法
IoC Container 具体实现功能的容器,一般可以指代框架,如 Spring Container

虽然 DI 和 IoC 现在两个基本上会被放在一起讲,不过除了 DI 之外,还有一些其他方式也可以实现 IoC:

  • 服务定位器模式,Service Locator Pattern
  • 工厂模式,Factory Pattern
  • 模板方法,Template Method Pattern

基本上说,只要高层代码不控制具体的处理流程,那么这就可以被归类成是一种 IoC

Spring Container 本身内部也实现了 DI 和 Factory Pattern(至少)这两种 IoC 的控制方法,这二者的关系也是相辅相成的,Spring Container 本身通过 bean(可以理解成对象) factory 对对象进行实例化管理,因此才能够内部实现依赖注入

bean 的管理本身包括:

  • 实例化
  • 生命周期管理
  • scope(默认为 singleton)

除了 bean 的管理和 DI 之外,spring container 还负责下面的功能:

  • AOP

    这也是一个比较流行的 term,后面处理到 event handling 再说

  • 事件处理

  • 资源管理

    比如说已经被视为 legacy 的使用 XML 加载内容

    现在也有用 data source 处理相关资源,这个下个阶段也会提到

  • 集成

    其他的 Spring 全家桶

  • 验证、数据转换和格式化

  • spring expression language(SpEL)

    这是 spring 内部使用的一个东西,具体是什么还没了解,先列一下

依赖注入

DI 是一个在顶层不具体使用某个对象,而是在底层通过配置等其他方式确定实例化对象的方式,一个简单的图例为:

[sping] spring core - 依赖注入_第1张图片

顶层的 app 本身在和 spring 沟通的时候不会声明说需要某个具体的 shape(可能是 abstract class 或是 interface),但是 srping 底层会通过不同的配置获取某个特定实例化的对象返回给 app

下面进入案例部分,我这里创建(cv)了一个新的 spring boot 项目:

[sping] spring core - 依赖注入_第2张图片

rest controller 这里相当于渲染一个页面,用来输出结果

其实不用创建一个新的 package 也行,不过我 cv 了之前的项目,就……

构造函数注入

这里总共有 3 个步骤:

  1. 创建一个 interface

    package com.example.demo;
    
    public interface DBConnection {
        String connect();
    }
    
    

    作为一个简单的案例,这里返回一个 string 即可

  2. 创建一个 Java 类去实现 interface

    package com.example.demo;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class JDBC implements DBConnection{
        @Override
        public String connect() {
            return "Connection via JDBC";
        }
    }
    
    

    这里比较特殊的就是一个 @Component 注解

    这个注解会将当前对象作为一个 bean 注册到 spring application context,同时,如果开启 component scan 的话,当前类就会被 spring 自动扫到

  3. 在 RestController 中显示出来

    更新 Rest Controller 的代码:

    package com.example.demo.rest;
    
    import com.example.demo.DBConnection;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class FirstRestController {
        private DBConnection dbconn;
    
        @Autowired
        public FirstRestController(DBConnection conn) {
            dbconn = conn;
        }
    
        @GetMapping("/dbconnection")
        public String getDBConnection() {
            return dbconn.connect();
        }
    
    }
    
    

    这里 @RestController@Component 的功能类似,不过这里告知 spring 这是一个 web controller,同时可以处理 HTTP 请求

    这里 FirstRestController 的实例化和参数都由 spring 内部进行管理,而控制参数的类型则是由 @Autowired 实现

    当前案例中只有一个 DBConnection 的实例化对象:

    [sping] spring core - 依赖注入_第3张图片

    因此 spring 会自动注册 JDBC,并将其传到参数中,从而实现依赖注入,即,在某一步,spring 会实现这个代码:

    DBConnection dbconn = new JDBC();
    
    FirstRestController firstController = new FirstRestController(dbconn);
    

    最终网页端现实的结果也证明了这点:

    [sping] spring core - 依赖注入_第4张图片

    ⚠️:从 spring 4.3 以后开始,如果只有一个 constructor 的情况下,@Autowired 是可以省略的

完成了这三步,构造函数注入的案例就实现完毕了

此时项目结构如下:

[sping] spring core - 依赖注入_第5张图片

setter 注入

构造函数注入是在构造函数上加 @Autowired,setter 注入顾名思义,就是在 setter 函数上加,如:

更新代码如下:

package com.example.demo.rest;

import com.example.demo.DBConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FirstRestController {
    private DBConnection dbconn;

    @Autowired
    public void setDBconn(DBConnection dbconn) {
        System.out.println("dbconn from setter");
        this.dbconn = dbconn;
    }

    @GetMapping("/dbconnection")
    public String getDBConnection() {
        return dbconn.connect();
    }

}

效果如下:

[sping] spring core - 依赖注入_第6张图片

可以看到终端上的输出

同构造函数注入,本质上 spring 在某一个阶段实例化 dbconn 和 rest controller 后,会调用 setter,将对应的 dbconn 传到 setter 中去

构造 vs setter 注入

目前 spring boot 是推荐使用构造注入,尤其是依赖为必须项时

换言之,当依赖为可选(optional)时,可以选择使用 setter 注入

field 注入

spring 团队现在已经不推荐使用,使用 field 注入的代码不太好测试

其实现原理为通过 java 反射(reflection),以上面的代码为例,实现方法为:

@RestController
public class FirstRestController {
    @Autowired
    private DBConnection dbconn;

    // ...
}

Qualifiers & Primary

一直到现在,案例中都只使用了一个 JDBC 实现,不过在日常中,interface 一般会对应多个实现,不过这种情况下 spring 无法辨别应该使用哪个实现时,就会报错:

[sping] spring core - 依赖注入_第7张图片

这个时候 spring 就会提示,使用 @Qualifier 或者 @Primary,这里使用 @Qualifier 或者 @Primary 的方式,构造函数注入和 setter 的实现方法都是一样的

Qualifiers

实现方式如下:

@RestController
public class FirstRestController {
    private DBConnection dbconn;

    @Autowired
    public void setDBconn(@Qualifier("mongoDB") DBConnection dbconn) {
        System.out.println("dbconn from setter");
        this.dbconn = dbconn;
    }

    // ...
}

显示效果如下:

[sping] spring core - 依赖注入_第8张图片

注意这里 Qualifier("mongoDB") 中的值和类名是一样的,不过首字母没有大写

Primary

@Qualifiers 是在调用类使用,@Primary 则是在具体要被调用的类使用,并且,只能有一个实现类能使用 @Primary 注解。如果有多个实现使用了 @Primary 注解,那么 spring 同样会报错

这里移除 setter 中的 @Qualifiers,并且在 MySQL 中使用 @Primary:

@Component
@Primary
public class MySQL implements DBConnection {
    @Override
    public String connect() {
        return "Connection via MySQL";
    }
}

如:

[sping] spring core - 依赖注入_第9张图片

之所以要移除 @Qualifiers 的原因是因为 @Qualifiers 的权重高于 @Primary,如果二者同时出现,那么 @Qualifiers 会覆写 @Primary

也因此,一般来说推荐使用 @Qualifiers 而不是 @Primary

你可能感兴趣的:(Spring,spring,servlet,java)