所有代码实现基于 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 的东西(其实主要就是 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:
基本上说,只要高层代码不控制具体的处理流程,那么这就可以被归类成是一种 IoC
Spring Container 本身内部也实现了 DI 和 Factory Pattern(至少)这两种 IoC 的控制方法,这二者的关系也是相辅相成的,Spring Container 本身通过 bean(可以理解成对象) factory 对对象进行实例化管理,因此才能够内部实现依赖注入
bean 的管理本身包括:
除了 bean 的管理和 DI 之外,spring container 还负责下面的功能:
AOP
这也是一个比较流行的 term,后面处理到 event handling 再说
事件处理
资源管理
比如说已经被视为 legacy 的使用 XML 加载内容
现在也有用 data source 处理相关资源,这个下个阶段也会提到
集成
其他的 Spring 全家桶
验证、数据转换和格式化
spring expression language(SpEL)
这是 spring 内部使用的一个东西,具体是什么还没了解,先列一下
DI 是一个在顶层不具体使用某个对象,而是在底层通过配置等其他方式确定实例化对象的方式,一个简单的图例为:
顶层的 app 本身在和 spring 沟通的时候不会声明说需要某个具体的 shape(可能是 abstract class 或是 interface),但是 srping 底层会通过不同的配置获取某个特定实例化的对象返回给 app
下面进入案例部分,我这里创建(cv)了一个新的 spring boot 项目:
rest controller 这里相当于渲染一个页面,用来输出结果
其实不用创建一个新的 package 也行,不过我 cv 了之前的项目,就……
这里总共有 3 个步骤:
创建一个 interface
package com.example.demo;
public interface DBConnection {
String connect();
}
作为一个简单的案例,这里返回一个 string 即可
创建一个 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 自动扫到
在 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 的实例化对象:
因此 spring 会自动注册 JDBC,并将其传到参数中,从而实现依赖注入,即,在某一步,spring 会实现这个代码:
DBConnection dbconn = new JDBC();
FirstRestController firstController = new FirstRestController(dbconn);
最终网页端现实的结果也证明了这点:
⚠️:从 spring 4.3 以后开始,如果只有一个 constructor 的情况下,@Autowired
是可以省略的
完成了这三步,构造函数注入的案例就实现完毕了
此时项目结构如下:
构造函数注入是在构造函数上加 @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();
}
}
效果如下:
可以看到终端上的输出
同构造函数注入,本质上 spring 在某一个阶段实例化 dbconn 和 rest controller 后,会调用 setter,将对应的 dbconn 传到 setter 中去
目前 spring boot 是推荐使用构造注入,尤其是依赖为必须项时
换言之,当依赖为可选(optional)时,可以选择使用 setter 注入
spring 团队现在已经不推荐使用,使用 field 注入的代码不太好测试
其实现原理为通过 java 反射(reflection),以上面的代码为例,实现方法为:
@RestController
public class FirstRestController {
@Autowired
private DBConnection dbconn;
// ...
}
一直到现在,案例中都只使用了一个 JDBC 实现,不过在日常中,interface 一般会对应多个实现,不过这种情况下 spring 无法辨别应该使用哪个实现时,就会报错:
这个时候 spring 就会提示,使用 @Qualifier
或者 @Primary
,这里使用 @Qualifier
或者 @Primary
的方式,构造函数注入和 setter 的实现方法都是一样的
实现方式如下:
@RestController
public class FirstRestController {
private DBConnection dbconn;
@Autowired
public void setDBconn(@Qualifier("mongoDB") DBConnection dbconn) {
System.out.println("dbconn from setter");
this.dbconn = dbconn;
}
// ...
}
显示效果如下:
注意这里 Qualifier("mongoDB")
中的值和类名是一样的,不过首字母没有大写
@Qualifiers
是在调用类使用,@Primary
则是在具体要被调用的类使用,并且,只能有一个实现类能使用 @Primary
注解。如果有多个实现使用了 @Primary
注解,那么 spring 同样会报错
这里移除 setter 中的 @Qualifiers
,并且在 MySQL
中使用 @Primary
:
@Component
@Primary
public class MySQL implements DBConnection {
@Override
public String connect() {
return "Connection via MySQL";
}
}
如:
之所以要移除 @Qualifiers
的原因是因为 @Qualifiers
的权重高于 @Primary
,如果二者同时出现,那么 @Qualifiers
会覆写 @Primary
。
也因此,一般来说推荐使用 @Qualifiers
而不是 @Primary