Spring Boot的底层原理
之前学习过了Spring Boot(在88章博客),但是并没有很深入的了解,这里致力于在原来的基础上学习更多Spring Boot知识
回顾(注意:只是回顾,所以更多细节在88章博客):
约定优于配置:
Spring Boot 是所有基于 Spring 开发的项目的起点,Spring Boot 的设计是为了让你尽可能快的跑起来,Spring 应用程序并且尽可能减少你的配置文件
约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式
本质上是说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置,比如说模型中有一个名为User的类,那么数据库中对应的表就应该默认命名为user,只有在偏离这一个约定的时候,例如想要将该表命名为person,才需要写有关这个名字的配置
比如平时架构师搭建项目就是限制软件开发随便写代码,制定出一套规范,让开发人员按统一的要求进行开发编码测试之类的,这样就加强了开发效率与审查代码效率,所以说写代码的时候就需要按要求命名,这样统一规范的代码就有良好的可读性与维护性了
约定优于配置简单来理解,就是遵循约定,比如,现在有一个依赖,我Spring需要他,那么Spring定义一部分与他连接,形成一个新的依赖,让这个依赖使用,这个时候Spring可以直接使用他,再考虑自动的配置类等等,于是乎,由于这样的约定越来越多,就形成了对应的起始依赖(通用的,或者某些考虑自动的配置,这里再后面会说明,通常在自动配置的原理那里,一般是一开始的父依赖),并且再后续进行维护,且许多框架都来进行整合,这样的整体,就是Spring Boot了,所以说Spring Boot不只是一个思想(约定),也是具体实现(依赖),只不过是各个框架整体的实现
SpringBoot概念:
Spring优缺点:
优点: spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品,无需开发重量级的 Enterprise JavaBean(EJB),Spring为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java对象(Plain Old Java Object,POJO)实现了EJB的功能
缺点: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的,一开始,Spring用XML配置,而且是很多XML配置,Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML 配置,Spring 3.0引入 了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML,所有这些配置都代表了开发时的损耗,因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间,和所有框架一样,Spring实用,但与此同时它要求的回报也不少,除此之外,项目的依赖管理也是一件耗时耗力的事情,在环境搭建时,需要分析要导入哪些库的坐标, 而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题 就会严重阻碍项目的开发进度,还有,虽然在一定程度上简化了配置处理,但是其配置在一定量的代码下,比xml需要更多的性能,只不过大多数是忽略的,当然,这是建立在扫描很多包的情况下(前面博客很多情况下,说明的都是这个),通常来说xml由于需要操作IO,所以性能会更加的大,也就是说,平常注解的开销就是比xml要小,所以建议使用注解,当然了,为了更加的明确,这里就给出具体的情况吧,考虑任何情况下注解和xml的区别:
注解和xml的区别和优缺点:
注解:通过反射来完成的配置,在代码里面进行处理
xml:通过配置文件来完成的配置,在配置文件中进行处理
在读取方面的区别:一个是反射,一个是IO
在运行后方面的区别:在代码中不可修改,在配置文件中可以修改
在开发方便的区别:注解简单,xml编写困难
这样要考虑性能,就看反射开销和IO开销谁大,看维护性,就看是否需要动态的修改(前提是可以读取)
那么由于通常情况下IO的开销是比较大的,所以呢,在不考虑其他因素,那么注解的启动效率基本都高于xml(读取后就固定了,所以只会考虑启动),但是由于注解绝对的运行时不可改变,所以在后续维护中也必然小于xml
从上面讲,那么需要考虑如下的问题:
考虑启动的快慢:那么使用注解
考虑后续的维护:那么使用xml
考虑开发的便捷:那么使用注解
很明显,在现在的环境下,我们通常需要启动快,便捷的方式,所以注解的操作现在非常流行
SpringBoot解决上述spring的问题:
SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短 了项目周期
起步依赖 :
起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能,简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能,通常用来解决依赖多的情况(一个依赖包含多个依赖)
自动配置:
springboot的自动配置,指的是springboot,会自动将一些配置类的bean注册进ioc容器(有些通常需要指定),我们可以需要的地方使用@autowired或者@resource等注解来使用它,"自动"的表现形式就是我们只需要引我们想用功能的包,相关的配置我们完全不用管,springboot会自 动注入这些配置bean,我们直接使用这些bean即可,所以springboot可以简单、快速、方便地搭建项目,对主流开发框架的无配置集成,极大提高了开发、部署效率(具体之所以可以配置是因为他依赖整合的),解决需要手动扫描的问题(虽然也是一个注解造成的全扫描(这个扫描是任何可以的框架的扫描,而不是单独的,所以解决了手动扫描的问题),但是我只需要一个注解即可)
现在看一下案例吧,创建项目(不会,到88章博客学习吧,也记得配置好maven哦):
对应的依赖:
<?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.7.2</ version>
< relativePath/>
</ parent>
< groupId> com</ groupId>
< artifactId> boot</ artifactId>
< version> 0.0.1-SNAPSHOT</ version>
< name> boot</ name>
< description> boot</ description>
< properties>
< java.version> 11</ 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>
< build>
< plugins>
< plugin>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-maven-plugin</ artifactId>
</ plugin>
</ plugins>
</ build>
</ project>
对应的项目:
在boot包下,创建controller包,然后创建HelloController类:
package com. boot. controller ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "hello" )
public class HelloController {
@RequestMapping ( "/boot" )
public String helloBoot ( ) {
return "Hello Spring Boot" ;
}
}
启动类:
package com. boot ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
@SpringBootApplication
public class BootApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( BootApplication . class , args) ;
}
}
我们直接的启动上面的启动类,http://localhost:8080/hello/boot,上面的依赖版本和jdk版本是需要适配的,否则可能启动不了,还有,在104章博客中,还有关于一些项目版本的说明,可以看一看
如果访问后出现了数据,那么我们操作成功了
单元测试与热部署 :
单元测试 :
开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确,Spring Boot对项目的单元测试提供了很好的支持,在使用时,需要提前在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器,快速构建springboot项目一般会加上该依赖,即使用Spring Initializr方式搭建的Spring Boot项目,会自动加入spring-boot-starter-test测试依赖启动器,无需再手动添加,然后可以通过相关注解实现单元测试
依赖:
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-test</ artifactId>
< scope> test</ scope>
</ dependency>
< dependency>
< groupId> junit</ groupId>
< artifactId> junit</ artifactId>
< scope> test</ scope>
</ dependency>
在测试类中BootApplicationTests中加上如下:
package com. boot ;
import com. boot. controller. HelloController ;
import org. junit. jupiter. api. Test ;
import org. junit. runner. RunWith ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. boot. test. context. SpringBootTest ;
import org. springframework. test. context. junit4. SpringRunner ;
@RunWith ( SpringRunner . class )
@SpringBootTest
class BootApplicationTests {
@Autowired
private HelloController helloController;
@Test
public void contextLoads ( ) {
String s = helloController. helloBoot ( ) ;
System . out. println ( s) ;
}
}
启动,若有结果说明操作成功
热部署:
在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率,为此, Spring Boot框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,而无需手动重启项目(也就是重新执行启动类,或者说重新执行启动类的main方法)
简单来说,热部署就是:在修改完代码之后(无论是配置文件还是类,基本只要是项目的进行了修改就会更新)
不需要重新启动容器,就可以实现更新,但是需要等待他更新
等日志出现,那么才会真正的部署,中途再次改变,会影响日志的出现
一般会迟一点,因为有缓冲等待(大概等几秒才更新),防止你频繁的更新(每次的改变,基本会重置该等待)
使用步骤:
1:添加SpringBoot的热部署依赖启动器
2:开启Idea的自动编译
3:开启Idea的在项目运行中自动编译的功能
添加spring-boot-devtools热部署依赖启动器:
在Spring Boot项目进行热部署测试之前,需要先在项目的pom.xml文件中添加spring-boot-devtools热部署依赖启动器:
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-devtools</ artifactId>
</ dependency>
由于使用的是IDEA开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对IDEA开发工具进行热部署相关的功能设置
IDEA工具热部署设置:
选择IDEA工具界面的【File】 ->【Settings】选项,打开Compiler面板设置页面
然后启动项目(不是测试的),访问:http://localhost:8080/hello/boot,然后修改打印信息,直接看看结果吧
全局配置文件 :
全局配置文件能够对一些默认配置值进行修改
Spring Boot使用一个application.properties或者application.yaml的文件作为全局配置文件
该文件存放在src/main/resource目录或者类路径的/config,一般会选择resource目录
接下来,将针对这两种全局配置文件进行讲解 :
Spring Boot配置文件的命名及其格式:
application.properties,application.yaml,application.yml(前面一个的简写,相当于是一样的)
application.properties配置文件 :
使用Spring Initializr方式构建Spring Boot项目时,会在resource目录下自动生成一个空的application.properties文件(通常是空的)
Spring Boot项目启动时会自动加载application.properties文件
我们可以在application.properties文件中定义Spring Boot项目的相关属性
当然,这些相关属性可以是系统属性、环境变量、命令参数等等信息,也可以是自定义配置文件名称和位置
比如:
#修改tomcat的端口号(有些时候也可以说是版本号)
server.port=8888
现在我们重新启动,就需要访问http://localhost:8888/hello/boot了
还需要注意一点:IDEA 通常会自动保存管理的项目的文件的(是管理的项目哦,通常指该文件夹下的所有,也可以认为是.idea所在文件夹下面的所有)
比如:
1:窗口切换:当你切换到另一个应用程序或窗口时,IDEA 会自动保存当前的更改
2:运行/调试:当你运行或调试项目时,IDEA 会自动保存所有未保存的文件
3:版本控制操作:在执行与版本控制相关的操作(如提交、推送、拉取)时,IDEA 会自动保存所有更改
4:特定时间间隔:如果启用了相应的设置,IDEA 会在一段时间的空闲之后自动保存文件(通常来说是默认启动的)
这些都会使得保存文件,所以在idea中不用担心文件没有保存哦,如果不放心可以ctrl+s保存一下的
继续说明全局配置文件,我们可以给全局配置文件加上如下:
#注意:
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver中
#他后面不能有空格,否则报错,这是一个特殊的地方,当然可以不写,会根据驱动默认加上的(这一般是spring boot自身的处理,其他的如单纯的spring可能还是需要指定)
#其他的基本都可以有空格(且空格无影响,相当于没有什么,基本不会参与到数值里面去)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ceshi
spring.datasource.username=root
spring.datasource.password=123456
操作了对应的数据库,那么一般需要对应的驱动包
spring boot的依赖中并不是所有的包都有传递,有些需要自己加上,如这个驱动包:
< dependency>
< groupId> mysql</ groupId>
< artifactId> mysql-connector-java</ artifactId>
< version> 6.0.6</ version>
</ dependency>
还需要如下的包:
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-jdbc</ artifactId>
< version> 2.7.1</ version>
</ dependency>
至此我们可以操作如下:
package com. boot. controller ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. jdbc. core. JdbcTemplate ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "hello" )
public class HelloController {
@RequestMapping ( "/boot" )
public String helloBoot ( ) {
return "Hello Spring Boot" ;
}
@Autowired
private JdbcTemplate jdbcTemplate;
@RequestMapping ( "/jdbc" )
public String jdbc ( ) {
return jdbcTemplate. toString ( ) ;
}
}
访问一下吧,http://localhost:8888/hello/jdbc
为了进一步的说明配置文件,我们在boot包下创建pojo包,然后创建如下的类:
package com. boot. pojo ;
public class Pet {
private String type;
private String name;
@Override
public String toString ( ) {
return "Pet{" +
"type='" + type + '\'' +
", name='" + name + '\'' +
'}' ;
}
public String getType ( ) {
return type;
}
public void setType ( String type) {
this . type = type;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
}
package com. boot. pojo ;
import org. springframework. boot. context. properties. ConfigurationProperties ;
import org. springframework. stereotype. Component ;
import java. util. Arrays ;
import java. util. List ;
import java. util. Map ;
@Component
@ConfigurationProperties ( prefix = "person" )
public class Person {
private int id;
private String name;
private List hobby;
private String [ ] family;
private Map map;
private Pet pet;
@Override
public String toString ( ) {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", hobby=" + hobby +
", family=" + Arrays . toString ( family) +
", map=" + map +
", pet=" + pet +
'}' ;
}
public int getId ( ) {
return id;
}
public void setId ( int id) {
this . id = id;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
public List getHobby ( ) {
return hobby;
}
public void setHobby ( List hobby) {
this . hobby = hobby;
}
public String [ ] getFamily ( ) {
return family;
}
public void setFamily ( String [ ] family) {
this . family = family;
}
public Map getMap ( ) {
return map;
}
public void setMap ( Map map) {
this . map = map;
}
public Pet getPet ( ) {
return pet;
}
public void setPet ( Pet pet) {
this . pet = pet;
}
}
@ConfigurationProperties(prefix = “person”)注解的作用:
将配置文件中以person开头的属性值通过setXX()方法注入到实体类对应属性中
@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中
只有这样才能被@ConfigurationProperties注解进行赋值,就如@Value一样需要对应的实例
但是若@ConfigurationProperties注解和@Value共存,那么@ConfigurationProperties注解会覆盖@Value注解的操作
若@Value注解报错,那么启动基本就会报错,使得访问不了
打开项目的resources目录下的application.properties配置文件,在该配置文件中编写需要对Person类设置的配置属性
#自定义配置信息注入到Person对象中
person.id=100
person.name=哈哈
#list
person.hobby=喝水,吃早餐
#String[]
person.family=儿子,老婆
#集合和对象,一般都需要多一个层(多了.)
#Map
person.map.k1=v1
person.map.k2=v2
#Pet对象
person.pet.type=狗
person.pet.name=旺财
#他们可以使用@Value注解来进行注入(对应的参数获取),因为他们是全局的
在测试类这加上如下:
@Autowired
private Person person;
@Test
public void configurationTest ( ) {
System . out. println ( person) ;
}
若返回数据则代表注入成功,即操作成功
具体的编码问题可以参考88章博客
application.yaml(yml)配置文件:
YAML文件格式是Spring Boot支持的一种JSON文件格式,相较于传统的Properties配置文件, YAML文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式
application.yaml配置文件的工作原理和application.properties是一样的,只不过yaml格式配置文件看起来更简洁一些,他们的区别主要在于基本上springboot的配置他们都有联系,但是并非都有编写,所以大多数情况下,可以通过properties来得到yml的编写,但是有些只有yml才可以或者只有properties才可以(少部分),相当于整合的依赖中,都存在他们的配置,只不过有些只操作yml,有些只操作properties,现在我们基本考虑yml,所以后面也基本以这个为主的(yaml与yml是一样的,解析也是一样,当然,可能某些只会看后缀名称,但是这里非常少,所以这里不考虑)
YAML文件的扩展名可以使用.yml或者.yaml,application.yml文件使用 "key:(空格) value"格式配置属性,使用缩进控制层级关系
SpringBoot的三种配置文件是可以共存的,也就是说,可以写这三个配置文件,都会进行读取:
< includes>
< include> **/application*.yml</ include>
< include> **/application*.yaml</ include>
< include> **/application*.properties</ include>
</ includes>
所以在某种程度上,我们建议使用一个配置文件,通常简单点,也就是yml,我们删除我们的properties的所有内容,修改后缀为yml
然后删除pojo包,再在controller对应的类中和测试类中,去掉关于之前数据库配置的代码,然后再删除数据库相关依赖,再在yml中添加如下:
案例:
server :
port : 8080
servlet :
context-path : /hello
启动项目,访问http://localhost:8080/hello/hello/boot,就有数据了
还有一些在yml中关于属性编写的介绍,但是这些细节太多,请到88章博客查看
配置文件属性值的注入:
使用Spring Boot全局配置文件设置属性时:
如果配置属性是Spring Boot已有属性,例如服务端口server.port,那么Spring Boot内部自动扫描并读取这些配置文件时,对应的属性值覆盖默认属性,如果配置的属性是用户自定义属性,例如刚刚自定义的Person实体类属性,则不会自动的覆盖,因为没有,那么他只是定义,并没有操作,需要我们在程序中手动注入这些配置属性方可操作,而不是自动的使用(因为定义)
那么实际上也可以这样的操作:如@Value(“${server.port}”),那么可以注入对应的端口值,因为虽然他覆盖了对应的默认属性,但他任然是定义的,既然是定义的,就可以使用
总体而言:该配置在spring boot中多了一个已有属性进行覆盖,其余的与普通的配置存放信息文件是一样的,需要被使用(如properties文件,以前有操作数据库的信息,那时就是被使用),然后使用注解注入对应的实例,当然若没有对应的注入配置属性,那么对应的实例自然是使用默认的值的,当然他们注入的方式基本都是扫描时进行操作的,只有扫描时,对应的注解操作才会进行
而对应的配置文件信息(写的信息)实际上也是使用后的再扫描的(在spring中也有注解和配置文件操作他,他基本是全局的)
Spring Boot支持多种注入配置文件属性的方式,下面来介绍如何使用注解@ConfigurationProperties和@Value注入属性
使用@ConfigurationProperties注入属性:
Spring Boot提供的@ConfigurationProperties注解
用来快速、方便地将配置文件中的自定义属性值批量注入到某个Bean对象的多个对应属性中
前面的操作中,我们就使用了这个注解并说明了,所以这里就不作说明
实际上上面的注解方式不够灵活,要想要更加的灵活(也就是不够更加的设置具体的细节),一般使用如下方式进行注入属性值
使用@Value注入属性:
@Value注解是Spring框架提供的,用来读取配置文件中的属性值并逐个注入到Bean对象的对应属性中
Spring Boot框架从Spring框架中对@Value注解进行了默认继承
所以在Spring Boot框架中还可以使用该注解读取和注入配置文件属性值,使用@Value注入属性的示例代码如下
@Value ( "${person.id}" )
private int id;
上述代码中,使用@Component和@Value注入Person实体类的id属性
其中,@Value不仅可以将配置文件的属性注入Person的id属性,还可以直接给id属性直接的赋值,如@Value(“1”),直接赋值为1
这点是@ConfigurationProperties不支持的,因为他只能去配置文件里加上对应的属性及其值才可,不够灵活,且使得配置文件信息变多
具体操作这里就不说明了
自定义配置:
spring Boot免除了项目中大部分的手动配置,对于一些特定情况,我们可以通过修改全局配置文件以适应具体生产环境,可以说,几乎所有的配置都可以写在application.yml文件中
Spring Boot会自动加载全局配置文件从而免除我们手动加载的烦恼(因为设置好的三个)
但是,如果我们自定义配置文件,Spring Boot是无法识别这些配置文件的(因为只有那三个可以),此时就需要我们手动加载
接下来,将针对Spring Boot的自定义配置文件及其加载方式进行讲解
使用@PropertySource加载配置文件:
对于这种加载自定义配置文件的需求,可以使用@PropertySource注解来实现,@PropertySource注解用于指定自定义配置文件的具体位置和名称,当然,如果需要将自定义配置文件中的属性值注入到对应类的属性中,可以使用@ConfigurationProperties或者@Value注解进行属性值注入,因为自定义配置文件与其他三个配置文件一样,都被读取操作了,自然结果是一样的,只是不会自动读取操作该自定义的配置文件而已,需要手动读取操作
现在我们演示:
打开Spring Boot项目的resources目录,在项目的类路径下新建一个test.properties自定义配置文件,在该配置文件中编写需要设置的配置属性
#对实体类对象MyProperties进行属性配置
test.id=110
test.name=test
在com.boot包下创建pojo包,然后创建一个配置类MyProperties
提供test.properties自定义配置文件中对应的属性,并根据@PropertySource注解的使用进行相关配置
package com. boot. pojo ;
import org. springframework. boot. context. properties. ConfigurationProperties ;
import org. springframework. context. annotation. PropertySource ;
import org. springframework. stereotype. Component ;
@Component
@PropertySource ( "classpath:test.properties" )
@ConfigurationProperties ( prefix = "test" )
public class MyProperties {
private int id;
private String name;
@Override
public String toString ( ) {
return "MyProperties{" +
"id=" + id +
", name='" + name + '\'' +
'}' ;
}
public int getId ( ) {
return id;
}
public void setId ( int id) {
this . id = id;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
}
进行测试:
@Autowired
private MyProperties myProperties;
@Test
public void myPropertiesTest ( ) {
System . out. println ( myProperties) ;
}
使用@Configuration编写自定义配置类:
在Spring Boot框架中,推荐使用配置类的方式向容器中添加和配置组件
在Spring Boot框架中,通常使用@Configuration注解定义一个配置类
Spring Boot会自动扫描和识别配置类,从而替换传统Spring框架中的XML配置文件
当定义一个配置类后,还需要在类中的方法上使用@Bean注解进行组件配置,将方法的返回对象注入到Spring容器中
并且组件名称默认使用的是方法名,当然也可以使用@Bean注解的name或value属性自定义组件的名称
演示:
在项目下新建一个com.boot.config包,并在该包下新创建一个类MyConfig,该类中不需要编写任何代码
而该类目前没有添加任何配置和注解,因此还无法正常被Spring Boot扫描和识别
创建了一个com.boot.service包,里面创建空的MyService类,用来操作
接下来使用@Configuration注解将该MyConfig类声明一个配置类,内容如下:
@Configuration
public class MyConfig {
@Bean
public MyService myService ( ) {
return new MyService ( ) ;
}
}
MyConfig是@Configuration注解声明的配置类(类似于声明了一个XML配置文件)
该配置类会被Spring Boot自动扫描识别,使用@Bean注解的myService()方法
其返回值对象会作为组件添加到了Spring容器中(类似于XML配置文件中的标签配置),并且该组件的id默认是方法名myService
测试类:
@Autowired
private MyService myService;
@Autowired
private MyConfig myConfig;
@Test
public void iocTest ( ) {
System . out. println ( myService) ;
System . out. println ( myConfig) ;
}
若返回数据,则代表注入成功,当然也可以操作如下:
@Autowired
private ApplicationContext applicationContext;
@Test
public void Test ( ) {
System . out. println ( applicationContext. getBean ( "myService2" ) ) ;
System . out. println ( applicationContext. containsBean ( "myService2" ) ) ;
}
本质上是spring的知识,只不过spring boot整合了而已(spring boot只是对应注解使得操作了而已,也就是扫描)
如果硬要说的话,spring boot本身基本没有任何注解,就算有,也非常少,他的注解主要是他引入的依赖中spring造成的,他自身的具体作用就是依赖管理相关,和自动配置相关,当然了启动类的注解还是他的,只不过内部基本操作了spring的,当然,如果看包含关系,那么也可以说成spring注解是spring boot的
随机数设置及参数间引用:
在Spring Boot配置文件中设置属性时,除了可以像前面示例中显示的配置属性值外,还可以使用 随机值和参数间引用对属性值进行设置,下面,针对配置文件中这两种属性值的设置方式进行讲解
随机值设置:
在Spring Boot配置文件中,随机值设置使用到了Spring Boot内嵌的 RandomValuePropertySource类,对一些隐秘属性值或者测试用例属性值进行随机值注入,随机值设置的语法格式为${random.xx},xx表示需要指定生成的随机数类型和范围,它可以生成随机的整数、uuid或字符串,示例代码如下:
my.secret=${random.value} // 配置随机值
my.number=${random.int} // 配置随机整数
my.bignumber=${random.long} // 配置随机long类型数
my.uuid=${random.uuid} // 配置随机uuid类型数
my.number.less.than.ten=${random.int(10)} // 配置小于10的随机整数
my.number.in.range=${random.int[1024,65536]} // 配置范围在[1024,65536]之间的随机整数
上述代码中,使用RandomValuePropertySource类中random提供的随机数类型,分别展示了不同类型随机值的设置示例 ,本质上是读取配置时,识别形成的
我们来测试:
首先在pojo包下,创建MyTest类:
package com. boot. pojo ;
import org. springframework. boot. context. properties. ConfigurationProperties ;
import org. springframework. stereotype. Component ;
@Component
@ConfigurationProperties ( prefix = "test" )
public class MyTest {
private int id;
private String name;
@Override
public String toString ( ) {
return "MyTest{" +
"id=" + id +
", name='" + name + '\'' +
'}' ;
}
public int getId ( ) {
return id;
}
public void setId ( int id) {
this . id = id;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
}
我们可以测试,在yml(只有少部分与properties是不同的识别,这里肯定类似的)中加上如下:
test :
id : ${
random.int}
name : ${
random.int[ 1024 , 65536 ] }
测试类:
@Autowired
private MyTest myTest;
@Test
public void fa ( ) {
System . out. println ( myTest) ;
}
多次的执行看看打印结果吧
参数间引用:
在Spring Boot配置文件中,配置文件的属性值还可以进行参数间的引用,也就是在后一个配置的属性值中直接引用先前已经定义过的属性,这样可以直接解析其中的属性值了, 使用参数间引用的好处就是,在多个具有相互关联的配置属性中,只需要对其中一处属性预先配置,其他地方都可以引用,省去了后续多处修改的麻烦,参数间引用的语法格式为${xx},xx表示先前在配置文件中已经配置过的属性名,示例代码如下:
app.name=MyApp
app.description=${app.name} is a Spring Boot application
我们继续修改上面的操作,在前面的yml中加上如下:
test :
ui : 8
id : ${
random.int}
name : ${
random.int[ 1024 , 65536 ] }
de : ${
test.id} is ${
test.ui}
在对应的MyTest类中加上如下:
private String de;
public String getDe ( ) {
return de;
}
public void setDe ( String de) {
this . de = de;
}
@Override
public String toString ( ) {
return "MyTest{" +
"id=" + id +
", name='" + name + '\'' +
", de='" + de + '\'' +
'}' ;
}
继续访问测试类看看结果吧,这个时候你会发现一个问题,在上面的配置中:
test :
ui : 8
id : ${
random.int}
name : ${
random.int[ 1024 , 65536 ] }
de : ${
test.id} is ${
test.ui}
SpringBoot原理深入及源码剖析:
在前面88章博客中,这里的说明比较粗糙,那么这里我们来进行更加细节的说明
依赖管理:
为什么导入dependency时不需要指定版本,这是因为项目pom.xml文件有两个核心依赖,这里以前面为主:
< parent>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-parent</ artifactId>
< version> 2.7.2</ version>
< relativePath/>
</ parent>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-web</ artifactId>
</ dependency>
首先我们先说明spring-boot-starter-parent
进入他,他里面配置一些基本的处理,通常有对应的三个配置文件,但是这不是我们需要的,我们进入他的:
< parent>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-dependencies</ artifactId>
< version> 2.7.2</ version>
</ parent>
核心代码具体如下:
< properties>
< activemq.version> 5.16.5</ activemq.version>
< antlr2.version> 2.7.7</ antlr2.version>
< appengine-sdk.version> 1.9.98</ appengine-sdk.version>
< artemis.version> 2.19.1</ artemis.version>
< aspectj.version> 1.9.7</ aspectj.version>
...
这是pom.xml引入依赖文件不需要标注依赖文件版本号的原因,这是依赖管理
spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来的,很明显,就是对应的spring-boot-starter-web,在上面的spring-boot-dependencies后面是有对应的
所以在结合依赖管理以及其他自身存在的依赖操作,也就形成了我们的起步依赖,当然了,这里的起步并不是指spring boot原始依赖,是指功能加上后的,单纯来说spring boot对依赖的只有管理和部分起步的依赖
自动配置:
如果说依赖管理不算是spring boot的,那么这里绝对是,这是最重要的
在这里需要提一点,一般不同的版本的boot,对应的代码显示是不同的(上面的依赖管理的内容可能也会不同)
但大致底层原理是一样的,后面有时会说明一下,所以注意即可
概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置
我们无需配置或者只需要少量配置就能运行编写的项目
Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置,这些的思考离不开我们补充的依赖,由于没有配置文件,也就是具体的xml,那么基本上都是操作配置类,而我们加上的依赖自然是存在配置类的,且由于是整合了spring boot,所以在满足约定的情况下,扫描的可以扫描到的,那么我们就只需要直到他是怎么扫描的即可
Spring Boot应用的启动入口是@SpringBootApplication注解标注的类中的main()方法
package com. boot ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
@SpringBootApplication
public class BootApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( BootApplication . class , args) ;
}
}
配置类自身是作为bean的,所以只需要考虑配置类的扫描处理,那么上面的注解是最重要的,我们看看他到底怎么操作扫描,或者他使得spring在什么时候操作扫描,我们直接的进入他:
package org. springframework. boot. autoconfigure ;
import java. lang. annotation. Documented ;
import java. lang. annotation. ElementType ;
import java. lang. annotation. Inherited ;
import java. lang. annotation. Retention ;
import java. lang. annotation. RetentionPolicy ;
import java. lang. annotation. Target ;
import org. springframework. beans. factory. support. BeanNameGenerator ;
import org. springframework. boot. SpringBootConfiguration ;
import org. springframework. boot. context. TypeExcludeFilter ;
import org. springframework. context. annotation. ComponentScan ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. context. annotation. FilterType ;
import org. springframework. context. annotation. ComponentScan . Filter ;
import org. springframework. core. annotation. AliasFor ;
@Target ( {
ElementType . TYPE } )
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan (
excludeFilters = {
@Filter (
type = FilterType . CUSTOM ,
classes = {
TypeExcludeFilter . class }
) , @Filter (
type = FilterType . CUSTOM ,
classes = {
AutoConfigurationExcludeFilter . class }
) }
)
public @interface SpringBootApplication {
@AliasFor (
annotation = EnableAutoConfiguration . class
)
Class < ? > [ ] exclude ( ) default {
} ;
@AliasFor (
annotation = EnableAutoConfiguration . class
)
String [ ] excludeName ( ) default {
} ;
@AliasFor (
annotation = ComponentScan . class ,
attribute = "basePackages"
)
String [ ] scanBasePackages ( ) default {
} ;
@AliasFor (
annotation = ComponentScan . class ,
attribute = "basePackageClasses"
)
Class < ? > [ ] scanBasePackageClasses ( ) default {
} ;
@AliasFor (
annotation = ComponentScan . class ,
attribute = "nameGenerator"
)
Class < ? extends BeanNameGenerator > nameGenerator ( ) default BeanNameGenerator . class ;
@AliasFor (
annotation = Configuration . class
)
boolean proxyBeanMethods ( ) default true ;
}
从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息
我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解
关于这三个核心注解的相关说明具体如下(第三个:
@SpringBootConfiguration注解:
@SpringBootConfiguration:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类
查看@SpringBootConfiguration注解源码,核心代码具体如下:
package org. springframework. boot ;
import java. lang. annotation. Documented ;
import java. lang. annotation. ElementType ;
import java. lang. annotation. Retention ;
import java. lang. annotation. RetentionPolicy ;
import java. lang. annotation. Target ;
import org. springframework. context. annotation. Configuration ;
import org. springframework. core. annotation. AliasFor ;
import org. springframework. stereotype. Indexed ;
@Target ( {
ElementType . TYPE } )
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor (
annotation = Configuration . class
)
boolean proxyBeanMethods ( ) default true ;
}
从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration
该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描,其中xml可以引入xml和扫描,配置类也可以引入配置类和扫描,而扫描(如:ComponentScan)也是可以扫描配置类的,当然,最终是谁是最开始的扫描,大概率是spring boot底层中进行处理的
由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类
只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已,这样会使得该类可以被获取调用(底层获取执行),从而执行main方法
虽然我们也可以再次进行获取,但也要注意,同一个类里面,多个不同的变量(通常考虑同类型),基本是能注入同一个对象的
@EnableAutoConfiguration注解:
@EnableAutoConfiguration:开启自动配置功能,以前由我们需要配置的东西,现在由SpringBoot帮我们自动配置
这个注解就是Springboot能实现自动配置的关键
同样,查看该注解内部查看源码信息,核心代码具体如下 :
package org. springframework. boot. autoconfigure ;
import java. lang. annotation. Documented ;
import java. lang. annotation. ElementType ;
import java. lang. annotation. Inherited ;
import java. lang. annotation. Retention ;
import java. lang. annotation. RetentionPolicy ;
import java. lang. annotation. Target ;
import org. springframework. context. annotation