DI 依赖注入
什么是依赖注入
依赖注入DI(Dependency Injection)
我们先明确依赖的概念
生活中对象之间的依赖关系:
- 人写字的时候需要依赖笔
- 人吃饭的时候需要依赖筷子
程序中对象之间的依赖关系:
A类中的方法需要使用到B类对象才能执行,我们说A类依赖B类
那么依赖注入就是将一个复杂的对象中的各个组件赋值装配的过程
最终能够实现,从Spring容器中直接获得赋值装配好的对象的功能
依赖注入程序的实现
下面我们要编写程序实现依赖注入
关羽依赖青龙偃月刀来战斗
关羽这个类中有一个战斗(fight)方法,需要使用到青龙偃月刀对象
1.创建three包
2.创建青龙偃月刀类(DragonBlade)
@Component
public class DragonBlade {
private String name="青龙偃月刀";
// 略
}
3.创建关羽类,类中声明青龙偃月刀属性类明确依赖关系
@Component
public class GuanYu {
private String name="关云长";
// 明确依赖关系:声明青龙偃月刀属性
private DragonBlade dragonBlade;
// 关羽的战斗方法:输出"关云长使用青龙偃月刀战斗"
public void fight(){
System.out.println(name+"使用"+dragonBlade.getName()+"战斗");
}
// 略
}
4.创建配置类ThreeConfig
@Configuration
@ComponentScan("cn.tedu.three")
public class ThreeConfig {
}
5.编写测试类
@Test
public void three(){
GuanYu guanYu=acac.getBean("guanYu",GuanYu.class);
guanYu.fight();
}
运行发生空指针异常
回到GuanYu类
添加@Autowired注解
// 明确依赖关系:声明青龙偃月刀属性
@Autowired
private DragonBlade dragonBlade;
再次运行测试方法
运行输出
关云长使用青龙偃月刀战斗
@Autowired的功能
上面章节中我们使用@Autowired注解实现了依赖注入的效果
我们在GuanYu类DragonBlade属性前添加这个注解就实现了依赖注入效果
导致我们从Spring容器中获得的关羽是自带青龙偃月刀的
具体工作原理如下
在Spring框架实例化GuanYu类时
会检测出DragonBlade属性添加了@Autowired注解
Spring就会在Spring容器中寻找DragonBlade类型的对象
如果找到了就会自动将这个对象赋值给DragonBlade属性
课上作业
按照上面关羽和青龙偃月刀的关系
完成吕布(LvBu)和方天画戟(SkyLance)的依赖关系
经过配置在测试类中输出"吕布使用方天画戟战斗"
@Bean 方式实现依赖注入
我们学习了两种向Spring容器中保存对象的方法
@Bean和组件扫描
上面章节中我们掌握了组件扫描的方式
下面来学习@Bean的方式
我们使用张飞和丈八蛇矛的例子来实现
创建cn.tedu.fei包
创建SnakeLance类
public class SnakeLance {
private String name="丈八蛇矛";
// get\set略
@Override
public String toString() {
return name;
}
}
创建张飞ZhangFei类
public class ZhangFei {
private String name="张翼德";
private SnakeLance snakeLance;
// get\set略
public void fight(){
System.out.println(name+"使用"+snakeLance+"战斗");
}
}
创建FeiConfig类来使用@Bean将对象保存在Spring容器中
@Configuration
public class FeiConfig {
@Bean
public SnakeLance lance(){
return new SnakeLance();
}
// @Bean标记的方法的参数列表中如果声明了参数
// Spring会自动从Spring容器中寻找合适对象,为这个参数赋值
// 也就是说上面保存到Spring容器中的lance对象,就会自动赋值给参数sl
// 方法中又将sl赋值给了zf的属性,所以实现了依赖注入
@Bean
public ZhangFei fei(SnakeLance sl){
ZhangFei zf=new ZhangFei();
// 将丈八蛇矛类型的参数赋值给zf对象依赖的属性
zf.setSnakeLance(sl);
return zf;
}
}
测试类和之前套路一直
最终测试代码
@Test
public void fei(){
ZhangFei zf=acac.getBean("fei",ZhangFei.class);
zf.fight();
}
@Bean依赖注入失败情况
上面章节顺利完成依赖注入是因为Spring容器中恰巧包含需要依赖的对象
如果Spring容器中没有丈八蛇矛
测试类运行就会发生错误
错误信息中包含:
“expected at least 1 bean”
表示当前Spring容器没有任何一个匹配类型的对象
除了Spring容器中没有匹配对象引发错误之外
还有另一种情况也可能引发错误
如果Spring容器中有两把以上的丈八蛇矛对象
也可能发生依赖注入失败的情况
FeiConfig类修改为
@Bean
public SnakeLance lance1(){
return new SnakeLance();
}
@Bean
public SnakeLance lance2(){
return new SnakeLance();
}
可能发生下面的错误
“expected single matching bean but found 2: lance1,lance2”
表示当前Spring容器中有多个匹配对象
但是Spring无法自动选择其中的某一个成为我们依赖的对象
我们需要将@Bean保存张飞的方法的参数名和其中一个丈八蛇矛对象的id匹配
才能在多个丈八蛇矛中选择其中一个,完成依赖注入
@Bean
public ZhangFei zhangFei(SnakeLance lance1){
ZhangFei zf=new ZhangFei();
zf.setSnakeLance(lance1);
return zf;
}
组件扫描依赖注入的失败情况
@Bean依赖注入可能会出现失败
组件扫描也一样
以three包中的关羽青龙偃月刀的例子上修改
首先删除DragonBlade类上的@Component注解
然后修改ThreeConfig配置如下
@Configuration
@ComponentScan("cn.tedu.three")
public class ThreeConfig {
// 为了测试组件扫描的依赖注入失败
// 我们注入两把青龙偃月刀对象
@Bean
public DragonBlade blade1(){
return new DragonBlade();
}
@Bean
public DragonBlade blade2(){
return new DragonBlade();
}
}
再去运行ThreeTest类的测试方法
也会发生依赖注入失败的错误
解决这个错误的办法有两个
- 将@Autowired注解标记的属性名称修改为匹配的id名称
- 在@Autowired注解下添加@Qualifier注解,并指定要匹配的id名称
如果采用修改属性名的办法,势必会带来较多代码的维护和修改,不推荐使用
这里推荐使用@Qualifier注解来选择匹配的id
@Autowired
@Qualifier("blade1")
private DragonBlade dragonBlade;
指定之后,再次测试运行成功!
Ioc\DI解耦
什么是耦合
解耦:解开耦合
让程序从紧耦合状态转换为松耦合状态的过程
所以我们要先明确什么是耦合
耦合的概念:指程序中的各个组件(对象)互相依赖的紧密程度
如果依赖关系容易更换,那么它们的依赖程度就是不紧密的
如果依赖关系不容易更换,那么它们的依赖程度就是就紧密的
关系紧密,耦合就高\紧,需要修改代码时,影响代码的代码就多
关系不紧密,耦合就低\松,需要修改代码时,影响代码的代码就少
在程序中,大部分情况下,我们要为代码的扩展和维护提供更广的空间,编写的代码都要追求低耦合
我们之前章节中,关羽和青龙偃月刀就是紧耦合的,因为关系不能使用除青龙偃月刀之外的任何武器
要想实现松耦合的程序,关键的一步就是将关羽的依赖类型,从之前的青龙偃月刀这样的具体类型,变更为一个抽象的类型(接口和抽象类)
编写解耦的程序
我们创建一个包cn.tedu.ou
然后创建一个武器接口Weapon
public interface Weapon {
// 我们声明一个武器都能用到的方法
// 获得武器名称,在fight方法输出时都需要用
String getName();
}
然后创建两个武器接口的实现类
@Component
public class DragonBlade implements Weapon {
private String name="青龙偃月刀";
// 其它代码略
}
@Component
public class SkyLance implements Weapon {
private String name="方天画戟";
// 其它代码略
}
创建关羽类
@Component
public class GuanYu {
private String name="关二爷";
// 依赖关系由之前的具体的类型,修改为抽象的接口类型,实现解耦
// 运行时Spring会从Spring容器中寻找Weapon接口的实现类对象来自动赋值给Weapon
// 更换依赖对象,只需要维护保存在Spring容器中的Weapon实现类即可
@Autowired
private Weapon weapon;
public void fight(){
System.out.println(name+"使用"+weapon.getName()+"战斗");
}
//其它代码略
}
编写OuConfig
@Configuration
@ComponentScan("cn.tedu.ou")
public class OuConfig {
}
继续编写测试类代码如下
@Test
public void ou(){
GuanYu guanYu=acac.getBean("guanYu",GuanYu.class);
guanYu.fight();
}
测试类运行报错,末尾的信息:
“expected single matching bean but found 2: dragonBlade,snakeLance”
意思为当前Spring容器中属于Weapon类型的实现类的对象有两个
这个两个对象需要我们选择其中一个来进行依赖注入
我们办法:
1.将不需要的对象的@Component注解删掉,留下需要的类的@Component注解,但是类比较多时,维护也比较麻烦
2.使用我们学习的@Qualifier注解来指定需要的对象的id
@Autowired
@Qualifier("skyLance")
private Weapon weapon;
再次运行输出
“关二爷使用方天画戟战斗”
}
测试类运行报错,末尾的信息:
"expected single matching bean but found 2: dragonBlade,snakeLance"
意思为当前Spring容器中属于Weapon类型的实现类的对象有两个
这个两个对象需要我们选择其中一个来进行依赖注入
我们办法:
1.将不需要的对象的@Component注解删掉,留下需要的类的@Component注解,但是类比较多时,维护也比较麻烦
2.使用我们学习的@Qualifier注解来指定需要的对象的id
```java
@Autowired
@Qualifier("skyLance")
private Weapon weapon;
再次运行输出
“关二爷使用方天画戟战斗”
SpringBoot聚合项目
什么是聚合项目
Maven提供的一种项目结构
简单来说,就是我们的项目是一个父级项目,其中可以创建多个子项目的结构
每个子项目编写自己的代码,和其它子项目互不干扰
每个子项目开发自己的功能,方便项目的模块化开发和维护
创建聚合项目
以商城为例
一个商城项目要分为很多模块,最典型的就是前台和后台
一般来说一定是两个项目
先来创建父项目
删除创建之后出现的src文件夹
因为父项目不会编写代码
创建完父项目修改它的pom文件内容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>jd</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jd</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--
下面的配置表示当前项目只是一个pom文件没有具体代码
当一个子项目继承这个父项目时,相当于只继承了这个项目的pom文件
-->
<packaging>pom</packaging>
</project>
创建子项目
在父项目名称上单击右键选择new->module
创建项目即可
父项目和子项目有一个确认关系的配置过程
我们称之为父子相认
父项目认儿子
<!--
下面的配置表示当前项目只是一个pom文件没有具体代码
当一个子项目继承这个父项目时,相当于只继承了这个项目的pom文件
-->
<packaging>pom</packaging>
<!--
modules标签中声明当前父项目包含的所有子项目的名称
也就是父认子的配置
-->
<modules>
<module>jd-shop</module>
<!-- <module></module>-->
</modules>
子项目的pom文件也需要配置
就是儿子人父亲的过程
将父项目pom文件的第1113行复制到子项目pom文件的第68行
<parent>
<groupId>cn.tedu</groupId>
<artifactId>jd</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
这样就完成父子相认了
我们要开发的项目都是这样的父项目和子项目的结构
课堂练习
按照刚刚的过程
创建jd的后台项目jd-backend
完成父子相认
有额外时间的同学,可以从创建父项目开始做一遍
达内知道项目概述
达内知道项目基本功能
<<达内知道>>项目的曾用名为<<稻草问答>>
是一个学生和讲师进行问题交流的在线互通平台
达内知道项目创建
使用聚合项目的方式进行创建
创建父项目knows
什么都不需要勾选
将父项目的pom文件修改为
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>knows</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>knows</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<packaging>pom</packaging>
</project>
创建聚合子项目
knows-portal(门户)
勾选SpringWeb选项
然后进行父子相认
父项目pom文件
<modules>
<module>knows-portal</module>
</modules>
子项目pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>knows</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath />
</parent>
<groupId>cn.tedu</groupId>
<artifactId>knows-portal</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>knows-portal</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
达内知道git地址:https://gitee.com/jtzhanghl/knows2111.git
加载数据库数据
我们之前使用命令行来错误mysql太麻烦了
在企业中工作不可能使用效率那么低的工具做数据库的操作
我们可以使用mysql\MariaDB的可视化工具软件来提高操作数据库的效率
如果安装的是苍老师网站提供的MariaDB的安装包,会在系统中安装一个可视化工具软件HeidiSQL
这个一个免费的具有基本功能的数据库可视化工具
如果安装的mysql不包含HeidiSQL软件,我们可以上网搜索类似软件来使用
例如sqlyog等,以达到相同的功能
我们今后就可以使用heidiSQL来操作数据了
下面就解压提供给大家的knows_v4.zip压缩包
将解压后文件夹中的knows_v4.sql文件中的内容复制
粘贴到查询页面然后运行
这样就加载了我们达内知道项目的数据
运行过程中出现56个警告或28个警告都是正常的!
达内知道项目数据库结构简介
上面数据加载后,创建了knows数据库
其中包含了14张表
这14张表可以分为两块
- 用户管理模块:涉及用户数据操作的相关表,用户,权限,角色,班级等
- 问答管理模块:涉及提问和回答操作相关表,问题,回答,评论,标签等
数据结构如图
加载静态资源
一个网站有了数据是第一步
然后还有制作页面,用户显示
我们学习的是java,主要做后端代码,开发页面不是我们的主要职责
所以页面直接发给大家,包含样式和js等
knows_v4文件夹中的"原型网页"文件夹中的所有内容
复制到portal项目的static目录下
启动knows-portal项目
在启动之后
浏览器地址栏输入
localhost:8080/index_student.html
测试是否能够正常访问静态资源
英文
Dependency :依赖
Injection:注入
expected:期望
single:单独的
match:匹配