目录
Spring Test与JUnit等其他测试框架结合起来,提供了便捷高效的测试手段。而Spring Boot Test 是在Spring Test之上的再次封装,增加了切片测试,增强了mock能力。
整体上,Spring Boot Test支持的测试种类,大致可以分为如下三类:
单元测试:一般面向方法,编写一般业务代码时,测试成本较大。涉及到的注解有@Test。
切片测试:一般面向难于测试的边界功能,介于单元测试和功能测试之间。涉及到的注解有@RunWith @WebMvcTest等。
功能测试:一般面向某个完整的业务功能,同时也可以使用切面测试中的mock能力,推荐使用。涉及到的注解有@RunWith @SpringBootTest等。
功能测试过程中的几个关键要素及支撑方式如下:
测试运行环境:通过@RunWith 和 @SpringBootTest启动spring容器。
mock能力:Mockito提供了强大mock功能。
断言能力:AssertJ、Hamcrest、JsonPath提供了强大的断言能力。
JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
官网:JUnit
Junit 测试也是程序员测试,即所谓的白盒测试,它需要程序员知道被测试的代码如何完成功能,以及完成什么样的功能
我们知道 Junit 是一个单元测试框架,那么使用 Junit 能让我们快速的完成单元测试。
通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。
增加spring-boot-starter-test依赖,使用@RunWith和@SpringBootTest注解,即可开始测试。
pom中添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
一旦依赖了spring-boot-starter-test,下面这些类库将被一同依赖进去:
Spring Boot2.66的测试起步依赖的pom文件如下:
?spring-boot-starter-test2.66.pom
从依赖的坐标可以看出是junit-jupiter(junit5.0+)
JUnit:Java测试事实上的标准,默认依赖版本与SpringBoot版本相关(JUnit5和JUnit4差别比较大,集成方式有不同)。
Spring Test & Spring Boot Test:Spring的测试支持。
AssertJ:提供了流式的断言方式。
Hamcrest:提供了丰富的matcher。
Mockito:mock框架,可以按类型创建mock对象,可以根据方法参数指定特定的响应,也支持对于mock调用过程的断言。
JSONassert:为JSON提供了断言功能。
JsonPath:为JSON提供了XPATH功能。
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest //为测试提供上下文环境 class UsermisApplicationTests { @Test //声明需测试的方法 void testEqual(){ int actual=1; //断言actual是否与1相等 Assertions.assertEquals(1,actual); } }
右键运行测试的结果:
@SpringBootTest 指定测试的启动类
@Test 指定测试方法
@TestMethodOrder(MethodOrderer.Alphanumeric.class)指定测试的顺序,需要配合@Order一起使用
package com.zhangguo.springboottest; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest @TestMethodOrder(MethodOrderer.OrderAnnotation.class) //设置排序模式 public class MyTests { @Test @Order(2) //设置排序值 public void testA(){ } @Test @Order(1) //设置排序值 public void testB(){ } }
运行效果:
@BeforeEach:在每个单元测试方法执行前都执行一遍
@BeforeAll:在所有单元测试方法执行前执行一遍(只执行一次),需静态方法,除非将测试类注解@TestInstance
org.junit.platform.commons.JUnitException: @BeforeAll method 'public void com.gdnf.usermis.UsermisApplicationTests.beforeAll()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).
@AfterEach:在每个单元测试方法执行后都执行一遍
@AfterAll:在所有单元测试方法执行后执行一遍(只执行一次)
package com.zhangguo.springboottest; import org.junit.jupiter.api.*; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest @TestMethodOrder(MethodOrderer.OrderAnnotation.class) //设置排序模式 public class MyTests { @BeforeAll public static void beforeAll(){ System.out.println("BeforeAll在所有单元测试方法执行前执行一遍(只执行一次)"); } @BeforeEach public void beforeEach(){ System.out.println("BeforeEach第个测试方法执行前先执行该方法"); } @AfterEach public void afterEach(){ System.out.println("AfterEach在每个单元测试方法执行后都执行一遍"); } @AfterAll public static void afterAll(){ System.out.println("AfterAll在所有单元测试方法执行后执行一遍(只执行一次)"); } @Test @Order(2) //设置排序值 public void testA(){ } @Test @Order(1) //设置排序值 public void testB(){ } }
运行结果:
?控制台日志
@DisplayName("商品入库测试"):用于指定单元测试的名称
@Disabled:当前单元测试置为无效,即单元测试时跳过该测试
@RepeatedTest(n):重复性测试,即执行n次
?示例代码
@ParameterizedTest:参数化测试,
参数化测试可以按照多个参数分别运行多次单元测试这里有点类似于重复性测试,只不过每次运行传入的参数不用。需要使用到@ParameterizedTest,同时也需要@ValueSource提供一组数据,它支持八种基本类型以及String和自定义对象类型,使用极其方便。
?可选参数
@ValueSource(ints = {1, 2, 3}):参数化测试提供数据
package com.zhangguo.springboottest; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.boot.test.context.SpringBootTest; import java.util.Random; @SpringBootTest public class MyTests { @ParameterizedTest //声明该方法为参数化测试 @ValueSource(ints = {-1,0,1}) //参数源 public void testB(int a){ Assertions.assertTrue(a>=0); //如果结果为true测试测试通过 } }
运行结果:
Jupiter提供了强大的断言方法用以验证结果,在使用时需要借助java8的新特性lambda表达式,均是来自org.junit.jupiter.api.Assertions包的static方法。
assertTrue与assertFalse用来判断条件是否为true或false,assertTrue表示如果值为true则通过,assertFalse表示如果值为false则通过
@Test @DisplayName("测试断言equals") void testEquals() { assertTrue(3 < 4); }
assertNull与assertNotNull用来判断条件是否为null
@Test @DisplayName("测试断言NotNull") void testNotNull() { assertNotNull(new Object()); }
?View Code
结果:
assertThrows用来判断执行抛出的异常是否符合预期,并可以使用异常类型接收返回值进行其他操作
lambda要求jdk8以上,如果报错需要设置:错误一(Lambda expressions are not supported at language level '7'),错误二(源发行版 8 需要目标发行版 1.8)
测试通过:
@Test public void testE(){ Assertions.assertThrows(NullPointerException.class,()->{ String str=null; System.out.println(str.toLowerCase()); }); }
测试不通过:
@Test public void testE(){ Assertions.assertThrows(NullPointerException.class,()->{ String str="A"; System.out.println(str.toLowerCase()); }); }
assertTimeout用来判断执行过程是否超时
@Test public void testF(){ long actual=Assertions.assertTimeout(Duration.ofMillis(1000),()->{ Date start=new Date(); //记住开始时间 Thread.sleep(700); //休眠700毫秒 long span=new Date().getTime()-start.getTime(); //结束时间减去开始时间得到耗时 return span; //返回耗时时间 }); System.out.println(actual); //如果耗时小于1000毫秒则测试通过 Assertions.assertTrue(actual<=1000); }
因为耗时只有700+小于1000的最大耗时结果测试通过。
assertAll是组合断言,当它内部所有断言正确执行完才算通过
不通过示例:
@Test public void testG(){ String name=null; int age=17; //第一个参数是标题 Assertions.assertAll("模拟输入",()->{ Assertions.assertNotNull(name); //name不为空 },()->{ Assertions.assertTrue(age>0); //age大于零 },()->{ Assertions.assertTrue(age<=130); //age小于130 }); //当3个子断言都通过时则整体通过 }
通过示例:
@Test public void testG(){ String name="张果"; int age=19; //第一个参数是标题 Assertions.assertAll("模拟输入",()->{ Assertions.assertNotNull(name); //name不为空 },()->{ Assertions.assertTrue(age>0); //age大于零 },()->{ Assertions.assertTrue(age<=130); //age小于130 }); //当3个子断言都通过时则整体通过 }
这里不能把所有断言方法都列出来,可以查看Assertions类的定义,或使用IDE的智能提示选择:
?所有断言方法
Mockito 是一种 Java Mock 框架,主要是用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等,避免你为了测试一个方法,却要自行构建整个 bean 的依赖链。
像是 Mockito 可以在单元测试中模拟一个 Service 返回的数据,而不会真正去调用该 Service,这就是上面提到的 Mock 测试精神,也就是通过模拟一个假的 Service 对象,来快速的测试当前我想要测试的类。
目前在 Java 中主流的 Mock 测试工具有 Mockito、JMock、EasyMock等等,而 SpringBoot 目前内建的是 Mockito 框架。
mock:在软件开发的世界之外, "mock"一词是指模仿或者效仿。 因此可以将“mock”理解为一个替身,替代者。 在软件开发中提及"mock",通常理解为模拟对象或者Fake
示例:
自定义控制器:
package com.zhangguo.springboottest; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/mock") public String hello(@RequestParam(defaultValue = "") String name){ return "Hello "+name+"!"; } }
注意参数是动态的。
测试代码:
package com.zhangguo.springboottest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @SpringBootTest @AutoConfigureMockMvc //启用MockMVC自动配置 public class MVCTest { @Autowired MockMvc mockMvc; //自动装配mockmvc对象,模拟mvc运行环境 @Test public void mvcTest() throws Exception { //模拟发送一个请求到/mock mockMvc.perform(MockMvcRequestBuilders .get("/mock") //设置请求地址 .param("name", "zhangguo")) //请求参数 .andExpect(MockMvcResultMatchers.status().isOk()) //期待状态为200 .andExpect(MockMvcResultMatchers.content().string("Hello zhangguo!")) //期待请求返回的字符为Hello zhangguo! .andDo(MockMvcResultHandlers.print()) //将处理结果输出到控制台 .andReturn(); //返回,可以继续其它操作 } }
.perform() 执行一个MockMvcRequestBuilders请求。其中.get()表示发送get请求(可以使用get、post、put、delete等);.contentType()设置编码格式;.param()请求参数,可以带多个。
andExpect()添加 MockMvcResultMatchers验证规则,验证执行结果是否正确。
.andDo()添加 MockMvcResultHandlers结果处理器,这是可以用于打印结果输出。
.andReturn()结果还回,然后可以进行下一步的处理。
测试结果
?控制台日志
?更多
SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用 application.properties或者application.yml(application.yaml)进行配置。
server.port=8080
server: port: 8080
SpringBoot提供了2种配置文件类型:properteis和yml/yaml
默认配置文件名称:application
在同一级目录下优先级为:properties > yml > yaml
server.port=8082
server.address=127.0.0.1
server:
port: 8081
address: 127.0.0.1
<server>
<port>8080</port>
<address>127.0.0.1</address>
</server>
可见yml格式更加简洁,以数据为核心。
大小写敏感
数据值前边必须有空格,作为分隔符
使用缩进表示层级关系
缩进时不允许使用Tab键,只允许使用空格(各个系统 Tab对应的 空格数目可能不同,导致层次混乱)。
缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
#
?表示注释,从这个字符一直到行尾,都会被解析器忽略。
server: port: 8081 address: 127.0.0.1 name: zhang
server: port: 8082 name: zhang name2: wang #对象 person: name2: ${name} name: li age: 20 gender: 男 address: - beijing - shanghai #对象行内写法 person2: { name: zhao,age: 21 }
#数组 address: - beijing - shanghai
#数组 行内写法
address2: [ beijing,shanghai ]
#纯量、常量 msg1: 'hello \n world' msg2: "hello \n world"
# yml 多文档 格式 --- 进行分隔 配置特定环境
#激活环境 spring: profiles: active: pro --- #开发环境 server: port: 8081 spring: config: activate: on-profile: dev --- #测试环境 server: port: 8082 spring: config: activate: on-profile: test --- #生产环境 server: port: 8083 spring: config: activate: on-profile: pro
name: zhang name2: zhang #对象 person: name2: ${name} # 引用上边定义的name值 name: zhang
配置文件类型
yaml:简洁,以数据为核心
基本语法
大小写敏感
数据值前边必须有空格,作为分隔符
使用空格缩进表示层级关系,相同缩进表示同一级
数据格式
对象
数组: 使用 “- ”表示数组每个元素
纯量(常量)
参数引用
SpringBoot分别提供3中方式读取项目的application.properties配置文件的内容。这个方式分别为:Environment类、@Value注解以及@ConfigurationProperties注解。
package com.zhangguo.springboottest; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class ParamController { @Value("${name}") String name; @Value("${server.port}") String port; @RequestMapping("/param") public String param(){ return "Hello "+name+" from "+port+"!"; } }
配置文件:
server: port: 8088 spring: config: activate: on-profile: pro name: zhangguo age: 18
运行结果:
其实@Value底层就是Environment
application.yaml
#1、map,键值对,对象,自定义 name: zhangguo #键值对 age: 19 superman: #对象 name: jack #引用 age: 88 sex: 男 address: - zhuhai - guangzhou #对象的行内写法 superstar: {name: tom, sex: 男, address: [zhuhai,guangzhou] } #系统预定义配置 server: compression: enabled: true port: 8899 #字面量 info: 'Hello \n yaml' #转义,换行 msg: "Hello \n msg" #原样输入
EvnController:
package com.zhangguo.configdemo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Properties; import java.util.Set; @RestController public class EvnController { @Autowired Environment env; //自动装配对象 @RequestMapping("/env") public String config(){ String name=env.getProperty("name"); String port=env.getProperty("server.port"); String java_home=env.getProperty("JAVA_HOME"); String java_version=env.getProperty("java.runtime.version"); return "port="+port+",name="+name+",java_home="+java_home+",java_version="+java_version; } public static void main(String[] args) { Properties properties = System.getProperties(); //获取系统中所有配置 Set<String> strings = properties.stringPropertyNames(); for (String key : strings) { System.out.println(key+"===>"+properties.get(key)); } } }
运行结果:
首先建立一个Person类,定义姓名年龄,重写tostring方法
写入两个注解
@Component //让Springboot 识别这是一个bean @ConfigurationProperties // 读取配置文件中的 name和age public class Person { private String name; private int age; }
在控制器类中用@AutoWired读入Person
@Autowired private Person per;
该配置方法只能读到第一层的name与age数据。如果想读取配置文件中次级目录如Person分级下,需要在person类中的@ConfigurationProperties设置分级。
// 读取配置文件前缀为person键对应的对象中的 name和age @ConfigurationProperties(prefix = "person") public class Person { private String name; private int age; }
配置后在控制器中的
@Autowired private Person per; //输出就是配置中person对象内的字段信息了 System.out.println(per.getName()+" "+per.getAge());
示例:
Man实体类:
?View Code
控制器:
package com.zhangguo.configdemo.controller; import com.zhangguo.configdemo.entity.Man; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Properties; import java.util.Set; @RestController public class ManController { @Autowired Man man; //自动装配对象 @RequestMapping("/man") public Man config(){ return man; } }
运行结果:
小结:
1、@Environment @Value 不具有面向对象的特征,属性过多就不方便管理和控制,太多就会现得杂乱无章。而且也没用自动提示的功能。
2、@ConfigurationProperties 就是面向对象的机制,可以自动提示。所以底层springboto用的是@ConfigurationProperties
我们在开发Spring Boot应用时,通常同一套程序会被安装到不同环境,比如:开发、测试、生产等。其中数据库地址、服务 器端口等等配置都不同,如果每次打包时,都要修改配置文件,那么非常麻烦。profile功能就是来进行动态配置切换的。
profile是用来完成不同环境下,配置动态切换功能的。
示例一:多文档方式
application-test.yaml:
#测试环境 server: port: 8081 name: 测试环境
application-dev.yaml:
#开发环境 server: port: 8082 name: 开发环境
application-pro.yaml:
#运行环境 server: port: 8083 name: 运行环境
application.yaml:
#在配置文件中激活profile spring: profiles: active: test
运行时控制台信息:
?View Code
控制器:
package com.zhangguo.profiledemo; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ProfileController { @Value("${name}") private String name; @RequestMapping("/profile") public String profile(){ return name; } }
运行结果:
示例二:单文档方式
application.yaml
#在配置文件中激活profile spring: profiles: active: dev --- #测试环境 server: port: 8886 name: 测试环境 spring: config: activate: on-profile: test #当前偏好名称 --- #开发环境 server: port: 8887 name: 开发环境 spring: config: activate: on-profile: dev #当前偏好名称 --- #生产环境 server: port: 8888 name: 生产环境 spring: config: activate: on-profile: pro #当前偏好名称
激活的方法见2.4.2,运行结果如下:
(2)、设置参数
?
Springboot程序启动时,会从以下位置加载配置文件:
加载顺序为上文的排列顺序,高优先级配置的属性会生效。
(1)、src路径下的文件在编译后会放到WEB-INF/classes路径下吧。默认的classpath是在这里。用maven构建项目时候resources目录就是默认的classpath
classes含义:?
0、这是一个定位资源的入口?
1.存放各种资源配置文件 eg.init.properties log4j.properties struts.xml?
2.存放模板文件 eg.actionerror.ftl,静态资源文件
3.存放class文件 对应的是项目开发时的src目录编译文件?
classpath 和 classpath* 区别:?
classpath:只会到你的class路径中查找找文件;?
classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找。
(2)、项目的java与resources目录都将打包到classes目录下:
Spring是一个开源免费的框架,为了解决企业应用开发的复杂性而创建。Spring框架是一个轻量级的解决方案,可以一站式地构建企业级应用。Spring是模块化的,所以可以只使用其中需要的部分。可以在任何web框架上使用控制反转(IoC),也可以只使用Hibernate集成代码或JDBC抽象层。它支持声明式事务管理、通过RMI或web服务实现远程访问,并可以使用多种方式持久化数据。它提供了功能全面的MVC框架,可以透明地集成AOP到软件中。
Spring被设计为非侵入式的,这意味着你的域逻辑代码通常不会依赖于框架本身。在集成层(比如数据访问层),会存在一些依赖同时依赖于数据访问技术和Spring,但是这些依赖可以很容易地从代码库中分离出来。
Spring框架是基于Java平台的,它为开发Java应用提供了全方位的基础设施支持,并且它很好地处理了这些基础设施,所以你只需要关注你的应用本身即可。
Spring可以使用POJO(普通的Java对象,plain old java objects)创建应用,并且可以将企业服务非侵入式地应用到POJO。这项功能适用于Java SE编程模型以及全部或部分的Java EE。
那么,做为开发者可以从Spring获得哪些好处呢?
不用关心事务API就可以执行数据库事务;
不用关心远程API就可以使用远程操作;
不用关心JMX API就可以进行管理操作;
不用关心JMS API就可以进行消息处理。
①JMX,Java Management eXtension,Java管理扩展,是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
②JMS,Java Message Service,Java消息服务,是Java平台上有关面向消息中间件(MOM)的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发。
一句话概括:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
官网:?http://spring.io
文档:?Redirecting...、?GitHub - waylau/spring-framework-4-reference: Chinese translation of the Spring Framework 4.x Reference Documentation (https://docs.spring.io/spring/docs/4.3.13.RELEASE/spring-framework-reference/html/) .中文翻译《Spring Framework 4.x参考文档》
中文帮助:?http://spring.cndocs.ml/
框架下载地址:?http://repo.springsource.org/libs-release-local/org/springframework/spring/
教程:?Spring教程
Git:?Spring · GitHub
源码:?GitHub - spring-projects/spring-framework: Spring Framework
Jar包:?Releases · spring-projects/spring-framework · GitHub
2002年,Rod Jahnson在《Expert One-on-One J2EE Design and Development》书中首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
从2004年3月到现在,已经经历了1.0、1.1、1.2、2.0、2.5、3.0、3.1几个主要的版本
3.2.0版发布 2013年5月5日13:53?
3.2.10版发布 2014年7月15日23:58
3.2.9版发布 2014年5月20日12:22
4.0.0版发布 2013年12月12日07:50
4.0.1版发布 2014年1月28日20:55
4.1.6版发布 2015年3月25日16:40
4.2.2版发布 2015年10月15日12:57
4.2.5版发布 2016年2月25日09:28
4.3.5版发布 2016年12月21日11:34
4.3.6版发布 2017年1月25日14:05
4.3.8版发布 2017年4月18日13:49
4.3.9版发布 2017年6月7日19:29
5.0.0版发布 2017年9月28日11:28
5.0.1版发布 2017年10月24日15:14
5.3.19版现在
轻量:从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
控制反转Ioc:Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面Aop:Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
容器:Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
框架:Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
MVC:Spring的作用是整合,但不仅仅限于整合,Spring 框架可以被看做是一个企业解决方案级别的框架,Spring MVC是一个非常受欢迎的轻量级Web框架。
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
BeanFactory
,它是工厂模式的实现。BeanFactory
?使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。
Spring是一个开源的框架,现在的Spring框架构成了一个体系平台,通过Spring的官方网站http://www.springsource.org可以了解到,围绕着Spring框架本身,还有许多其他优秀的项目:
SpringFramework(Core):核心项目
Spring Web Flow:工作流项目
Spring Security:安全项目
Spring Batch:批量数据处理项目
Spring Android:Android系统支持项目
Spring Social:社交项目
Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具;Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。
Microservices:微服务是一种开发软件的架构和组织方法,其中软件由通过明确定义的 API 进行通信的小型独立服务组成。这些服务由各个小型独立团队负责。微服务架构使应用程序更易于扩展和更快地开发,从而加速创新并缩短新功能的上市时间。
Spring Cloud?是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发
Reactor是Spring提供的非阻塞式响应式编程框架,实现了Reactive Streams规范。 它提供了可组合的异步序列API
Serverless,又叫无服务器。Serverless?强调的是一种架构思想和服务模型,让开发者无需关心基础设施(服务器等),而是专注到应用程序业务逻辑上。
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中我们使用面向对象编程对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
在Springboot项目中都会存在一个SpringApplication的启动类,我们通过以下代码启动IOC容器。
SpringApplication.run(Application.class, args);
其实run方法会将创建的IOC容器作为返回值返回,那么我们就可以通过声明一个ApplicationContext对象来接收run方法的返回值。
public class SpringApplication { public static void main(String[] args) { ApplicationContext applicationContext = SpringApplication.run(Application.class, args); Object startSerive = applicationContext.getBean("startSerive"); } }
通过编写实现了ApplicationContextAware的工具类来获取IOC容器,当实现了ApplicationContextAware的类在容器中被初始化和加载后,会自动调用ApplicationContextAware中的setApplicationContext方法,将IOC容器传入setApplicationContext方法的形参中。
以下是用于获取IOC容器的工具类:
public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; public SpringContextUtil() { } /** * 设置上下文 * @param applicationContext * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringContextUtil.applicationContext == null) { SpringContextUtil.applicationContext = applicationContext; } } /** * 获取上下文 * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 通过名字获取上下文中的bean * @param name * @return */ public static Object getBean(String name){ return applicationContext.getBean(name); } /** * 通过类型获取上下文中的bean * @param requiredType * @return */ public static Object getBean(Class<?> requiredType){ return applicationContext.getBean(requiredType); } }
上面这个工具类只有在被IOC容器加载完之后才会调用setApplicationContext,那么该怎么把工具类放到IOC容器中呢?我们使用@Import注解来实现,具体使用方法请看下面代码:
@SpringBootApplication @Import({SpringContextUtil.class}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
注:不使用@Import也是可以的,例如在SpringContextUtil类上面标注@Component等类似的注解也是可以的。
@Import注解须知:
@Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中
加入IOC容器的方式有很多种,@Import注解可以用于导入第三方包 ,当然@Bean注解也可以,但是@Import注解快速导入的方式更加便捷
想要让一个普通类接受 Spring 容器管理,有以下方法
@Component : 作用在要装载的Bean上,用于容器扫描标识,加入到IOC容器中。@Repository、@Service、@Constroller、@RestConstroller也衍生自@Component
IBookDAO接口如下:
package com.zhangguo.Spring051.ioc01; /** * 图书数据访问接口 */ public interface IBookDAO { /** * 添加图书 */ public String addBook(String bookname); }
BookDAO实现
package com.zhangguo.Spring051.ioc02; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; /** * 图书数据访问实现类 */ @Component("bookdaoObj") public class BookDAO implements IBookDAO { public String addBook(String bookname) { return "添加图书"+bookname+"成功!"; } }
在类上增加了一个注解Component,在类的开头使用了@Component注解,它可以被Spring容器识别,启动Spring后,会自动把它转成容器管理的Bean。
除了@Component外,Spring提供了3个功能基本和@Component等效的注解,分别对应于用于对DAO,Service,和Controller进行注解。
1:@Repository 用于对DAO实现类进行注解。
2:@Service 用于对业务层注解,但是目前该功能与 @Component 相同。
3:@Constroller用于对控制层注解,但是目前该功能与 @Component 相同。
一般来说spring boot默认的扫描路径是启动类当前的包和子包。
标注了@Component和@Component的衍生注解如@Controller,@Service,@Repository就可以把当前的Bean加入到IOC容器中。那么SpringBoot是如何知道要去扫描@Component注解的呢。@ComponentScan做的事情就是告诉Spring从哪里找到bean
SpringBoot默认包扫描机制:?从启动类所在包开始,扫描当前包及其子级包下的所有文件。我们可以通过以下的测试来验证一下。
@ComponentScan : 常用在启动类上。
basePackages:标识扫描包路径(包括子包)下所有类,用于指定包的路径,进行扫描(默认参数)
excludeFilters={@Filter(classes={A.class,B.class})} :扫描排除
includeFilters={@Filter(classes={C.class,D.class})} : 扫描包含
basePackageClasses: 用于指定某个类的包的路径进行扫描
includeFilters: 包含的过滤条件
FilterType.ANNOTATION:按照注解过滤
FilterType.ASSIGNABLE_TYPE:按照给定的类型
FilterType.ASPECTJ:使用ASPECTJ表达式
FilterType.REGEX:正则
FilterType.CUSTOM:自定义规则
excludeFilters: 排除的过滤条件,用法和includeFilters一样
nameGenerator: bean的名称的生成器
useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测
@SpringBootApplication @Import(SpringContextUtil.class) @ComponentScan(value = "com.zhangguo",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})}) public class ProfiledemoApplication { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(ProfiledemoApplication.class, args); } }
@Condition来指定一定条件下注册组件对象到IoC容器中,所有的条件必须实现Condition接口,重写matches方法,来决定组件是否注册。
Spring Boot 包含多个 @Conditional 注释,可以在@Configuration注解的类和@Bean注解方法中使用。@Conditional类型的注解,可以注解在类上,可以注解在Bean方法上,可以允许基于Spring Environment属性包含配置,可以仅允许在存在特定资源时包含配置。
(一)、自定义条件注解
application.yml
isAdd: false #是否添加User Bean
IsAddCondition
package com.zhangguo.project1; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; /** * 自定义条件加载类 * */ public class IsAddCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment env = context.getEnvironment(); //获取配置文件 String isAdd=env.getProperty("isAdd"); //获取属性值 return isAdd!=null&&isAdd.toLowerCase().equals("true"); //是否为true } }
ConditionConfig:? 注解在方法上
package com.zhangguo.project1; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /**配置类*/ @Configuration public class ConditionConfig { @Bean @Conditional(IsAddCondition.class) public User getUser(){ return new User(); } }
ConditionConfig:? 注解在配置类上
package com.zhangguo.iocdemo2.demo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration @Conditional(IsAddUserCondition.class) public class ConditionConfig { }
测试:
package com.zhangguo.project1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.Conditional; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication public class Project1Application { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(Project1Application.class, args); User user=run.getBean(User.class); System.out.println(user); } }
(二)、系统定义
?逐个打开这 13 个注解,我们发现这些注解上有相同的元注解:
都可以应用在 TYPE 上,也就是说,Spring 自动扫描的一切类 (@Configuration, @Component, @Service, @Repository, or @Controller) 都可以通过添加相应的 @ConditionalOnXxxx 来判断是否加载
都可以应用在 METHOD 上,所以有 @Bean 标记的方法也可以应用这些注解
都是用了 @Conditional 注解来标记,OnBeanCondition 等自定义 Condition 还是实现了 Condition 接口的。
Spring Boot 包含多个 @Conditional 注释,可以在@Configuration注解的类和@Bean注解方法中使用。@Conditional类型的注解,可以注解在类上,可以注解在Bean方法上,可以允许基于Spring Environment属性包含配置,可以仅允许在存在特定资源时包含配置。也可自定义,接下来我们来熟悉一下 Spring Boot 提供的一些具体的条件注解。
?
1. ConditionalOnProperty
package com.zhangguo.project1; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @ConditionalOnProperty(value = "hello.enabled",havingValue = "true") public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello spring boot!"; } }
这个条件解释是: application.properties 或 application.yml 文件中?hello.enabled 为 true 才会加载?HelloController 这个 Bean,如果没有匹配上不会加载,因为 matchIfMissing 默认值是 false。
2. @ConditionalOnBean 和 ConditionalOnMissingBean
有时候我们需要某个 Bean 已经存在应用上下文时才会加载,那么我们会用到 @ConditionalOnBean 注解:
与之相反,有时候我们需要某个 Bean 不存在于应用上下文时才会加载,那么我们会用到 @ConditionalOnMissingBean 注解
package com.zhangguo.project1; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @ConditionalOnBean(User.class) public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello spring boot!"; } }
3.@ConditionalOnClass 和 @ConditionalOnMissingClass
和上面的一样,只不过判断某个类是否存在于 classpath 中
4.@ConditionalOnExpression
如果我们有更复杂的多个配置属性一起判断,那么我们就可以用这个表达式了:
只有当两个属性都为 true 的时候才加载 MyModule。
5.@ConditionalOnSingleCandidate
只有指定类已存在于 BeanFactory 中,并且可以确定单个候选项才会匹配成功
BeanFactory 存在多个 bean 实例,但是有一个 primary 候选项被指定(通常在类上使用 @Primary 注解),也会匹配成功。实质上,如果自动连接具有定义类型的 bean 匹配就会成功
目前,条件只是匹配已经被应用上下文处理的 bean 定义,本身来讲,强烈建议仅仅在 auto-configuration 类中使用这个条件,如果候选 bean 被另外一个 auto-configuration 创建,确保使用该条件的要在其后面运行
6.@ConditionalOnResource
如果我们要加载的 bean 依赖指定资源是否存在于 classpath 中,那么我们就可以使用这个注解
看到这个 logback.xml 是不是很亲切,在我们引入第三方工具类如 Dozer 等都可以添加类似的开关
7.@ConditionalOnJndi
只有指定的资源通过 JNDI 加载后才加载 bean
8.@ConditionalOnJava
只有运行指定版本的 Java 才会加载 Bean
9.@ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication
只有运行在 web 应用里才会加载这个 bean
与之相反,在非 web 环境才加载 bean
10.@ConditionalOnCloudPlatform
这个注解冷的我呼吸都要停止了,只有运行在指定的云平台上才加载指定的 bean,CloudPlatform 是 org.springframework.boot.cloud 下一个 enum 类型的类,大家可以打开自行看看:
用来指定bean的作用域
singleton---单例 ?只创建一个对象。
prototype---原型 ?想创建多少个就创建多少了。
request---针对Web项目,不同的请求创建单独的Bean对象,同一个请求共享一个Bean。
session---针对Web项目,不同的会话创建单独的Bean对象,同一个会话共享一个Bean。
默认情况下IoC容器中的Bean是单例的,多次获取的是同一个Bean对象:
package com.zhangguo.iocdemo2.demo; import org.springframework.stereotype.Component; @Component public class Student { }
测试是否相等:
package com.zhangguo.iocdemo2.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import; import javax.jws.soap.SOAPBinding; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args); Student s1=ctx.getBean(Student.class); Student s2=ctx.getBean(Student.class); System.out.println(s1==s2); } }
结果:
?如果想让Bean是非单例的,可以如下注解:
@Component @Scope("prototype") public class Student { }
测试:
?再测试时就为false了,因为两个Bean不是同一个对象。
Student 类
public class Student { }
配置类
@Configuration @Import(Student.class) public class ImportConfig { }
测试代码
@Test public void test7() { ApplicationContext ctx = new AnnotationConfigApplicationContext(ImportConfig.class); String[] beanDefinitionNames = ctx.getBeanDefinitionNames(); for (String name : beanDefinitionNames) { System.out.println(name); } }
spring 4.2 以前,该注解只能导入配置类。
@PostConstruct? 初始化方法的注解方式 等同与在XML中声明init-method=init
@PreDestroy 销毁方法的注解方式 等同于在XML中声明destory-method=destory
?View Code
示例:
BookDAO
package com.zhangguo.iocdemo2.demo; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Component public class BookDAO { public void add(){ System.out.println("添加图书成功!"); } /**创建该对象时调用*/ @PostConstruct public void init(){ System.out.println("准备创建该对象"); } /**当该对象被回收时调用*/ @PreDestroy public void destory(){ System.out.println("准备销毁该对象"); } }
测试:
package com.zhangguo.iocdemo2.demo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @SpringBootTest class DemoApplicationTests { @Test public void bookDAOTest(){ ApplicationContext ctx=new AnnotationConfigApplicationContext(DemoApplication.class); BookDAO bean = ctx.getBean(BookDAO.class); bean.add(); } }
结果:
如果使用Compont注解时不指定名称,基于@Componet及其扩展(如@Servic和自定义等)标注和classpath-scan定义的Bean,注解有一个value属性,如果提供了,那么就此Bean的名字。如果不提供。就会使用Spring默认的命名机制,即简单类名且第一个字母小写。
在类的开头使用了@Component注解,它可以被Spring识别,启动Spring后,会自动把它转成容器管理的Bean。
(1)、根据类型获取
@Component public class Car { }
测试
@Test public void NameTest(){ ApplicationContext ctx=new AnnotationConfigApplicationContext(DemoApplication.class); Car car1=ctx.getBean(Car.class); //根据类型获取bean }
(2)、根据默认名称获取
@Test public void NameTest(){ ApplicationContext ctx=new AnnotationConfigApplicationContext(DemoApplication.class); Car car2= (Car) ctx.getBean("car"); //根据默认名称获取bean }
(3)、根据特定名称获取
@Component("byd") public class Car { }
测试
@Test public void NameTest(){ ApplicationContext ctx=new AnnotationConfigApplicationContext(DemoApplication.class); Car car2= (Car) ctx.getBean("byd"); //根据自定义名称获取bean System.out.println(car2); }
从上面示例中可以看出有两个位置都使用了ApplicationContext初始化容器后获得需要的Bean,可以通过自动装配简化。
@Autowired 可以单独使用。如果单独使用,它将按类型装配。
@Autowired 默认按类型装配(这个注解是属于spring的),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required = false) 。
User类
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Component; @Component public class User { }
测试类
package com.zhangguo.autoconfigdemo; import javafx.application.Application; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @SpringBootTest class AutoconfigdemoApplicationTests { @Autowired(required = false) //按类型自动装配对象,required = true表示必须存在该对象,否则异常,如果false则为null User user; @Autowired ApplicationContext ctx; @Test public void testUser1(){ System.out.println(user); System.out.println(ctx); } }
结果:
因此,如果在容器中声明了多个相同类型的bean,则会出现问题,因为 @Autowired 不知道要使用哪个bean来注入。因此,使用 @Qualifier 与 @Autowired 一起,通过指定bean名称来阐明实际装配的bean (按姓名连线)。
@Qualifier 默认按名称装配(这个注解是属于spring的),value 默认@Qualifier(value = "")空值。
@Qualifier("XXX") 中的 XX是 Bean 的名称,所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了
IBookDAO接口
package com.zhangguo.autoconfigdemo; /**对图书访问的接口*/ public interface IBookDAO { void add(); //添加 }
OracleDAO实现类
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Repository; /**使用Oracle数据库访问图书*/ @Repository("oracle") public class OracleDAO implements IBookDAO { @Override public void add() { System.out.println("Oracle:添加图书成功!"); } }
MySQLDAO实现类
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Repository; /**使用MySQL数据库访问图书*/ @Repository("mysql") public class MySQLDAO implements IBookDAO { @Override public void add() { System.out.println("MySQL:添加图书成功!"); } }
BookDAOTest测试类
package com.zhangguo.autoconfigdemo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class BookDAOTest { @Autowired @Qualifier("mysql") /*根据名称装配*/ IBookDAO bookDAO; @Test public void bookTest(){ bookDAO.add(); } }
运行结果:
@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定, 如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
package com.zhangguo.autoconfigdemo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; @SpringBootTest public class BookDAOTest { @Resource(name = "oracle") IBookDAO bookDAO; @Test public void bookTest(){ bookDAO.add(); } }
运行结果:
@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Qualifier。另外可以使用其它注解,@ Resource :等同于@Qualifier,@Inject:等同于@ Autowired。
@Service用于注解业务层组件(我们通常定义的service层就用这个)
@Controller用于注解控制层组件(如struts中的action)
@Repository用于注解数据访问组件,即DAO组件
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行注解。
装配注解主要有:@Autowired、@Qualifier、@Resource,它们的特点是:
1、@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;
2、@Autowired默认是按照类型装配注入的,如果想按照名称来转配注入,则需要结合@Qualifier一起使用;
3、@Resource注解是J2EE提供,而@Autowired是由spring提供,故减少系统对spring的依赖建议使用@Resource的方式;如果Maven项目是1.5的JRE则需换成更高版本的。
4、@Resource和@Autowired都可以书写注解在字段或者该字段的setter方法之上
5、@Autowired 可以对成员变量、方法以及构造函数进行注释,而 @Qualifier 的注解对象是成员变量、方法入参、构造函数入参。
6、@Qualifier("XXX") 中的 XX是 Bean 的名称,所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。
7、@Autowired 注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个,通过属性required可以设置非必要。
8、@Resource装配顺序
8.1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
8.2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
8.3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
8.4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
区别:
区别在于 @Autowired 和 @Qualifier 是Spring注解,而 @Resource 是标准的java注解(来自JSR-250)。此外, @Resource 仅支持字段和setter注入,而 @Autowired 支持字段,setter,构造函数和多参数方法注入。
建议使用 @Resource 进行字段和setter注入。坚持使用 @Qualifier 和 @Autowired 进行构造函数或多参数方法注入。
@Primary : 优先级装载标识。如果多个子类装载,注入的时候优先注入子类上有@Primary标识的。
MySQLDAO
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Repository; /**使用MySQL数据库访问图书*/ @Repository public class MySQLDAO implements IBookDAO { @Override public void add() { System.out.println("MySQL:添加图书成功!"); } }
OracleDAO
package com.zhangguo.autoconfigdemo; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Repository; /**使用Oracle数据库访问图书*/ @Repository @Primary //优先被装配 public class OracleDAO implements IBookDAO { @Override public void add() { System.out.println("Oracle:添加图书成功!"); } }
测试
package com.zhangguo.autoconfigdemo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; @SpringBootTest public class BookDAOTest { @Autowired IBookDAO bookDAO; @Test public void bookTest(){ bookDAO.add(); } }
结果:
spring profile是Spring Framework 3.1以后推出一个解决切换环境变量的注解。主要解决一个环境问题切换的问题,其原理就是通过spring di在注入的时候通过环境变量来判断注入相应的环变量,以达到减少配置问题引起的各种麻烦。
IBookDAO
package com.zhangguo.autoconfigdemo; /**对图书访问的接口*/ public interface IBookDAO { void add(); //添加 }
MySQLDAO
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Repository; /**使用MySQL数据库访问图书*/ public class MySQLDAO implements IBookDAO { @Override public void add() { System.out.println("MySQL:添加图书成功!"); } }
OracleDAO
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Repository; /**使用MySQL数据库访问图书*/ public class MySQLDAO implements IBookDAO { @Override public void add() { System.out.println("MySQL:添加图书成功!"); } }
DbConfig
package com.zhangguo.autoconfigdemo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration public class DbConfig { @Bean //将该方法返回的实例添加到IoC容器中 @Profile("dev") //如果当前的环境是dev则注入该实例 public IBookDAO getOracle(){ return new OracleDAO(); } @Bean //将该方法返回的实例添加到IoC容器中 @Profile("pro") //如果当前的环境是pro则注入该实例 public IBookDAO getMySQL(){ return new MySQLDAO(); } }
测试类
package com.zhangguo.autoconfigdemo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class ProfileTest { @Autowired IBookDAO bookDAO; @Test public void addBookTest(){ bookDAO.add(); } }
application.yaml
spring: profiles: active: pro
结果
示例:
package spring13; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Scope; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class InjectTest { //注入给构造方法 @Autowired public InjectTest(IUserDao dao2) { this.dao2=dao2; } //注入给成员变量,字段 @Resource IUserDao dao1; IUserDao dao2; IUserDao dao3; IUserDao dao4; //注入给属性 @Autowired public void setDao3(IUserDao dao3) { this.dao3 = dao3; } //注入给方法参数 @Autowired public void injectDao4(IUserDao dao4, IUserDao dao5) { this.dao4 = dao4; System.out.println(dao5); } public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("bookbean13.xml"); InjectTest obj = ctx.getBean(InjectTest.class); System.out.println(obj.dao1); System.out.println(obj.dao2); System.out.println(obj.dao3); System.out.println(obj.dao4); } } interface IUserDao { } @Scope("prototype") @Repository class UserDao implements IUserDao { }
结果:
所谓的零配置就是不再使用xml文件来初始化容器,使用一个类型来替代
?IBookDAO代码如下:
package com.zhangguo.Spring051.ioc06; /** * 图书数据访问接口 */ public interface IBookDAO { /** * 添加图书 */ public String addBook(String bookname); }
IBookDAO的实现类BookDAO代码如下:
package com.zhangguo.Spring051.ioc06; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; /** * 图书数据访问实现类 */ @Repository public class BookDAO implements IBookDAO { public String addBook(String bookname) { return "添加图书"+bookname+"成功!"; } }
在BookDAO类上注解了@Repository当初始化时该类将被容器管理会生成一个Bean,可以通过构造方法测试。
业务层BookService代码如下:
package com.zhangguo.Spring051.ioc06; import javax.annotation.Resource; import org.springframework.stereotype.Service; /** * 图书业务类 */ @Service public class BookService { @Resource IBookDAO bookDAO; public void storeBook(String bookname){ System.out.println("图书上货"); String result=bookDAO.addBook(bookname); System.out.println(result); } }
类BookService将对容器管理因为注解了@Service,初始化时会生成一个单例的Bean,类型为BookService。在字段bookDAO上注解了@Resource,用于自动装配,Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
新增一个用于替代原xml配置文件的ApplicationCfg类,代码如下:
package com.zhangguo.Spring051.ioc06; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * 容器的配置类 */ @Configuration @ComponentScan(basePackages="com.zhangguo.Spring051.ioc06") public class ApplicationCfg { @Bean public User getUser(){ return new User("成功"); } }
@Configuration相当于配置文件中的<beans/>,ComponentScan相当于配置文件中的context:component-scan,属性也一样设置
,@Bean相当于<bean/>,只能注解在方法和注解上,一般在方法上使用,源码中描述:@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}),方法名相当于id。中间使用到了User,User类的代码如下:
package com.zhangguo.Spring051.ioc06; import org.springframework.stereotype.Component; @Component("user1") public class User { public User() { System.out.println("创建User对象"); } public User(String msg) { System.out.println("创建User对象"+msg); } public void show(){ System.out.println("一个学生对象!"); } }
初始化容器的代码与以前有一些不一样,具体如下:
package com.zhangguo.Spring051.ioc06; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Test { @org.junit.Test public void testStoreBook() { //容器,注解配置应用程序容器,Spring通过反射ApplicationCfg.class初始化容器 ApplicationContext ctx=new AnnotationConfigApplicationContext(ApplicationCfg.class); BookService bookservice=ctx.getBean(BookService.class); bookservice.storeBook("《Spring MVC权威指南 第四版》"); User user1=ctx.getBean("user1",User.class); user1.show(); User getUser=ctx.getBean("getUser",User.class); getUser.show(); } }
容器的初始化通过一个类型完成,Spring通过反射ApplicationCfg.class初始化容器,中间user1与getUser是否为相同的Bean呢?
答案是否定的,因为在ApplicationCfg中声明的方法getUser当相于在xml文件中定义了一个<bean id="getUser" class="..."/>,在User类上注解@Component("user1")相当于另一个<bean id="user1" class="..."/>。
运行结果:
小结:使用零配置和注解虽然方便,不需要编写麻烦的xml文件,但并非为了取代xml,应该根据实例需要选择,或二者结合使用,毕竟使用一个类作为容器的配置信息是硬编码的,不好在发布后修改。
package spring14; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Repository; public class NoXMLIoC { public static void main(String[] args) { //基于类型的配置 ApplicationContext ctx=new AnnotationConfigApplicationContext(AppCfg.class); ICarDao dao1=ctx.getBean(ICarDao.class); dao1.add("Spring Pro"); } } interface ICarDao{ void add(String name); } @Repository class CarDao implements ICarDao{ public void add(String name) { System.out.println("添加"+name+"成功!"); } } @Configuration @ComponentScan(basePackages = "spring14") class AppCfg{ }
运行结果:
package spring14; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Repository; public class NoXMLIoC { public static void main(String[] args) { //基于类型的配置 ApplicationContext ctx=new AnnotationConfigApplicationContext(AppCfg.class); ICarDao dao2=ctx.getBean("mysqlDao",ICarDao.class); dao2.add("Spring Pro"); } } interface ICarDao{ void add(String name); } class CarDao implements ICarDao{ public void add(String name) { System.out.println("添加"+name+"成功!"); } } @Configuration @ComponentScan(basePackages = "spring14") class AppCfg{ @Bean ICarDao mysqlDao(){ //方法名就是bean的name return new CarDao(); } }
运行结果:
package spring14; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Repository; public class NoXMLIoC { public static void main(String[] args) { //基于类型的配置 ApplicationContext ctx=new AnnotationConfigApplicationContext(AppCfg.class); ICarDao dao1=ctx.getBean("oracleDao",ICarDao.class); dao1.add("Spring Pro Oracle"); ICarDao dao2=ctx.getBean("mysqlDao",ICarDao.class); dao2.add("Spring Pro MySQL"); System.out.println(dao1==dao2); } } interface ICarDao{ void add(String name); } @Repository("oracleDao") class CarDao implements ICarDao{ public void add(String name) { System.out.println("添加"+name+"成功!"); } } @Configuration @ComponentScan(basePackages = "spring14") class AppCfg{ @Bean ICarDao mysqlDao(){ //方法名就是bean的name return new CarDao(); } }
运行结果:
1、完成每一个上课示例
2、完成一个我的任务功能,可以记录自己要完成的任务清单。
?实现添加,删除,变更状态的功能,要求如下:
(1)、需要用entity、dao、service、controller分层开发,使用IoC。
(2)、数据不需要存在数据库中,初始数据使用对象数组。
(3)、dao层与controller层每个方法都需要完成单元测试。
(4)、profile:开发模式时我的任务字体为蓝色,8881端口,运行模式时我的任务字体为红色,8882端口。
(5)、 打包,控制台运行,脱离开发环境。
作业解答:https://www.bilibili.com/video/BV1Hs411F71x?share_source=copy_web