SpringBoot基础
SpringBoot
概述
SpringBoot简介
SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心投入到逻辑业务的代码编写中,从而大大提高开发效率,一定程度上缩短了项目周期。2014年4月SpringBoot 1.0.0发布,是Spring的顶级项目之一
Spring的缺点
配置繁琐
虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始Spring用XML配置,而且是很多XML配置,Spring2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。
所有这些配置都代表了开发时的损耗,因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间,和所有框架一样,Spring实用,但它要求的回报也不少
依赖繁琐
项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度
SpringBoot的功能
自动配置
SpringBoot的自动配置是一个运行时(应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个、不该用哪个。
起步依赖(依赖传递)
起步依赖本质上是一个Maven项目对象模型(POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。简单来说,起步依赖就是将具备各种功能的坐标打包到一起,并提供一些默认的功能
辅助功能
提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标、健康检测、外部配置等
SpringBoot并不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式
SpringBoot快速入门
Maven项目手动引入SpringBoot依赖方式
引入父坐标
1
2
3
4
5<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>引入web依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>引入插件
1
2
3
4
5<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.6.RELEASE</version>
</plugin>创建SpringBootApplication核心启动类
1
2
3
4
5
6
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}创建Controller进行测试
1
2
3
4
5
6
7
public class HelloController {
public String hello( String name) {
return "hello " + name;
}
}访问默认的8080端口测试
/hello返回的结果(不带name参数为hello tao,否则为hello name值)
补充:要返回JSP页面需要引入tomcat-embed-jasper依赖,并且此依赖不能与servlet-api依赖冲突
1 | <dependency> |
利用Spring Initialzr快速创建SpringBoot项目
SpringBoot起步依赖原理
在spring-boot-starter-parent中定义了各种技术的版本信息,组合了一套最优搭配的版本
在各种starter中,定义了该功能所需要的坐标合集,其中大部分版本信息来自于父工程
我们的工程继承于parent,引入starter后,通过依赖传递,就可以简单方便的获取需要的jar包,并且不会导致版本冲突等问题
SpringBoot配置文件
配置文件分类
SpringBoot是基于约定的,所以很多的配置都有默认值,如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(也可以是yaml后缀)进行配置
properties方式
1
server.port=8080
yml方式
1
2server:
port: 8080
总结:
- SpringBoot提供了2种配置文件的类型:properties和yml
- 默认的配置文件名称:application
- 在同一级目录下的优先级为:properties > yml > yaml
- 自定义配置文件名:
- 后缀类型也要是properties或yml
- 启动参数
--spring.config.name=自定义配置文件名(参数的自定义配置文件名不需要加类型后缀)
注意:IDEA对.yaml的配置文件没有语法提示的解决办法:
- 在IDEA中设置模块的Facet:
- 如果配置文件名称不是application可能导致添加配置时点不了ok按钮,需要设置上图
spring.config.name
yaml
YAML全程是YAML Ain’t Markup Language。YAML是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同编程语言程序导入。比如C/C++,Ruby,Python,Java,Perl,C#,PHP等。YML文件是以数据为核心的,比传统的xml方式更加简洁。扩展名可以为
.yml或者.yaml
properties方式:
1
2server.port=8080
server.address=127.0.0.1xml方式:
1
2
3
4<server>
<port>8080</port>
<address>127.0.0.1</address>
</server>yml方式:
1
2
3server:
port: 8080
address: 127.0.0.1
YAML基本语法:
- 大小写敏感
- 数据值前面必须有空格
- 使用缩进表示层级关系
- 缩进时不允许使用Tab键,只允许使用空格(各个系统Tab对应的空格数目可能不同,导致层次混乱)
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- #表示注释,从这个字符一直到行尾都会被解析器忽略
YAML数据格式
对象(map):键值对集合
1
2
3
4
5
6person:
name: zhangsan
# 行内写法
person: {name: zhangsan}
map: {key1: value1,key2: value2}数组:一组按次序排序的值
1
2
3
4
5address:
- beijing
- shanghai
# 行内写法
address: [beijing,shanghai]对象集合:利用
List<User> users接收1
2
3
4
5
6# 两个user对象
users:
- name: 张三
age: 20
- name: 李四
age: 22纯量:单个的、不可再分的值
1
2msg1: 'hello \ n world' # 单引号或者不加任何引号忽略转义字符
msg2: "hello \n world" # 双引号识别转义字符
YAML参数引用
${配置项名}
读取配置文件内容
@Value
直接取出配置文件中某个完整字段的值赋值给成员属性,此类必须为Spring组件
1 |
|
冷知识:
@Value("#{beanName}"):可用于注入一个bean,类似于@Resource, 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法。当然还有可以表示常量@Value(${配置文件的属性}):获取配置文件中的属性值它俩可以结合使用:比如:
@Value("#{'${spring.hhh}'.split(',')}")是一个结合使用的案例,可以把配置文件的属性键spring.hhh对应的值解析为List注入1
2spring:
hh: 1,2,3,a1
2
private List list;@value(${配置文件的属性:默认值}):用于给此配置属性设定默认值
@ConfigurationProperties
| 功能 | @ConfigurationProperties | @Value |
|---|---|---|
| 松散绑定 | 支持 | 仅有限支持驼峰和横杠转换 |
| SpEL | 不支持 | 支持 |
| 元数据支持(提示) | 支持 | 不支持 |
| JSR 303数据校验 | 支持 | 不支持 |
| 复杂类型封装 | 支持 | 不支持 |
作用:读取配置文件的配置项绑定到此配置类的成员属性上。
说明:此注解需要配置一个
prefix“前缀”参数,例如:@ConfigurationProperties(prefix = "person"),表示读取此前缀下的内容到此注解修饰的类中。如果不配置前缀,会导致读取到第一个符合此类中成员属性名的字段时就赋值。注册此配置类为Bean:
- 通过给配置类打上@Component把配置类注册为Spring的Bean
通过在@SpringBootApplication注解所在的启动类上使用**@EnableConfigurationProperties**(参数为配置类的class对象)把配置类注册为Spring的Bean。
获取配置类的Bean(注入):
属性注入:通过@Autowired注入配置类的Bean到成员属性中:
构造器注入:把配置类作为参数赋值给成员属性
Setter注入:利用Setter给成员属性赋值:
以上这三种方式都可以把配置类注册到Spring容器中,并且通过构造方法注入参数时可以省略@Autowired,但进行属性注入时不能注入到
final属性中,所以不推荐使用字段(属性)注入
搭配@Bean使用:
@ConfigurationProperties注解也可以用在@Bean修饰的public方法上,给此方法返回值所产生的Bean的成员属性赋值,并且可以省略赋值语句完成自动赋值,只需无脑return new 需要的Bean()就行了
这种方式在读取配置文件给第三方依赖的Bean赋值时特别有用,例如配置不含起步依赖的druid连接池时利用此方法产生DruidDataSource的Bean并且赋值一些从配置文件中读取到的配置给此Bean
注意:
- 需要提供Setter方法,也可以利用lombok的@Data注解
- 也可以通过@ConstructorBinding进行构造器绑定,此时需要全参构造,并且产生Bean不能通过常规的@Bean或@Component或@Import,而需要@EnableConfigurationProperties(参数为配置类的class对象)注解来注入配置类的Bean或利用@ConfigurationPropertiesScan配置属性扫描
1 |
|
可以读取以下yml文件的内容:
1 | person: |
建议配置注解处理器以提供配合@ConfigurationProperties注解编写配置文件的语法提示:
松散绑定:
对于类中的成员属性名,例如:firstName
配置文件中可选的松散绑定名称:firstName、first-name、first_name、FIRST_NAME
但松散匹配注解@ConfigurationProperties和@Value的参数不能出现大写子母(实际配置项可以松散绑定)
引入注解处理器的依赖:
1
2
3
4
5<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>ctrl+F9重新构建项目,使注解处理器生效即可在编写配置文件时触发@ConfigurationProperties语法提示,此操作会编译产生一个
META-INF/spring-configuration-metadata.json文件,语法提示依赖此文件
数据校验需要配合JSR 303依赖,然后给类打上@Validated注解,再配合具体注解进行校验。内部类需要@Valid
Environment
注入Environment对象
1
2
private Environment environment;使用此对象的
getProperty(String s)方法获取s对应的值1
2
3
4environment.getProperty("name"));
environment.getProperty("person2.age"));
environment.getProperty("address[0]"));
environment.getProperty("msg1"));
profile
我们在开发SpringBoot应用时,通常同一套程序会被安装到不同的环境,例如:开发、测试、生产等。其中数据库地址、服务器端口等配置都不同,如果每次打包时都修改配置会非常麻烦。profile就是来进行动态配置切换的。
profile配置方式
多profile文件方式(properties或yml都可以)
- 创建
application-环境名1.properties - 创建
application-环境名2.properties - 创建
application-环境名3.properties - 在application.properties中指定
spring.profiles.active=环境名即可使用上面创建的多个文件的某一个
- 创建
yml多文档方式
在一个yml文件中利用
---分割多个配置文档,并利用spring.config.activate.on-profile指定环境名1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8191
spring:
config:
activate:
on-profile: dev
server:
port: 8192
spring:
config:
activate:
on-profile: pro
server:
port: 8193
spring:
config:
activate:
on-profile: test继续在此yml文件中指定使用哪个环境:
1
2
3
4
spring:
profiles:
active: test #启用上面的test环境
值得注意的是:yml配置的优先级低于properties,即使在yml文档中指定环境名也会先找此环境名的properties文件
profile激活方式
配置文件
- properties文件激活profile:
spring.profiles.active=环境名 - yml文档激活profile:
spring.profiles.active: 环境名
- properties文件激活profile:
虚拟机参数
- 在IDEA中配置VM参数:
-
- 虚拟机选项配置为:
-Dspring.profiles.active=环境名(优先级高于配置文件的方式)
- 在IDEA中配置VM参数:
命令行参数
- 在IDEA中配置程序参数:
-
- 程序参数配置为:
--spring.profiles.active=环境名(优先级最高)
在控制台利用命令行启动:
java -jar ./springboot项目jar包 --spring.profiles.active=环境名- 在IDEA中配置程序参数:
内部配置加载顺序
file:./config/:当前项目的/config目录下file:./:当前项目的根目录classpath:/config/:classpath的/config目录(默认classpath为resources目录)classpath:/:classpath的根目录(默认classpath为resources目录)
优先级从高到低,所有配置文件都会被读取,但会以高优先级的文件属性为准
外部配置加载顺序
由于maven的package命令不会打包当前项目的pom.xml所在层级的文件,所以要手动指定外部配置
- jar包所在目录下的config目录下的application.properties会被自动读取,优先级最高
- jar包所在目录下的application.properties会被自动读取,优先级次之
也可以利用java -jar ./项目jar包 --配置项名=配置值在执行时指定配置
还可以利用java -jar ./项目jar包 --spring.config.location=配置文件的绝对地址 引用磁盘上的配置
SpringBoot整合其他框架
SpringBoot整合Junit
引入test的起步依赖和junit坐标
1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>编写测试类,打上@RunWith(SpringRunner.class)注解和@SpringBootTest注解
1
2为测试方法打上@Test注解
注意:如果需要注入java目录下的类,需要保证test目录下的目录层次与引导类的包结构层次一致,否则需要给@SpringBootTest注解指定classes属性为引导类的class
1 |
如果目录层次一致可以省略classes属性,但要保证类位于SpringBootApplication注解类所在的包结构层次及子包层次,因为默认扫描
SpringBoot整合Redis
引入Redis起步依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>注入使用RedisTemplate模板,调用相应的方法
1
2
private RedisTemplate redisTemplate;
SpringBoot整合MyBatis
引入mybatis起步依赖,添加mysql驱动
1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>编写DataSource和MyBatis相关配置
1
2
3
4
5
6
7
8
9
10
11
12# 配置datasource
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///db3
username: root
password: 199988
# 配置MyBatis的XML
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml #Mapper映射文件的位置
#别名扫描的包,例如User类会被扫描为user或User的别名
type-aliases-package: com.taoyyz.springbootmybatis.domain定义表和实体类
编写dao/mapper文件或注解开发
注解开发:此时的UserMapper是一个Spring容器中的Bean,可以直接调用方法
1
2
3
4
5
public interface UserMapper {
List<User> findAll();
}XML开发:需要在XML中编写SQL,UserXmlMapper也是一个Spring Bean
编写Mapper接口
1
2
3
4
public interface UserXmlMapper {
List<User> findAll();
}配置Mapper.xml
1
2
3
4
5
6
7
8
9
<mapper namespace="com.taoyyz.springbootmybatis.mapper.UserXmlMapper">
<select id="findAll" resultType="user">
select * from user
</select>
</mapper>
SpringBoot整合Druid
引入Druid起步依赖
1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>配置SpringBoot核心配置文件
方式1:
1
2
3
4
5
6
7spring:
datasource:
url: jdbc:mysql://120.79.141.53:3307/mimile?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource方式2:
1
2
3
4
5
6
7spring:
datasource:
druid:
url: jdbc:mysql://120.79.141.53:3307/mimile?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
SpringBoot原理分析
SpringBoot自动配置
Conditional条件判断
Conditional是Spring 4.0增加的条件判断功能,通过这个功能可以实现选择性的创建Bean操作
用法:
@Conditional注解打在产生Bean的方法上,常配合@Bean注解实现条件判断创建Bean
@Conditional注解的参数属性需要一个实现了
Condition函数式接口的条件类的Class字节码对象在这个Condition函数式接口实现类中重写
boolean matches(条件context参数,元注解metadata参数)方法根据此
matches()方法返回的结果进行条件判断,为true就加载Bean,否则false不加载SpringBoot提供了一些@ConditionalOnXxx的注解用于条件判断
例如:
@ConditionalOnProperty(name = "tjj", havingValue = "22")可以判断配置文件是否有tjj=22的属性
| @Conditional扩展注解 | 作用(条件判断) |
|---|---|
| @ConditionalOnJava | 系统的Java版本是否符合要求 |
| @ConditionalOnBean | 容器中存在指定的Bean |
| @ConditionalOnMissingBean | 容器中不存在指定的Bean |
| @ConditionalOnExpression | 满足SpEL表达式 |
| @ConditionalOnClass | 系统中有指定的类 |
| @ConditionalOnMissingClass | 系统中没有指定的类 |
| @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或这个Bean为首选Bean |
| @ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
| @ConditionalOnResource | 类路径下是否存在指定资源文件 |
| @ConditionalOnWebApplication | 当前是web环境 |
| @ConditionalOnNotWebApplication | 当前不是web环境 |
| @ConditionalOnJndi | JNDI存在指定项 |
切换内置Web服务器
SpringBoot的web环境默认使用tomcat作为内置服务器,提供了4种内置服务器供我们选择
切换到
Jetty在pom.xml排除
spring-boot-starter-web起步依赖中的spring-boot-starter-tomcat依赖引入jetty的依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--排除掉tomcat的依赖-->
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入jetty的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
@Enable*注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而底层原理是使用@Import导入一些配置类实现Bean的动态加载
默认情况下@SpringBootApplication只会扫描当前包及其子包的Bean,所以其他模块的Bean获取不到
获取其他模块的Bean的几种方式:
使用@ComponentScan指定扫描的包
1
使用@Import注解加载类,被@Import导入的类都会被Spring容器创建和管理
1
对@Import注解进行封装
在其他模块中创建一个EnableXxx的注解,在此注解中利用@Import加载类,只需要在使用时@EnableXxx即可
1
2
3
4
5
6
public EnableUser {
}在@SpringBootApplication配置类上调用@EnableUser注解即可
@Import注解
@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到Ioc容器中,而@Import提供4种用法
导入Bean
1 | //通过字节码Import的Bean在容器中的名称为全限定名com.taoyyz.domain.User |
导入配置类
创建配置类
1
2
3
4
5
6public class UserConfiguration {
public User getUser() {
return new User();
}
}导入配置类
1
//导入配置类的字节码,从字节码中获得Bean,Bean的名称为@Bean规定的
导入ImportSelector实现类。一般用于加载配置文件中的类
创建ImportSelector实现类
1
2
3
4
5
6public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.taoyyz.domain.User", "com.taoyyz.domain.Role"};
}
}导入此实现类
1
导入ImportBeanDefinitionRegistrar实现类
创建ImportBeanDefinitionRegistrar实现类
1
2
3
4
5
6
7public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user",beanDefinition);
}
}导入此实现类
1
@EnableAutoConfiguration注解
由于@SpringBootApplication注解被@EnableAutoConfiguration注解修饰,而@EnableAutoConfiguration注解又导入了AutoConfigurationImportSelector的字节码文件
- AutoConfigurationImportSelector类的
getCandidateConfigurations()方法会读取spring.factories配置文件,根据@Conditional条件加载配置类初始化Bean - 只有满足了@Conditional条件的Bean才会被初始化
自定义starter
要求:自定义redis-starter,当导入redis坐标时,SpringBoot自动创建Jedis的Bean
实现步骤:
创建redis-spring-boot-autoconfigure模块,初始化Jedis的Bean,并定义META-INF/spring.factories文件
创建RedisAutoConfiguration配置类,用于通过配置信息产生Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RedisAutoConfiguration {
/**
* 提供Jedis的Bean
*/
//如果没有叫做jedis的bean才加载
public Jedis getJedis(RedisProperties props) {
System.out.println("config的bean");
return new Jedis(props.getHost(), props.getPort());
}
}创建RedisProperties配置读取类,读取application.properties或yml文件的属性赋值给配置成员属性
1
2
3
4
5
6//绑定以redis开头的properties配置,赋值给成员属性
//利用lombok产生Getter/Setter
public class RedisProperties {
private String host = "localhost";
private int port = 6379;
}在resources目录下创建META-INF目录,编辑
spring.factories文件1
2=\
com.taoyyz.config.RedisAutoConfiguration引入jedis依赖
1
2
3
4
5
6<!--引入jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
创建redis-spring-boot-starter模块,依赖redis-spring-boot-autoconfigure模块
1
2
3
4
5
6<!--引入autoconfigure的依赖-->
<dependency>
<groupId>com.taoyyz</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>在测试模块中引入自定义的redis-starter起步依赖,并测试是否能根据依赖引入自动配置获得Bean操作redis
1
2
3
4
5
6<!--引入自定义的redis起步依赖-->
<dependency>
<groupId>com.taoyyz</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
SpringBoot监听机制
Java监听机制
SpringBoot的监听机制是对Java提供的监听机制的封装
Java的事件监听机制定义了以下几个角色:
- 事件:Event,继承java.util.EventObject类的对象
- 事件源:Srouce,任意Object对象
- 监听器:Listener,实现了java.util.EventListener接口的对象
SpringBoot监听机制
SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作
ApplicationContextInitializer
位于
contextPrepared()上下文准备之前,打印SpringBoot的启动banner之后调用此initialize()方法需要手动配置META-INF下的
spring.factories1
2=\
com.taoyyz.springbootlistener.listener.MyApplicationContextInitializer
SpringApplicationRunListener
贯穿整个SpringBoot启动过程,需要一个带参构造方法
1
2
3
4
5
6public class MySpringApplicationRunListener implements SpringApplicationRunListener {
public MySpringApplicationRunListener(SpringApplication application, String[] args) {
System.out.println("application = " + application);
System.out.println(Arrays.toString(args));
}
}需要手动配置META-INF下的
spring.factories1
2=\
com.taoyyz.springbootlistener.listener.MySpringApplicationRunListener
CommandLineRunner
- 项目启动结束后执行
run()方法
ApplicationRunner
- 项目启动结束后执行
run()方法
SpringBoot启动流程分析
@EnableAutoConfiguration利用了@Import(AutoConfigurationImportSelector.class)AutoConfigurationImportSelector类实现了ImportSelector接口的selectImports(AnnotationMetadata annotationMetadata)方法,此方法中调用getAutoConfigurationEntry(annotationMetadata)返回一个AutoConfigurationEntry对象(此对象含有一个List<String>类型的configurations和一个Set<String>类型的exclusions)在上述的
getAutoConfigurationEntry方法中调用了getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)方法,来获取候选配置,此方法内部利用SpringFactoriesLoader的loadFactoryNames(Class<?> factoryType, ClassLoader classLoader)方法中调用loadSpringFactories(ClassLoader classLoader)方法去读取SpringFactoriesLoader中一个静态常量:public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"在
loadSpringFactories(ClassLoader classLoader)方法中加载所有spring.factories文件
然后
loadFactoryNames(Class<?> factoryType, ClassLoader classLoader)方法会把上一步方法的返回值(一个多值Map<String, List<String>>)调用getOrDefault(factoryTypeName, Collections.emptyList()),这里传入的factoryTypeName其实就是EnableAutoConfiguration的全类名,也就是筛选出EnableAutoConfiguration注解所自动配置的key对应的value(这里value为List<String>),此时getCandidateConfigurations(annotationMetadata, attributes)方法结束。然后
getAutoConfigurationEntry方法的下一步调用removeDuplicates(configurations)移除重复项目,这里利用了HashSet去重,根据HashSet内容构造一个ArrayList并返回下一步调用
getExclusions(annotationMetadata, attributes)返回一个排除的Set<String>下一步调用
checkExcludedClasses(configurations, exclusions)检查需要排除的Class是否为无效Class,检查无效首先在ClassUtils.isPresent()方法中利用forName()尝试加载这个exclusions的className,如果发生异常立即报错并返回false,其次即使forName()可以成功加载,还需要满足!configurations.contains(exclusion),也就是自动配置列表不包含exclusion这个需要排除的项时取反为true就会把此exclusion加入到invalidExcludes这个List<String>中去统一报错处理如果没有报错,说明上一步成功排除,下一步就调用
configurations.removeAll(exclusions)移除正确的排除项下一步让所有配置在
META-INF/spring.factories下的AutoConfigurationImportListener执行AutoConfigurationImportEvent事件最终
getAutoConfigurationEntry(annotationMetadata)执行完毕,返回AutoConfigurationEntry中的configurations的String[]形式
SpringBoot监控
SpringBoot自带监控功能Actuator,可以实现对程序内部运行情况监控,例如监控状况、Bean加载情况、配置属性、日志信息等
使用步骤:
导入依赖坐标
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置监控:
1
2
3
4
5
6# 开启info监控
=true
# 暴露所有端口
=*
# 开启健康监控完整信息
=alwaysweb图形化监控:admin依赖
创建server端项目,引入server端依赖:(可能需要依赖管理中指定版本)
1
2
3
4<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>在SpringBoot配置文件中配置server端访问端口:
1
=9000
开启server端注解,此时可以启用server端项目了
1
2
3
4
5
6
7
public class SpringBootAdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAdminServerApplication.class, args);
}
}在client端中引入client端依赖:
1
2
3
4<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>在client端的SpringBoot配置文件中配置client端的url(可根据需要暴露监控端口等)
1
2# 开启admin.server图形化监控
=http://localhost:9000访问
http://localhost:9000即可进入图形化监控
SpringBoot异常处理
使用全局异常处理器
1
2
3
4
5
6
7
8
9
10
11//@RestControllerAdvice
public class MyExceptionHandler {
//拦截异常
public JsonResponse exceptionHandler(Exception e){
//处理异常逻辑,可以是记录日志等操作
e.printStackTrace();
return JsonResponse.failure("服务器异常,原因是" + e.getMessage());
}
}
SpringBoot项目部署
启动SpringBoot项目
通过jar包的内置tomcat启动
- SpringBoot的Maven项目默认打jar包,其中内置了tomcat服务器可以直接运行
- 利用
mvn package生成jar包 - 利用
java -jar ./项目jar包即可利用SpringBoot内置tomcat容器运行
- 利用
通过war包在外置tomcat启动
修改Maven的pom.xml,设置
<packaging>war</packaging>打包类型为war修改
@SpringBootApplication注解修饰的启动类,使其继承于SpringBootServletInitializer类重写启动类中的
configure()方法,传入启动类的class字节码对象1
2
3
4
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringBootDeployApplication.class);
}利用
mvn package生成war包复制war包到tomcat安装目录的webapps目录下,然后启动外置tomcat服务器
访问路径含有上下文路径,也就是此项目在webapps目录下的文件夹名称
热部署SpringBoot项目
方式1:重新编译rebuild项目:ctrl+F9
方式2:通过插件自动构建部署
引入devtools依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>开启IDEA自动构建
允许在运行时自动构建
- 按
ctrl+shift+alt+/在维护窗口选择注册表选项:
- 勾选
在运行期编译:
- 按
刷新页面访问会rebuild项目并热部署