Spring-定义、功能、Ioc/DI、Junit、作用域(singleson、prototype)、惰性初始化、依赖注入(@Bean、组件扫描)、Ioc和DI的解耦
0.对前面学习的总结
前面初步使用了Spring MVC 和 Spring Boot,大概总结如下:
-
Controller 和 java与页面的交互,都是Spring MVC的功能
M: Model模型,狭义上指实体类, 广义上指除了控制器之外的其它java类
V: View视图,html页面, 即显示给用户看到的界面
C: Controller控制器,Controller类,视图与java进行数据交互的
Spring MVC框架简化了V和C之间的数据交互过程
-
Spring Boot:
boot : 启动
支持Spring Boot框架的项目内置了很多配置,这些配置都是大部分框架的常规配置,当我们使用这些框架时,就免去了那些繁琐的配置。除了这些配置之外,还包含了一些约定:例如static文件夹存放静态资源等。
1.什么是Spring?
Spring本身的意思是春天、泉水,Spring是一个基础框架,Spring MVC、Spring Boot、Spring Data、Spring Security、Spring Validation、Spring Cloud都是Spring框架衍生出来的以及众多以Spring开头的框架的基础。它的出现是现在java能够长盛不衰的主要原因。因为它提供了一个java开发的生态环境,几乎市面上任何通用的常用需求,Spring都有解决方案。
2.Spring框架的功能
-
Ioc\DI (主要讲解内容):Ioc是一种思想,DI是基于Ioc的操作。
-
Aop (最后讲解内容):
2.1 什么是Ioc?
-
Ioc(Inversion of Control) 翻译为:控制反转,正常的控制称之为主动控制
-
主动控制: 我们编写的程序主动控制对象组件的产生,再编写对象组件互相调用,以完成程序功能
-
控制反转: 我们编写的程序需要的对象组件保存在外部容器中,需要时从容器中获取,之后调用方法实现功能
2.2 Spring 实现Ioc
(1)首先创建一个Maven项目,默认配置即可
注意:Maven项目不同于普通项目,因为里面包含pom.xml文件,可以添加组件依赖,方便管理依赖,而普通项目需要单独导入jar包,当jar包较多时,不方便管理,使用不便。Spring Boot内置Maven!Maven中内置Spring MVC!
(2)项目的pom.xml中添加依赖
注意:先添加依赖(代码正确),再刷新Maven,等待下载完毕即可!
maven下载资源失败的解决方案:
1.检查setting.xml文件的位置是否设置了阿里\华为的镜像
2.退出所有防火墙或电脑管家等占用电脑资源的程序
3.更换网络再试(手机热点,手机共享网络给电脑)
4.安装maven(可以从苍老师网站下载)
5.删除.m2文件夹下的repository文件夹,再导入依赖,刷新Maven
(3)详细步骤
在src/main/java路径下创建一个包hello,包下创建类Stu,类中编写代码如下:
package hello;
/**
* 1.实体类Stu
*/
public class Stu {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
包下编写配置类Config,常见的将对象保存到Spring容器中的方法有两个
我们先介绍第一个:@Bean方式,Bean是对象的意思。
package hello;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 2.Spring配置类
* 配置Spring容器中内容
*/
//标准情况下,Spring配置类要添加下面的注解:@Configuration
包下新建测试类:获得Spring容器中的对象
package hello;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 3.测试:获得Spring容器中的对象
*/
public class Test {
public static void main(String[] args) {
//初始化Spring容器,参数为配置类的反射
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(Config.class);//快捷键ACAC,然后回车
//从Spring容器中获取对象
//getBean方法两个参数:
//1.想获得的对象在Spring容器中的id(唯一的名字:@Bean时对应方法名)
//2.获得对象类型的反射(不需要强转,直接返回这个类型的对象)
Stu stu = acac.getBean("stu",Stu.class);
System.out.println(stu);
acac.close();//关闭资源
}
}
输出结果:Stu{name='杨过', age=26}
2.3 利用单元测试框架JUnit运行Spring
我们之前使用过Junit来测试程序运行,Junit测试程序有很多好处,例如一个测试类可以编写多个可以直接运行的方法,来减少main方法和类的数量。
在maven项目的pom.xml文件中添加junit的依赖,刷新Maven
<!--JUnit单元测试框架-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
在我们测试Spring的方法中,发现实例化ACAC对象和ACAC对象的关闭是每次都要编写执行的,我们可以利用Junit的@Before和@After这两个注解减少冗余代码,简化编写。
src下面有两个路径:main和test,它们的子文件中都包含java文件夹,其中main下面的java主要存储我们日常编写的程序,test下面的java主要是对上面编写程序的测试,虽然名字相同,但是具有不同的功能。在编写测试类时,尽量与上面的包同名,以便测试类中可以直接调用编写好的类,不需要导包。同时,注意测试类的名字不能为Test,因为测试时需要用到@Test注解,会发生冲突。
在test/java路径下,新建包hello,编写HelloTest的代码如下:
package hello;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class HelloTest {
//声明ACAC为成员变量,方便每个方法使用(Java中叫做成员变量,有的语言中也叫全局变量)
AnnotationConfigApplicationContext acac;
//测试类中的@Before注解,注解下的方法会在@Test标记的方法运行之前自动运行
输出结果:Stu{name='杨过', age=26}
2.4 将对象保存到Spring容器中方法2:Spring 组件扫描(大部分使用此方式)
除了上面章节中给大家介绍的@Bean方式可以将对象保存到Spring容器中,还有组件扫描方式可以将对象保存到Spring容器中。
在main/java路径下,新建包ioc,包中创建类Hero
package ioc;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
//@Component(组件):这个注解一旦标记,表示当前类要自动实例化对象,保存到Spring容器中
//这个对象保存到Spring容器中的id就是当前类名,只是首字母要小写
//此处直接设置对象属性,适用于对象属性不需要改变的情况
包下编写配置类Config
package ioc;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
同样按照上面的方法,转到测试类进行测试
package ioc;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IocTest {
AnnotationConfigApplicationContext acac;
输出结果:Hero{name='关羽', job='战士'}
组件扫描流程:
1.实例化ACAC对象时,指定Config类的反射
2.这个Config类上如果有@ComponentScan注解,并指定了包
3.扫描这个包中的所有类(包括子孙包中的所有类)
4.如果某个类上带有支持组件扫描方式保存该类对象到Spring容器的注解(例如@Component)
5.实例化这个对象,并将这个对象保存到Spring容器中,注意:id是这个类名首字母小写
2.5 保存到Spring的细节
-
除了@Component,还有以下常见注解有相同功能
-
@Controller \ RestController 控制器
-
@Service 业务逻辑
-
@Repository 数据访问
-
.....
-
为什么功能一样要设计这么多的注解呢?原因是见到注解名称,就知道这个类的作用(见名知意)
-
关于特殊的类名
我们已经学习使用组件扫描时,当前类型对象的id是当前类名首字母小写,但是有特殊情况:如果有类名连续两个以上字母都是大写,例如:VIPStu,那么它保存到Spring容器中的id就是类名(原类名)。
-
自定义组件ID
如果有特殊原因不能或不希望类名做id,我们可以自定义组件id,例如:
当然,其它支持组件扫描的注解,也可以用这个方法定义id,一般都是被动使用。
3.Spring中对象的作用域
3.1 作用域(Scope)概述
我们的飞机大战项目中,一共有6个类型,这6个类型中就涉及了我们要讲的两种作用域:
单例(singleton):从程序开始到程序结束,某个类型的对象始终只有一个
天空和英雄机就是单例
原型(prototype): 从程序开始到程序结束,某个类型不断出现新的对象,没有数量的限制(需要时就初始化创建新的对象)
小敌机,大敌机,小蜜蜂,子弹
3.2 单例(singleson)作用域
默认情况下,所有保存到Spring容器中的对象都是单例(singleton)(节省内存,原型要创建多个对象,占用内存)
3.3 原型(prototype)作用域
修改默认scope,将对象修改为prototype(原型)的scope
@Bean保存方式中的设置:
组件扫描方式保存的设置:
测试代码:
今后使用Spring框架的时候,如果需要设置Scope,根据这两个特性来设置修改即可。
4.惰性初始化(懒惰初始化、延迟加载、懒加载)
Spring容器中大部分对象都是单例的,单例对象会在实例化Spring容器时进行对象的实例化,也就是说实例化Spring容器时会有大量对象进行实例化。针对实例化时会消耗较多资源,但是运行过程中又不一定使用到的对象,我们可以设置为懒惰初始化。
使用@Lazy表示懒惰初始化
@Bean对象设置懒惰加载:
组件扫描的方式设置懒惰加载:
测试代码:
package ioc;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IocTest {
AnnotationConfigApplicationContext acac;
输出结果:
init方法运行完毕
lazy方法运行
Hero实例化
Hero{name='关羽', job='战士'}
非Lazy情况下输出结果:
Hero实例化
init方法运行完毕
lazy方法运行
Hero{name='关羽', job='战士'}
正常情况下,是在init的时候实例化对象,而输出的结果证明我们的设置生效了,是在acac.getBean方法的位置才实例化的Hero对象。如果方法中没有获得Hero对象的代码,这个对象就不会实例化了。所以通过使用惰性初始化可以针对个别对象节省内存。
什么时候需要使用这个对象,什么时候懒惰初始化的对象才会实例化.
惰性初始化可以针对个别对象节省内存,不宜大范围使用,因为大范围使用会引起运行压力大\缓慢。由于prototype是在需要的时候产生对象,所以不能和Lazy一起使用。
5.DI
5.1 什么是DI?
DI(Dependency Injection)依赖注入
Ioc是控制反转,它是一种编程思想,DI是基于控制反转思想的一种操作,要明确依赖注入的原因和作用,我们要先了解"依赖的概念"。
5.2 什么是依赖?
依赖指程序中A类需要使用到B类型对象的情况,那么就说A类依赖B类。
要在Spring中实现这个依赖过程,我们要创建如下类来演示:
青龙偃月刀类
package san;
public class DragonBlade {
private String name = "青龙偃月刀";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
关羽类
package san;
public class GuanYu {
private String name = "关云长";
//在GunaYu类中声明了属性DragonBlade
//这就是依赖关系,关羽依赖青龙偃月刀
private DragonBlade dragonBlade;
public void fight(){
//dragonBlade自动调用toString输出名字
System.out.println(name+"使用"+dragonBlade+"战斗");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public DragonBlade getDragonBlade() {
return dragonBlade;
}
public void setDragonBlade(DragonBlade dragonBlade) {
this.dragonBlade = dragonBlade;
}
}
关羽依赖青龙偃月刀,在配置类中:
package san;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
测试类让关羽战斗:
package san;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SanTest {
AnnotationConfigApplicationContext acac;
我们经过配置看到测试代码,逻辑和之前主动控制没有什么多大区别,但是使用依赖注入可以简化这个流程。
实现依赖注入:修改Config配置
package san;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
测试类修改代码运行:
5.3 @Bean方式实现依赖注入
上次课完成了基本的依赖注入操作,下面我们研究一下,在Spring容器中有不同数量的DragonBlade类型对象时,运行的效果分别是什么?
-
当Spring容器中没有任何DragonBlade类型对象时,运行会报错(IDEA也会在相应对象下面出现红色下划线,提示报错:Could not autowire. No beans of 'DragonBlade' type found.)
-
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'guanYu' defined in san.Config: Unsatisfied dependency expressed through method 'guanYu' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'san.DragonBlade' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
-
-
当Spring容器中有唯一的DragonBlade类型对象时,运行成功,即使保存DragonBlade对象的方法名和关羽参数的参数名不同也可以.
-
@Bean
public DragonBlade blade1(){
return new DragonBlade();
}
//实现依赖注入
//GuanYu方法的参数列表中声明DragonBlade
//Spring会自动从Spring容器中找到匹配的对象赋值
@Bean
public GuanYu guanYu(DragonBlade blade){
GuanYu guanYu = new GuanYu();
guanYu.setDragonBlade(blade);
return guanYu;
} -
输出结果:关云长使用青龙偃月刀战斗
-
-
当Spring容器中有两个及以上的DragonBlade类型对象时,关羽方法参数名没有和任何一个方法名匹配时会报错;关羽方法参数名有和任何一个方法名匹配会成功。
-
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'guanYu' defined in san.Config: Unsatisfied dependency expressed through method 'guanYu' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'san.DragonBlade' available: expected single matching bean but found 2: blade1,blade2
-
5.4 组件扫描方式实现依赖注入
先创建新的包di,包中创建丈八蛇矛类SnakeLance
package di;
import org.springframework.stereotype.Component;
创建张飞类:类中需要使用自动装配获得Spring容器中的丈八蛇矛,实现依赖注入
package di;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
配置类实现组件扫描:
package di;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
测试类运行:
package di;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DiTest {
//声明ACAC为成员变量,方便每个方法使用(Java中叫做成员变量,有的语言中也叫全局变量)
AnnotationConfigApplicationContext acac;
//测试类中的@Before注解,注解下的方法会在@Test标记的方法运行之前自动运行
输出结果:张飞使用丈八蛇矛战斗
5.5 Ioc和DI的解耦
5.5.1 什么是耦合性?
所谓耦合性,就是一组依赖关系中组成依赖关系的类的依赖紧密度。
耦合性高:表示依赖关系紧,好处是整体性好,封装数据好;
耦合性低:表示依赖关系松,好处是易更换,方便维护和扩展。
我们的程序应该追求低耦合,原因是现代程序经常需要进行修改和扩展。
如何实现低耦合的程序呢?关键在一点:将原有的依赖类型,由具体的类更换为接口。
具体到我们课程中的例子:关羽类应该依赖武器接口,而不是具体的类,如青龙偃月刀
5.5.2 解耦测试案例
武器接口类:
package ou;
public interface Weapon {
//接口中方法上public可以不写
String getName();
}
青龙偃月刀类实现武器接口:
package ou;
import org.springframework.stereotype.Component;
丈八蛇矛类实现武器接口:
package ou;
import org.springframework.stereotype.Component;
关羽类:
package ou;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
配置类:
package ou;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
测试类:
package ou;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class OuTest {
//声明ACAC为成员变量,方便每个方法使用(Java中叫做成员变量,有的语言中也叫全局变量)
AnnotationConfigApplicationContext acac;
//测试类中的@Before注解,注解下的方法会在@Test标记的方法运行之前自动运行
运行报错:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'guanYu': Unsatisfied dependency expressed through field 'weapon'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'ou.Weapon' available: expected single matching bean but found 2: dragonBlade,snakeLance
因为Spring容器中有两个Weapon类型对象,Spring无法选择,匹配失败。所以使用@Qualifier注解指定要匹配的Spring容器中对象的id,关羽类中Weapon依赖的位置:
private String name = "关二爷";
//解耦的依赖,声明的类型是接口或者抽象类,不声明具体可实例化的内容
输出结果:关二爷使用青龙偃月刀战斗
作业:
(1)组件扫描方式实现
-
创建武器接口Weapon
package xi;
import org.springframework.stereotype.Component;
-
创建金箍棒GoldenCudgel 实现武器接口
package xi;
import org.springframework.stereotype.Component;
-
创建乾坤圈UniverseCircle 实现武器接口
package xi;
import org.springframework.stereotype.Component;
-
创建孙悟空类WuKong,悟空类依赖武器,编写打妖怪方法
package xi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
-
创建配置类
package xi;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
-
创建测试类
package xi;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class XiTest {
AnnotationConfigApplicationContext acac;
-
使用依赖注入输出"孙悟空使用金箍棒打妖怪"
package xi;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class XiTest {
AnnotationConfigApplicationContext acac;
输出结果:孙悟空使用金箍棒打妖怪
(2)利用@Bean方式实现
利用@Bean解耦实现:
package xi;
public interface Weapon {
}
package xi;
public class GoldenCudgel implements Weapon{
private String name = "金箍棒";
package xi;
public class UniverseCircle implements Weapon{
private String name = "乾坤圈";
package xi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class WuKong {
private String name = "悟空";
package xi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
package xi;
import com.sun.deploy.panel.WinUpdatePanel;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class WuKongTest {
AnnotationConfigApplicationContext acac;
@Bean其他测试:
1.金箍棒类、乾坤圈类去掉@Component,悟空类不变
2.Config:
3.测试效果与原先一致
其它情况:
1.金箍棒类、乾坤圈类去掉@Component,悟空类去掉@Autowired,Config去掉@ComponentScan:孙悟空使用null打妖怪
2.金箍棒类、乾坤圈类去掉@Component,悟空类去掉@Autowired、@Qualifier,Config去掉@ComponentScan:孙悟空使用null打妖怪
3.金箍棒类、乾坤圈类去掉@Component,悟空类去掉@Autowired、@Qualifier,Config去掉@ComponentScan,Config添加:
测试效果与原先一样。