01-SpringBoot快速入门
1. SpringBoot介绍
1.1 简介
在您第1次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?
在你第n次使用Spring框架的时候,是否觉得一堆反复黏贴的配置有一些厌烦?
那么您就不妨来试试使用SpringBoot来让你更易上手,更简单快捷地构建Spring应用!
SpringBoot让我们的Spring应用变的更轻量化。
我们不必像以前那样繁琐的构建项目、打包应用、部署到Tomcat等应用服务器中来运行我们的业务服务。
通过SpringBoot实现的服务,只需要依靠一个Java类,把它打包成jar,并通过java -jar 命令就可以运行起来。
这一切相较于传统Spring应用来说,已经变得非常的轻便、简单。
总结一下SpringBoot的主要优点:
- 为所有Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
- 统一的依赖管理
- 自动装配,更易使用,更易扩展
1.2 使用版本说明
Springboot版本:使用最新的2.5.0版本
教程参考了官方文档进行制作,权威。
其他依赖版本:
1. Maven 需求:3.5+
2. JDK 需求 8+
3. Spring Framework 5.3.7以上版本
4. Tomcat 9.0
5. Servlet版本 4.0 但是可以部署到Servlet到3.1+的容器中
2. 快速入门
快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。
教程使用的Idea版本:2019.3
2.1 创建基础项目
**第一步:**创建maven项目
pom.xml :
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xiaopizhu</groupId>
<artifactId>helloSpringBoot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
</project>
注意上方的parent必须加,其中定义了springboot官方支持的n多依赖,基本上常用的已经有了,所以接下来导入依赖的时候,绝大部分都可以不加版本号。
此时的工程结构为: **第二步:**添加web依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
添加上方的web依赖,其中间接依赖了spring-web,spring-webmvc,spring-core等spring和springmvc的包,并且集成了tomcat。
**第三步:**编写启动类
package com.xiaopizhu.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloApp {
public static void main(String[] args) {
SpringApplication.run(HelloApp.class,args);
}
}
@SpringBootApplication注解标识了HelloApp为启动类,也是Spring Boot的核心。
**第四步:**运行启动类的main方法
看到如上配置,证明启动成功,tomcat端口号默认为8080。
**第五步:**如果想要修改端口号,可以在resources目录下新建application.properties
server.port=8082
**第六步:**重新运行
此时的项目结构为:
src/main/java : 编写java代码,注意启动类需要放在项目的根包下。
src/main/resources: 放置资源的目录,比如springboot的配置文件,静态文件,mybatis配置,日志配置等。
src/test/java: 测试代码
2.2 编写一个Http接口
**第一步:**创建HelloController 类,内容如下:
package com.xiaopizhu.springboot.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("hello")
public class HelloController {
@GetMapping("boot")
public String hello(){
return "hello spring boot";
}
}
注意包名,必须在启动类所在的包名下。
**第二步:**重启程序,使用postman或者直接在浏览器输入http://localhost:8082/hello/boot
得到结果:hello spring boot
2.3 编写单元测试用例
**第一步:**添加spring boot测试依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
**第二步:**在src/test 下,编写测试用例
package com.xiaopizhu.springboot.controller;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
public class TestHelloController {
private MockMvc mockMvc;
@BeforeEach
public void beforeEach(){
mockMvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
}
@Test
public void testHello() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/hello/boot")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("hello spring boot")));
}
}
上面的测试用例,是构建一个空的WebApplicationContext ,并且在before中加载了HelloController,得以在测试用例中mock调用,模拟请求。
2.4 打包为jar运行
**第一步:**添加打包(maven构建springboot)插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
在idea的右侧 maven中,使用package来打包程序,打包完成后,在target目录下生成helloSpringBoot-1.0-SNAPSHOT.jar
**第二步:**打开cmd:找到jar对应的目录
输入命令
java -jar helloSpringBoot-1.0-SNAPSHOT.jar
**第三步:**测试,使用postman或者直接在浏览器输入http://localhost:8082/hello/boot
得到结果:hello spring boot
2.5 查看jar包内容
jar tvf helloSpringBoot-1.0-SNAPSHOT.jar
3. 小结
- 通过Maven构建了一个空白Spring Boot项目,再通过引入web模块实现了一个简单的请求处理。
- 通过修改配置文件,更改端口号
- 编写了测试用例
- 打包jar包运行
02-SpringBoot工程结构
1. 前言
Spring Boot框架本身并没有对工程结构有特别的要求,但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑。
尤其是Spring包扫描机制的存在,如果您使用最佳实践的工程结构,可以免去不少特殊的配置工作。
2. 典型示例
以下结构是比较推荐的package组织方式:
com
+- example
+- myproject
+- Application.java
|
+- dao
| +- Customer.java
| +- CustomerDao.java
|
+- service
| +- CustomerService.java
|
+- controller
| +- CustomerController.java
|
root package:com.example.myproject ,所有的类和其他package都在root package之下。
**Application.java : **应用主类,该类直接位于root package 下。通常我们会在应用主类中做一些框架配置扫描等配置,我们放在root package下可以帮助程序减少手工配置来加载到我们希望被Spring加载的内容。
**com.example.myproject.dao包:**用于定义实体映射关系与数据访问相关的接口和实现。
**com.example.myproject.service包:**用于编写业务逻辑相关的接口与实现。
**com.example.myproject.controller:**用于编写Web层相关的实现,比如:Spring MVC的Controller等
默认情况下,Spring Boot的应用主类会自动扫描root package 以及所有子包下的所有类来进行初始化。
包的层级目录一定要搞对,如果把CustomerController放入到com.example 下,则spring boot就无法扫描到CustomerController,并进行初始化了。
3. 非典型结构下的初始化
那么如果,我们一定要加载非root package 下的内容怎么办呢?
比如有一些通用的工具类,比如第三方的包等等。
方法一:使用@ComponentScan 注解指定具体的加载包,比如:
@SpringBootApplication
@ComponentScan(basePackages="com.example")
public class HelloApp {
public static void main(String[] args) {
SpringApplication.run(HelloApp.class, args);
}
}
这种方法通过注解直接指定要扫描的包,比较直观。
方法二:使用@Bean 注解来初始化,比如:
@SpringBootApplication
public class HelloApp {
public static void main(String[] args) {
SpringApplication.run(HelloApp.class, args);
}
@Bean
public CustomerController customerController() {
return new CustomerController();
}
}
@Bean这种一般用于加载第三方的jar包,比如一些框架封装场景,比如Mybaits-plus等。
4. 工程结构之其他
这里讨论一下,除了controller,service,dao这种典型的package组织方式之外,还有没有别的?
com
+- example
+- myproject
+- Application.java
|
+- dao
+- data
| +- Customer.java
+- mapper
| +- CustomerMapper.java
|
+- domain
+- repository
| +- CustomerRepository.java
+- CustomerDomain.java
|
+- service
| +- CustomerService.java
|
+- controller
| +- CustomerController.java
|
上面的结构中,多出来一层domain层,将业务逻辑操作放入到domain中进行执行,Repository和数据层打交道,service用来管理业务流程。
03-SpringBoot配置文件详解
1. 前言
在前面的入门案例中,我们轻松的实现了一个简单的RESTful API应用,体验了一下Spring Boot给我们带来的诸多优点,我们用非常少的代码量成功的实现了一个Web应用,这是传统的Spring应用无法办到的。
配置方面,使用了application.properties 这个Spring Boot可以识别的配置文件,做了tomcat监听端口的修改,其他配置暂时没有用到。
在实际的工作中,开发一个Spring Boot程序需要修改配置文件的内容来达到不同的需求,到了后期如果学习到了Spring Cloud,在Spring Cloud中,其实有大量的工作都会是针对配置文件的。
所以我们有必要深入的了解一些关于Spring Boot中的配置文件的知识,比如:它的配置方式、如何实现多环境配置,配置信息的加载顺序等。
2. 配置基础
Spring Boot的默认配置文件位置为: src/main/resources/application.properties 。
关于Spring Boot应用的配置内容都可以集中在该文件中了,根据我们引入的不同Starter模块,可以在这里定义诸如:容器端口名、数据库链接信息、日志级别等各种配置信息。
server.port=8888 #自定义web模块的服务端口号,指定服务端口为8888
spring.application.name=hello #指定应用名(该名字在Spring Cloud应用中会被注册为服务名)
Spring Boot的配置文件除了可以使用传统的properties文件之外,还支持现在被广泛推荐使用的YAML文件。
src/main/resources/application.yml 也可以被Spring Boot程序识别。
YAML全称是 YAML Ain’t Markup Language 。YAML是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互,可以被支持YAML库的不同的编程语言程序导入,比如: C/C++, Ruby, Python, Java, Perl, C#, PHP 等。YML文件是以数据为核心的,比传统的xml方式更加简洁。 YAML文件的扩展名可以使用.yml或者.yaml。
YAML采用的配置格式不像properties的配置那样以单纯的键值对形式来表示,而是以类似大纲的缩进形式来表示。比如:下面的一段YAML配置信息
environments:
dev:
url: http://dev.xiaopizhu.com
name: Developer Setup
prod:
url: http://prod.xiaopizhu.com
name: My App
与其等价的properties配置如下。
environments.dev.url=http://dev.xiaopizhu.com
environments.dev.name=Developer Setup
environments.prod.url=http://prod.xiaopizhu.com
environments.prod.name=My App
通过YAML的配置方式,我们可以看到配置信息利用阶梯化缩进的方式,其结构显得更为清晰易读,同时配置内容的字符量也得到显著的减少。
注意:yml文件冒号后面有一个空格
除此之外,YAML还可以在单个文件中通过使用spring.config.activate.on-profile 属性来定义多个不同的环境配置。
例如下面的内容:
在指定为test环境时,server.port 将使用8882端口;
而在prod环境,server.port 将使用8883端口;
如果没有指定环境,server.port 将使用8881端口。
server:
port: 8881
---
spring:
config:
activate:
on-profile: test
server:
port: 8882
---
spring:
config:
activate:
on-profile: prod
server:
port: 8883
注意:YAML目前还有一些不足,它无法通过@PropertySource 注解来加载配置。但是,YAML加载属性到内存中保存的时候是有序的,所以当配置文件中的信息需要具备顺序含义时,YAML的配置方式比起properties配置文件更有优势。
如果properties配置文件和yml配置文件同时存在,优先加载properties文件,在加载yml文件,有同名的属性以properties为主。
2.1 自定义参数
我们除了可以在Spring Boot的配置文件中设置各个Starter模块中预定义的配置属性,也可以在配置文件中定义一些我们需要的自定义属性。比如在application.properties 中添加:
book.name=SpringBoot
book.author=god_coder
然后,在应用中我们可以通过@Value 注解来加载这些自定义的参数,比如:
@Component
public class Book {
@Value("${book.name}")
private String name;
@Value("${book.author}")
private String author;
}
@Value 注解加载属性值的时候可以支持表达式来进行配置:
- PlaceHolder方式:格式为
${...} ,大括号内为PlaceHolder,上面示例中使用的方式
yml格式:
book:
name: SpringBoot
author: god_coder
2.2 参数引用
在application.properties 中的各个参数之间,我们也可以直接通过使用PlaceHolder的方式来进行引用,就像下面的设置:
book.name=SpringBoot
book.author=god_coder
book.desc=${book.author} is writing《${book.name}》
book.desc 参数引用了上文中定义的book.name 和book.author 属性,最后该属性的值就是god_coder is writing《SpringBoot》 。
book:
name: SpringBoot
author: god_coder
desc: ${book.author} is writing《${book.name}》
2.3 使用随机数
在一些特殊情况下,有些参数我们希望它每次加载的时候不是一个固定的值,比如:密钥、服务端口等。在Spring Boot的属性配置文件中,我们可以通过使用${random} 配置来产生随机的int值、long值或者string字符串,这样我们就可以容易的通过配置随机生成属性值,而不是在程序中通过编码来实现这些逻辑。
${random} 的配置方式主要有一下几种,读者可作为参考使用。
# 随机字符串
com.xiaopizhu.blog.value=${random.value}
# 随机int
com.xiaopizhu.blog.number=${random.int}
# 随机long
com.xiaopizhu.blog.bignumber=${random.long}
# 10以内的随机数
com.xiaopizhu.blog.test1=${random.int(10)}
# 10-20的随机数
com.xiaopizhu.blog.test2=${random.int[10,20]}
该配置方式可以用于设置应用端口等场景,避免在本地调试时出现端口冲突的麻烦,比如在测试用例随机生成springboot应用启动的端口号
@Value("${com.xiaopizhu.blog.value}")
private String value;
@Value("${com.xiaopizhu.blog.number}")
private Integer number;
@Value("${com.xiaopizhu.blog.bignumber}")
private Long bignumber;
@Value("${com.xiaopizhu.blog.test1}")
private Integer test1;
@Value("${com.xiaopizhu.blog.test2}")
private Integer test2;
2.4 命令行参数
在之前的快速入门案例中,我们介绍了使用命令java -jar 命令来启动的方式。
该命令除了启动应用之外,还可以在命令行中来指定应用的参数,比如:
java -jar xxx.jar --server.port=8888
直接以命令行的方式,来设置server.port属性,令启动应用的端口设为8888。
在命令行方式启动Spring Boot应用时,连续的两个减号-- 就是对application.properties 中的属性值进行赋值的标识。
所以,java -jar xxx.jar --server.port=8888 命令,等价于我们在application.properties 中添加属性server.port=8888 。
通过命令行来修改属性值是Spring Boot非常重要的一个特性,通过此特性,理论上已经使得我们应用的属性在启动前是可变的,所以其中端口号也好、数据库连接也好,都是可以在应用启动时发生改变,而不同于以往的Spring应用通过Maven的Profile在编译器进行不同环境的构建。
Spring Boot的这种方式,可以让应用程序的打包内容,贯穿开发、测试以及线上部署。
但是,如果每个参数都需要通过命令行来指定,这显然也不是一个好的方案,所以下面我们看看如果在Spring Boot中实现多环境的配置。
注意:命令行参数在需要临时改变一些参数的时候非常适用
2.5 多环境配置
我们在开发任何应用的时候,一般都会有多套环境,比如:开发、测试、生产等。
其中每个环境的数据库地址、服务器端口等等配置都会不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常繁琐且容易发生错误的事。
对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,Spring Boot也不例外,或者说更加简单。
在Spring Boot中多环境配置文件名需要满足application-{profile}.properties 的格式,其中{profile} 对应你的环境标识,比如:
application-dev.properties :开发环境application-test.properties :测试环境application-prod.properties :生产环境
至于哪个具体的配置文件会被加载,需要在application.properties 文件中通过spring.profiles.active 属性来设置,其值对应配置文件中的{profile} 值。如:spring.profiles.active=test 就会加载application-test.properties 配置文件内容。
下面,以不同环境配置不同的服务端口为例,进行样例实验。
- 针对各环境新建不同的配置文件
application-dev.properties 、application-test.properties 、application-prod.properties - 在这三个文件均都设置不同的
server.port 属性,如:dev环境设置为1111,test环境设置为2222,prod环境设置为3333 - application.properties中设置
spring.profiles.active=dev ,就是说默认以dev环境设置 - 测试不同配置的加载
- 执行
java -jar xxx.jar ,可以观察到服务端口被设置为1111 ,也就是默认的开发环境(dev) - 执行
java -jar xxx.jar --spring.profiles.active=test ,可以观察到服务端口被设置为2222 ,也就是测试环境的配置(test) - 执行
java -jar xxx.jar --spring.profiles.active=prod ,可以观察到服务端口被设置为3333 ,也就是生产环境的配置(prod)
按照上面的实验,可以如下总结多环境的配置思路:
application.properties 中配置通用内容,并设置spring.profiles.active=dev ,以开发环境为默认配置application-{profile}.properties 中配置各个环境不同的内容- 通过命令行方式去激活不同环境的配置
2.6 加载顺序
在上面的例子中,我们将Spring Boot应用需要的配置内容都放在了项目工程中,虽然我们已经能够通过spring.profiles.active 或是通过Maven来实现多环境的支持。
但是,当我们的团队逐渐壮大,分工越来越细致之后,往往我们不需要让开发人员知道测试或是生产环境的细节,而是希望由每个环境各自的负责人(QA或是运维)来集中维护这些信息。
那么如果还是以这样的方式存储配置内容,对于不同环境配置的修改就不得不去获取工程内容来修改这些配置内容,当应用非常多的时候就变得非常不方便。
同时,配置内容都对开发人员可见,本身这也是一种安全隐患。
对此,现在出现了很多将配置内容外部化的框架和工具,比如Spring Cloud Config,nacos等
springboot 官方文档链接:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config
Spring Boot为了能够更合理的重写各属性的值,使用了下面这种较为特别的属性加载顺序:
- 命令行中传入的参数。
SPRING_APPLICATION_JSON 中的属性。SPRING_APPLICATION_JSON 是以JSON格式配置在系统环境变量中的内容。- ServletConfig初始化参数 InitParameter
- ServletContext初始化参数 InitParameter
java:comp/env 中的JNDI 属性。- Java的系统属性,可以通过
System.getProperties() 获得的内容。 - 操作系统的环境变量
- 通过
random.* 配置的属性 RandomValuePropertySource - 位于当前应用jar包之外,针对不同
{profile} 环境的配置文件内容,例如:application-{profile}.properties 或是YAML 定义的配置文件 - 位于当前应用jar包之外的
application.properties 和YAML 配置内容 - 位于当前应用jar包之内,针对不同
{profile} 环境的配置文件内容,例如:application-{profile}.properties 或是YAML 定义的配置文件 - 位于当前应用jar包之内的
application.properties 和YAML 配置内容 - 在
@Configuration 注解修改的类中,通过@PropertySource 注解定义的属性 - 应用默认属性,使用
SpringApplication.setDefaultProperties 定义的内容
优先级按上面的顺序有高到低,数字越小优先级越高。
可以看到,其中第9项和第10项都是从应用jar包之外读取配置文件,所以,实现外部化配置的原理就是从此切入,为其指定外部配置文件的加载位置来取代jar包之内的配置内容。
通过这样的实现,我们的工程在配置中就变的非常干净,我们只需要在本地放置开发需要的配置即可,而其他环境的配置就可以不用关心,由其对应环境的负责人去维护即可。
3. 2.x 新特性
在Spring Boot 2.0中推出了Relaxed Binding 2.0,对原有的属性绑定功能做了非常多的改进以帮助我们更容易的在Spring应用中加载和读取配置信息。下面本文就来说说Spring Boot 2.0中对配置的改进。
3.1 配置文件绑定
3.1.1 简单类型
在Spring Boot 2.0中对配置属性加载的时候,除了像1.x版本时候那样移除特殊字符外,还会将配置均以全小写的方式进行匹配和加载。所以,下面的4种配置方式都是等价的:
spring.jpa.databaseplatform=mysql
spring.jpa.database-platform=mysql
spring.jpa.databasePlatform=mysql
spring.JPA.database_platform=mysql
spring:
jpa:
databaseplatform: mysql
database-platform: mysql
databasePlatform: mysql
database_platform: mysql
Tips:推荐使用全小写配合- 分隔符的方式来配置,比如:spring.jpa.database-platform=mysql
3.1.2 List类型
在properties文件中使用[] 来定位列表类型,比如:
spring.my-example.url[0]=http://example.com
spring.my-example.url[1]=http://spring.io
也支持使用逗号分割的配置方式,上面与下面的配置是等价的:
spring.my-example.url=http://example.com,http://spring.io
而在yaml文件中使用可以使用如下配置:
spring:
my-example:
url:
- http://example.com
- http://spring.io
也支持逗号分割的方式:
spring:
my-example:
url: http://example.com, http://spring.io
注意:在Spring Boot 2.0中对于List类型的配置必须是连续的,不然会抛出UnboundConfigurationPropertiesException 异常,所以如下配置是不允许的:
foo[0]=a
foo[2]=b
在Spring Boot 1.x中上述配置是可以的,foo[1] 由于没有配置,它的值会是null
3.1.3 Map类型
Map类型在properties和yaml中的标准配置方式如下:
spring.my-example.foo=bar
spring.my-example.hello=world
spring:
my-example:
foo: bar
hello: world
注意:如果Map类型的key包含非字母数字和- 的字符,需要用[] 括起来,比如:
spring:
my-example:
'[foo.baz]': bar
3.2 环境属性绑定
简单类型
在环境变量中通过小写转换与. 替换_ 来映射配置文件中的内容,比如:环境变量SPRING_JPA_DATABASEPLATFORM=mysql 的配置会产生与在配置文件中设置spring.jpa.databaseplatform=mysql 一样的效果。
List类型
由于环境变量中无法使用[ 和] 符号,所以使用_ 来替代。任何由下划线包围的数字都会被认为是[] 的数组形式。比如:
MY_FOO_1_ = my.foo[1]
MY_FOO_1_BAR = my.foo[1].bar
MY_FOO_1_2_ = my.foo[1][2]
另外,最后环境变量最后是以数字和下划线结尾的话,最后的下划线可以省略,比如上面例子中的第一条和第三条等价于下面的配置:
MY_FOO_1 = my.foo[1]
MY_FOO_1_2 = my.foo[1][2]
3.3 系统属性绑定
简单类型
系统属性与文件配置中的类似,都以移除特殊字符并转化小写后实现绑定,比如下面的命令行参数都会实现配置spring.jpa.databaseplatform=mysql 的效果:
-Dspring.jpa.database-platform=mysql
-Dspring.jpa.databasePlatform=mysql
-Dspring.JPA.database_platform=mysql
List类型
系统属性的绑定也与文件属性的绑定类似,通过[] 来标识,比如:
-Dspring.my-example.url[0]=http://example.com
-Dspring.my-example.url[1]=http://spring.io
同样的,他也支持逗号分割的方式,比如:
-Dspring.my-example.url=http://example.com,http://spring.io
3.4 属性的读取
上文介绍了Spring Boot 2.0中对属性绑定的内容,可以看到对于一个属性我们可以有多种不同的表达,但是如果我们要在Spring应用程序的environment中读取属性的时候,每个属性的唯一名称符合如下规则:
- 通过
. 分离各个元素 - 最后一个
. 将前缀与属性名称分开 - 必须是字母(a-z)和数字(0-9)
- 必须是小写字母
- 用连字符
- 来分隔单词 - 唯一允许的其他字符是
[ 和] ,用于List的索引 - 不能以数字开头
所以,如果我们要读取配置文件中spring.jpa.database-platform 的配置,可以这样写:
this.environment.containsProperty("spring.jpa.database-platform")
而下面的方式是无法获取到spring.jpa.database-platform 配置内容的:
this.environment.containsProperty("spring.jpa.databasePlatform")
注意:使用@Value 获取配置内容的时候也需要这样的特点
3.5 全新的绑定API
在Spring Boot 2.0中增加了新的绑定API来帮助我们更容易的获取配置信息。下面举个例子来帮助大家更容易的理解:
例子一:简单类型
假设在properties配置中有这样一个配置:com.mszlu.foo=bar
我们为它创建对应的配置类:
@Data
@ConfigurationProperties(prefix = "com.mszlu")
public class FooProperties {
private String foo;
}
接下来,通过最新的Binder 就可以这样来拿配置信息了:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
Binder binder = Binder.get(context.getEnvironment());
FooProperties foo = binder.bind("com.mszlu", Bindable.of(FooProperties.class)).get();
System.out.println(foo.getFoo());
}
}
例子二:List类型
如果配置内容是List类型呢?比如:
com.mszlu.post[0]=Why Spring Boot
com.mszlu.post[1]=Why Spring Cloud
com.mszlu.posts[0].title=Why Spring Boot
com.mszlu.posts[0].content=It is perfect!
com.mszlu.posts[1].title=Why Spring Cloud
com.mszlu.posts[1].content=It is perfect too!
要获取这些配置依然很简单,可以这样实现:
ApplicationContext context = SpringApplication.run(Application.class, args);
Binder binder = Binder.get(context.getEnvironment());
List<String> post = binder.bind("com.mszlu.post", Bindable.listOf(String.class)).get();
System.out.println(post);
List<PostInfo> posts = binder.bind("com.mszlu.posts", Bindable.listOf(PostInfo.class)).get();
System.out.println(posts);
4. 2.5.0新特性
- 支持java16
spring.datasource.* 已被 spring.sql.init.* 属性替代。- Flyway 和 Liquibase(数据库版本管理工具) 需要指定单独的 username / password,不再从 datasource 继承。
- 不再维护 spring data solr , 从此版本开始 已经开始从源码中移除。
- 断点 /info 不再通过 web 暴露,如果类中包含 spring security,需要安全验证。
- EL 语法实现由 tomcat-embed-el 替代为 jakrta-el。
- Error View 异常页面中不会包含 具体的错误信息,如果需要则可以通过
server.error.include-message 开启。 - 通过
logging.register-shutdown-hook 属性可以在 jvm 退出时释放日志资源。
04-简单整合
1. SpringBoot整合MybatisPlus
1.1 准备数据库脚本
CREATE TABLE If Not Exists user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
replace into user (id,name, age, email) VALUES
(1, 'Jone', 18, 'test1@mszlu.com'),
(2, 'Jack', 20, 'test2@mszlu.com'),
(3, 'Tom', 28, 'test3@mszlu.com'),
(4, 'Sandy', 21, 'test4@mszlu.com'),
(5, 'Billie', 24, 'test5@mszlu.com');
1.2 新建SpringBoot工程
1.2.1 依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mszlu</groupId>
<artifactId>union</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
1.2.2配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
#数据库 默认采用的数据源HikariDataSource
#sql脚本初始化 2.5.0
#DataSourceInitializationConfiguration 源码中得知不能写username和password,但好多文档说要写
#spring.sql.init.username=root
#spring.sql.init.password=root
spring.sql.init.schema-locations=classpath*:sql/*.sql
spring.sql.init.data-locations=classpath*:sql/data/*.sql
1.2.3 启动测试
package com.mszlu.union;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
1.3 MybatisPlus
1.3.1 Mapper接口
package com.mszlu.union.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.union.pojo.User;
public interface UserMapper extends BaseMapper<User> {}
package com.mszlu.union.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.mszlu.union.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
1.3.2 Sevice代码
package com.mszlu.union.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mszlu.union.mapper.UserMapper;
import com.mszlu.union.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> findAll(){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
List<User> users = userMapper.selectList(queryWrapper);
return users;
}
public List<User> findPage(){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
Page page = new Page(2,2);
Page<User> userPage = userMapper.selectPage(page,queryWrapper);
log.info("total:{}",userPage.getTotal());
log.info("pages:{}",userPage.getPages());
return userPage.getRecords();
}
}
1.3.3 测试
package com.mszlu.union.controller;
import com.mszlu.union.pojo.User;
import com.mszlu.union.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("findAll")
public List<User> findAll(){
return userService.findAll();
}
@GetMapping("findPage")
public List<User> findPage(){
return userService.findPage();
}
}
2. SpringBoot整合ShardingSphere
http://shardingsphere.apache.org/index_zh.html
2.1 导入依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.0.0-alpha</version>
</dependency>
2.2 配置读写分离
##shardingsphere配置
spring.shardingsphere.datasource.common.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.common.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.common.username=root
spring.shardingsphere.datasource.common.password= root
spring.shardingsphere.datasource.names=master,slave0,slave1
# 配置第 1 个数据源
spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=root
# 配置第 2 个数据源
spring.shardingsphere.datasource.slave0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.slave0.jdbc-url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.shardingsphere.datasource.slave0.username=root
spring.shardingsphere.datasource.slave0.password=root
# 配置第 3 个数据源
spring.shardingsphere.datasource.slave1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.slave1.jdbc-url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.shardingsphere.datasource.slave1.username=root
spring.shardingsphere.datasource.slave1.password=root
# 主数据源名称
spring.shardingsphere.rules.replica-query.data-sources.ms.primary-data-source-name=master
# 从数据源名称,多个从数据源用逗号分隔
spring.shardingsphere.rules.replica-query.data-sources.ms.replica-data-source-names=slave0,slave1
# 负载均衡算法名称
spring.shardingsphere.rules.replica-query.data-sources.ms.load-balancer-name=round-robin
## 负载均衡算法配置
spring.shardingsphere.rules.replica-query.load-balancers.round-robin.type=ROUND_ROBIN
## 负载均衡算法属性配置
spring.shardingsphere.rules.replica-query.load-balancers.round-robin.props.workId=1
#打印sql
spring.shardingsphere.props.sql-show=true
2.3 配置分表
2.3.1 准备表
CREATE TABLE If Not Exists user_0
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
CREATE TABLE If Not Exists user_1
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
CREATE TABLE If Not Exists user_2
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
2.3.2 配置
## 分表
spring.shardingsphere.rules.sharding.binding-tables=user
# 标准分片表配置
# 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
#ms是上面 读写分离的配置
spring.shardingsphere.rules.sharding.tables.user.actual-data-nodes=ms.user_$->{0..2}
# 用于单分片键的标准分片场景
# 分片列名称
spring.shardingsphere.rules.sharding.tables.user.table-strategy.standard.sharding-column=id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.user.table-strategy.standard.sharding-algorithm-name=table-inline
#雪花算法 分布式id
spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKE
spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123
spring.shardingsphere.rules.sharding.sharding-algorithms.table-inline.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.table-inline.props.algorithm-expression=user_$->{id % 3}
spring.shardingsphere.enabled=true
3. SpringBoot整合Redis
3.1 安装Redis并启动
curl -sSL https://get.daocloud.io/docker | sh
docker pull redis:latest
docker run -p 6379:6379 --name redis -v /usr/local/docker/redis/redis.conf:/opt/docker/redis/redis.conf -v /usr/local/docker/redis/data:/opt/docker/redis/data -d redis redis-server /opt/docker/redis/redis.conf --appendonly yes
docker start redis
docker exec -it redis /bin/bash
redis-cli
3.2 SpringBoot添加Redis的配置并导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
## redis配置
spring.redis.port=6379
spring.redis.host=192.168.200.100
3.3 使用
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
对之前的findAll代码 做一个缓存
@Autowired
private RedisTemplate<String,String> redisTemplate;
public List<User> findAll(){
String userListJsonStr = redisTemplate.opsForValue().get("UserService.findAll");
if (StringUtils.isNotBlank(userListJsonStr)){
List<User> users = JSON.parseArray(userListJsonStr, User.class);
log.info("走了缓存~~~");
return users;
}else {
List<User> users = userMapper.selectList(new LambdaQueryWrapper<>());
redisTemplate.opsForValue().set("UserService.findAll",JSON.toJSONString(users),2, TimeUnit.HOURS);
log.info("存入缓存~~~");
return users;
}
}
4. SpringBoot整合RocketMQ
- RocketMQ是由阿里捐赠给Apache的一款低延迟、高并发、高可用、高可靠的分布式消息中间件。经历了淘宝双十一的洗礼。RocketMQ既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。
4.1 安装 rocketmq
docker pull foxiswho/rocketmq:4.8.0
docker run -d -v /usr/local/rocketmq/logs:/opt/docker/rocketmq/logs \
--name rmqnamesrv \
-e "JAVA_OPT_EXT=-Xms512M -Xmx512M -Xmn128m" \
-p 9876:9876 \
foxiswho/rocketmq:4.8.0 \
sh mqnamesrv
brokerIP1=192.168.200.100
namesrvAddr=192.168.200.100:9876
brokerName=broker_all
docker run -d -v /opt/docker/rocketmq/logs:/usr/local/rocketmq/logs -v /opt/docker/rocketmq/store:/usr/local/rocketmq/store \
-v /opt/docker/rocketmq/conf:/usr/local/rocketmq/conf \
--name rmqbroker \
-e "NAMESRV_ADDR=192.168.200.100:9876" \
-e "JAVA_OPT_EXT=-Xms512M -Xmx512M -Xmn128m" \
-p 10911:10911 -p 10912:10912 -p 10909:10909 \
foxiswho/rocketmq:4.8.0 \
sh mqbroker -c /usr/local/rocketmq/conf/broker.conf
docker run --name rmqconsole --link rmqnamesrv:rmqnamesrv \
-e "JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.200.100:9876 -Dcom.rocketmq.sendMessageWithVIPChannel=false" \
-p 8180:8080 -t styletang/rocketmq-console-ng
4.2 整合
4.2.1 导入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
4.2.2 写配置
#rocketmq配置
rocketmq.name-server=192.168.200.100:9876
rocketmq.producer.group=springboot_group
4.2.3 代码
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void send(){
User user = this.findById(1L);
rocketMQTemplate.convertAndSend("topic_springboot",user);
}
package com.mszlu.union.service.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
@Component
@RocketMQMessageListener(topic = "topic_springboot",consumerGroup = "group1")
@Slf4j
public class UserConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String msg) {
log.info("msg:{}",msg);
}
}
//一个消费者时ConsumeMode.ORDERLY(有序地接收异步传递的消息。一个队列,一个线程,对应线程队列按顺序一个一个取)
// 1.所有发送者全部发送到同一队列(hashKey相同)时,消费者只开一个线程并且按发送顺序一个一个读取
// 2.如2个发送者发送到各自队列时,则消费者会开2个线程,每个线程按队列里数据一个一个读取,
此处只有2个队列,如果发到对应4个队列则同样开启4个线程
// 3.不指定队列,默认按一个队列放一个轮训发到默认的4个队列中,默认开启4个线程一个一个读对应线程队列的数据
//一个消费者时ConsumeMode.CONCURRENTLY(默认,同时取,并发消费模式)
// 1.默认按一个队列放一个轮训发到默认的4个队列中,每条消息都会开启一个线程读数据
// 2.所有人发送到同一队列或发送到各自队列,每条消息都会开启一个线程读数据
//一个队列只会发给一个消费者处理,不会发给多个消费者处理,多个队列可以同时由一个消费者处理,如上
//当只有一个队列时,消费的消费者挂了,会由同组(集群)的其他一个消费者继续消费,会有重复消费几个消息
producer nameServer管理broker consumer 发送者从nameServer获取broker,发消息给broker 消费者从nameServer获取broker,从broker获取消息
nameServer集群之间无状态 broker里的topic默认有4个queue broker集群有Master(brokerId为0)和Slave(brokerId非0)通过定义相同的brokerName producerGroup和consumerGroup名称相同既是集群
双主双重复制4个mq,配b1主b2从同brokerName,b3主b4从同brokerName,4个broker的brokerClusterName一样,namesrvAddr=4个IP地址分号隔,更多配置看笔记
Consumer在拉取消息之前需要对TopicMessage进行负载操作,负载操作由一个定时器来完成单位,定时间隔默认20s
默认平均负载策略 AllocateMessageQueueAveragely
偶数个队列:平均分配 奇数个队列: 队列个数>消费者 排前的那个多拿一个后面平均分,4个队列3个消费者 (Q0,Q1)->C1 Q2->C2 Q3->C3 队列个数<消费者 前面平均分最后那个拿不到,2个队列3个消费者 Q1->C1 Q2->C2 C3没有队列 C3分不到消息队列,除非前面的某个Consumer挂了,20s之后,在队列重新负载的时候就能拿到MessageQueue
环形平均分配 AllocateMessageQueueAveragelyByCircle
环形平均分配,这个和平均分配唯一的区别就是,再分队列的时候,平均队列是将属于自己的MessageQueue全部拿走,而环形平均则是,一人拿一个,拿到的Queue不是连续的。
5. SpringBoot整合ES
5.1 安装ES
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎
https://docs.spring.io/spring-data/elasticsearch/docs/4.2.1/reference/html/#new-features
看官方的对应版本。
docker pull elasticsearch:7.12.1
docker run -d --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.12.1
http://192.168.200.100:9200/
docker pull kibana:7.12.1
docker inspect es的容器id(docker ps -a 查看)
server.name: kibana
server.host: "0"
elasticsearch.hosts: [ "http://172.17.0.2:9200" ]
xpack.monitoring.ui.container.elasticsearch.enabled: true
docker run -d --restart=always --log-driver json-file --log-opt max-size=100m --log-opt max-file=2 --name kibana -p 5601:5601 -v /opt/docker/es/kibana.yml:/usr/share/kibana/config/kibana.yml kibana:7.12.1
http://192.168.200.100:5601/
GET /_search
PUT /ecommerce/product/1
{
"name" : "gaolujie yagao",
"desc" : "gaoxiao meibai",
"price" : 30,
"producer" : "gaolujie producer",
"tags": [ "meibai", "fangzhu" ]
}
GET /ecommerce/product/1
POST /ecommerce/product/1/_update
{
"doc" : {
"name" : "佳洁士牙膏"
}
}
DELETE /ecommerce/product/1
5.2 整合
5.2.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
5.2.2 常用注解
public @interface Document {
String indexName();
String type() default "";
short shards() default 5;
short replicas() default 1;
}
public @interface Id {}
public @interface Field {
FieldType type() default FieldType.Auto;
boolean index() default true;
boolean store() default false;
String analyzer() default "";
}
public enum FieldType {
Text,
Integer,
Long,
Date,
Float,
Double,
Boolean,
Object,
Auto,
Nested,
Ip,
Attachment,
Keyword
}
5.2.3 写配置
#es的配置
spring.elasticsearch.rest.uris=http://192.168.200.100:9200
spring.data.elasticsearch.repositories.enabled=true
spring.data.elasticsearch.client.reactive.endpoints=192.168.200.100:9200
5.2.4 代码
package com.mszlu.union.domain.repository;
import com.mszlu.union.model.es.Article;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article,String> {
Page<Article> findByAuthorsName(String name, Pageable pageable);
Page<Article> findByTitleIsContaining(String word,Pageable pageable);
Page<Article> findByTitle(String title,Pageable pageable);
}
package com.mszlu.union.model.es;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.List;
@Document(indexName = "blog")
@Data
public class Article {
@Id
private String id;
private String title;
@Field(type = FieldType.Nested, includeInParent = true)
private List<Author> authors;
public Article(String title) {
this.title = title;
}
}
public class Author {
private String name;
public Author(String name) {
this.name = name;
}
}
package com.mszlu.union.service;
import com.alibaba.fastjson.JSON;
import com.mszlu.union.domain.repository.ArticleRepository;
import com.mszlu.union.model.es.Article;
import com.mszlu.union.model.es.Author;
import org.checkerframework.checker.units.qual.A;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import java.util.Arrays;
import static java.util.Arrays.asList;
import static org.elasticsearch.index.query.QueryBuilders.regexpQuery;
@SpringBootTest
public class ESTest {
@Autowired
private ArticleRepository articleRepository;
@Test
public void save(){
Article article = new Article("Spring Data Elasticsearch");
article.setAuthors(asList(new Author("god"),new Author("John")));
articleRepository.save(article);
article = new Article("Spring Data Elasticsearch2");
article.setAuthors(asList(new Author("god"),new Author("King")));
articleRepository.save(article);
article = new Article("Spring Data Elasticsearch3");
article.setAuthors(asList(new Author("god"),new Author("Bill")));
articleRepository.save(article);
}
@Test
public void queryAuthorName() {
Page<Article> articles = articleRepository.findByAuthorsName("chali", PageRequest.of(0,10));
for (Article article : articles.getContent()) {
System.out.println(article);
for (Author author : article.getAuthors()) {
System.out.println(author);
}
}
}
@Test
public void update() {
Page<Article> articles = articleRepository.findByTitle("Spring Data Elasticsearch",PageRequest.of(0,10));
Article article = articles.getContent().get(0);
System.out.println(article);
System.out.println(article.getAuthors().get(0));
Author author = new Author("chali");
article.setAuthors(Arrays.asList(author));
articleRepository.save(article);
}
@Test
public void delete(){
Page<Article> articles = articleRepository.findByTitle("Spring Data Elasticsearch",PageRequest.of(0,10));
Article article = articles.getContent().get(0);
articleRepository.delete(article);
}
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Test
void queryTileContainByTemplate() {
Query query = new NativeSearchQueryBuilder().withFilter(regexpQuery("title",".*elasticsearch2.*")).build();
SearchHits<Article> articles = elasticsearchRestTemplate.search(query, Article.class, IndexCoordinates.of("blog"));
System.out.println(JSON.toJSONString(articles));
}
}
5.2.5 ElasticsearchRepository
关键字 | 使用示例 | 等同于的ES查询 |
---|
And | findByNameAndPrice | {“bool” : {“must” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}} | Or | findByNameOrPrice | {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}} | Is | findByName | {“bool” : {“must” : {“field” : {“name” : “?”}}}} | Not | findByNameNot | {“bool” : {“must_not” : {“field” : {“name” : “?”}}}} | Between | findByPriceBetween | {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : ?,”include_lower” : true,”include_upper” : true}}}}} | LessThanEqual | findByPriceLessThan | {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}} | GreaterThanEqual | findByPriceGreaterThan | {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}} | Before | findByPriceBefore | {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}} | After | findByPriceAfter | {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}} | Like | findByNameLike | {“bool” : {“must” : {“field” : {“name” : {“query” : “? *”,”analyze_wildcard” : true}}}}} | StartingWith | findByNameStartingWith | {“bool” : {“must” : {“field” : {“name” : {“query” : “? *”,”analyze_wildcard” : true}}}}} | EndingWith | findByNameEndingWith | {“bool” : {“must” : {“field” : {“name” : {“query” : “*?”,”analyze_wildcard” : true}}}}} | Contains/Containing | findByNameContaining | {“bool” : {“must” : {“field” : {“name” : {“query” : “?”,”analyze_wildcard” : true}}}}} | In | findByNameIn(Collectionnames) | {“bool” : {“must” : {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“name” : “?”}} ]}}}} | NotIn | findByNameNotIn(Collectionnames) | {“bool” : {“must_not” : {“bool” : {“should” : {“field” : {“name” : “?”}}}}}} | True | findByAvailableTrue | {“bool” : {“must” : {“field” : {“available” : true}}}} | False | findByAvailableFalse | {“bool” : {“must” : {“field” : {“available” : false}}}} | OrderBy | findByAvailableTrueOrderByNameDesc | {“sort” : [{ “name” : {“order” : “desc”} }],”bool” : {“must” : {“field” : {“available” : true}}}} |
6. SpringBoot整合SpringSecurity
Spring Security 是 Spring 家族中的一个安全管理框架,提供了权限的解决方案,通过一些简单的配置以及代码,就可以轻松实现。
安全:认证+授权
6.1 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
6.2 启动访问接口
Using generated security password: 98687887-01bf-41b5-bb6e-63009367be0f
同时访问http://localhost:8080/user/findAll ,会出现一个登陆页面
这时候,用户名输入user,密码输入上方控制台打印的密码,即可登录,并且正常访问接口。
6.3 登录的用户名/密码
对登录的用户名/密码进行配置,有三种不同的方式:
-
在 application.properties 中进行配置 spring.security.user.name=admin
spring.security.user.password=mszlu
-
通过 Java 代码配置在内存中 package com.mszlu.union.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.roles("admin")
.password("$2a$10$2UaOufuypWR1TASuso2S6.u6TGL7nuAGCsb4RZ5X2SMEuelwQBToO")
.and()
.withUser("user")
.roles("user")
password("$2a$10$2UaOufuypWR1TASuso2S6.u6TGL7nuAGCsb4RZ5X2SMEuelwQBToO");
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public static void main(String[] args) {
String mszlu = new BCryptPasswordEncoder().encode("mszlu");
System.out.println(mszlu);
}
}
-
通过 Java 从数据库中加载
6.4 登录配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user/findAll").hasRole("admin")
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("success");
out.flush();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("fail");
out.flush();
}
})
.permitAll()
.and().logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("logout success");
out.flush();
}
})
.permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
6.4.1 自定义登录
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定义登录页面</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<div id="app">
用户名: <el-input v-model="username" placeholder="请输入内容"></el-input>
密码: <el-input v-model="password" placeholder="请输入内容"></el-input>
<el-button type="success" @click="login()">提交</el-button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
new Vue({
el: "#app",
data:{
username:"",
password:""
},
methods:{
login(){
axios.post("/login?username="+this.username+"&password="+this.password).then((res)=>{
if (res.data == "success"){
this.$message.success("登录成功");
}else{
this.$message.error("用户名或密码错误");
}
})
}
}
});
</script>
</body>
</html>
6.5 数据库访问认证和授权
6.5.1 登录认证
-
表结构 CREATE TABLE `admin_user` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`create_time` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
INSERT INTO `admin_user`(`id`, `username`, `password`, `create_time`) VALUES (1, 'admin', '$2a$10$2UaOufuypWR1TASuso2S6.u6TGL7nuAGCsb4RZ5X2SMEuelwQBToO', 1622711132975);
package com.mszlu.union.pojo;
import lombok.Data;
@Data
public class AdminUser {
private Long id;
private String username;
private String password;
private Long createTime;
}
package com.mszlu.union.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.union.pojo.AdminUser;
public interface AdminUserMapper extends BaseMapper<AdminUser> {
}
-
实现UserDetailService接口 package com.mszlu.union.security;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.union.mapper.AdminUserMapper;
import com.mszlu.union.pojo.AdminUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class SecurityUserService implements UserDetailsService {
@Autowired
private AdminUserMapper adminUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AdminUser::getUsername,username).last("limit 1");
AdminUser adminUser = this.adminUserMapper.selectOne(queryWrapper);
if (adminUser == null){
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> authorityList = new ArrayList<>();
UserDetails userDetails = new User(username,adminUser.getPassword(),authorityList);
return userDetails;
}
}
-
在配置中添加使用UserDetailService @Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(securityUserService);
}
-
测试
6.5.2 授权
-
权限相关表结构 DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`role_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`role_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`role_keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
INSERT INTO `role` VALUES (1, '管理员', '管理员', 'ADMIN');
INSERT INTO `role` VALUES (2, '运营', '运营部门', 'BUSINESS');
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`user_id` bigint(0) NOT NULL,
`role_id` int(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `user_id`(`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
INSERT INTO `user_role` VALUES (1, 1, 1);
package com.mszlu.union.pojo;
import lombok.Data;
@Data
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
private String roleKeyword;
}
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`permission_keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
INSERT INTO `permission` VALUES (1, '查询全部', '查询全部', 'USER_FINDALL', '/user/findAll');
INSERT INTO `permission` VALUES (2, '年龄查询', '年龄查询', 'USER_FINDAGE', '/user/findAge');
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`role_id` int(0) NOT NULL,
`permission_id` int(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `role_id`(`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
INSERT INTO `role_permission` VALUES (1, 1, 1);
package com.mszlu.union.pojo;
import lombok.Data;
@Data
public class Permission {
private Integer id;
private String name;
private String desc;
private String permissionKeyword;
private String path;
}
-
在UserDetailService的接口实现中,查询用户的权限 package com.mszlu.union.security;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.union.mapper.AdminUserMapper;
import com.mszlu.union.mapper.PermissionMapper;
import com.mszlu.union.mapper.RoleMapper;
import com.mszlu.union.pojo.AdminUser;
import com.mszlu.union.pojo.Permission;
import com.mszlu.union.pojo.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class SecurityUserService implements UserDetailsService {
@Autowired
private AdminUserMapper adminUserMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AdminUser::getUsername,username).last("limit 1");
AdminUser adminUser = this.adminUserMapper.selectOne(queryWrapper);
if (adminUser == null){
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> authorityList = new ArrayList<>();
List<Role> roleList = roleMapper.findRoleListByUserId(adminUser.getId());
for (Role role : roleList) {
List<Permission> permissionList = permissionMapper.findPermissionByRole(role.getId());
authorityList.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleKeyword()));
for (Permission permission : permissionList) {
authorityList.add(new SimpleGrantedAuthority(permission.getPermissionKeyword()));
}
}
UserDetails userDetails = new User(username,adminUser.getPassword(),authorityList);
return userDetails;
}
}
package com.mszlu.union.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.union.pojo.Permission;
import com.mszlu.union.pojo.Role;
import java.util.List;
public interface PermissionMapper extends BaseMapper<Permission> {
List<Permission> findPermissionByRole(Integer roleId);
}
package com.mszlu.union.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.union.pojo.Role;
import java.util.List;
public interface RoleMapper extends BaseMapper<Role> {
List<Role> findRoleListByUserId(Long userId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mszlu.union.mapper.PermissionMapper">
<resultMap id="perMap" type="com.mszlu.union.pojo.Permission">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="desc" property="desc"/>
<result column="permission_keyword" property="permissionKeyword"/>
<result column="path" property="path"/>
</resultMap>
<select id="findPermissionByRole" parameterType="int" resultMap="perMap">
select * from permission where id in (select permission_id from role_permission where role_id=#{roleId})
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mszlu.union.mapper.RoleMapper">
<resultMap id="roleMap" type="com.mszlu.union.pojo.Role">
<id column="id" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_desc" property="roleDesc"/>
<result column="role_keyword" property="roleKeyword"/>
</resultMap>
<select id="findRoleListByUserId" parameterType="long" resultMap="roleMap">
select * from role where id in (select role_id from user_role where user_id=#{userId})
</select>
</mapper>
-
在接口上配置权限 @GetMapping("findAll")
@PreAuthorize("hasAuthority('USER_FINDALL')")
public List<User> findAll(){
return userService.findAll();
}
@GetMapping("findAge")
@PreAuthorize("hasAuthority('USER_FINDAGE')")
public List<User> findAge(){
return userService.findAge();
}
@GetMapping("findById")
@PreAuthorize("hasRole('ADMIN')")
public User findById(@RequestParam("id") Long id){
return userService.findById(id);
}
-
在配置上开启权限认证 @Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {}
-
测试
6.5.3 另一种方式进行授权
-
修改配置 @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().access("@authService.auth(request,authentication)")
}
-
编写AuthService的auth方法、 package com.mszlu.union.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.union.mapper.AdminUserMapper;
import com.mszlu.union.mapper.PermissionMapper;
import com.mszlu.union.mapper.RoleMapper;
import com.mszlu.union.mapper.UserMapper;
import com.mszlu.union.pojo.AdminUser;
import com.mszlu.union.pojo.Permission;
import com.mszlu.union.pojo.Role;
import com.mszlu.union.security.MySimpleGrantedAuthority;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Service
public class AuthService {
@Autowired
private AdminUserMapper adminUserMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
public boolean auth(HttpServletRequest request, Authentication authentication){
String requestURI = request.getRequestURI();
Object principal = authentication.getPrincipal();
if (principal == null || "anonymousUser".equals(principal)){
return false;
}
UserDetails userDetails = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
for (GrantedAuthority authority : authorities) {
MySimpleGrantedAuthority grantedAuthority = (MySimpleGrantedAuthority) authority;
String[] paths = StringUtils.split(requestURI, "?");
if (paths[0].equals(grantedAuthority.getPath())){
return true;
}
}
return false;
}
}
-
修改UserDetailService中 授权的实现,实现自定义的授权类,增加path属性 package com.mszlu.union.security;
import org.springframework.security.core.GrantedAuthority;
public class MySimpleGrantedAuthority implements GrantedAuthority {
private String authority;
private String path;
public MySimpleGrantedAuthority(){}
public MySimpleGrantedAuthority(String authority){
this.authority = authority;
}
public MySimpleGrantedAuthority(String authority,String path){
this.authority = authority;
this.path = path;
}
@Override
public String getAuthority() {
return authority;
}
public String getPath() {
return path;
}
}
-
测试
7. SpringBoot整合Dubbo
Dubbo是阿里开源的高性能,轻量级的分布式服务框架,提供了六大核心能力:
-
面向接口代理的高性能RPC调用 -
智能容错和负载均衡 -
服务自动注册和发现 -
高度可扩展能力 -
运行期流量调度 -
可视化的服务治理与运维
https://dubbo.apache.org/zh/
使用2.7版本:
https://dubbo.apache.org/zh/docs/v2.7/user/preface/background/ 架构演进
https://dubbo.apache.org/zh/docs/v2.7/user/preface/architecture/ dubbo架构
7.1 导入依赖
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
7.2 服务提供方
7.2.1 搭建工程dubbo-provider
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mszlu</groupId>
<artifactId>dubbo-provider</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
</project>
7.2.2 配置dubbo
# Spring boot application
spring.application.name=dubbo-provider
# Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
dubbo.scan.base-packages=com.mszlu.dubbo.service
# Dubbo Application
## The default value of dubbo.application.name is ${spring.application.name}
## dubbo.application.name=${spring.application.name}
# Dubbo Protocol
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
## Dubbo Registry
dubbo.registry.protocol=zookeeper
dubbo.registry.address=localhost:2181
7.2.3 编写服务
-
搭建通用的接口模块 dubbo-provider-service <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mszlu</groupId>
<artifactId>dubbo-provider-service</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
-
编写service接口 package com.mszlu.dubbo.service;
public interface DubboUserService {
void save();
}
-
在dubbo-provider中编写实现类,前提 导入dubbo-provider-service依赖 package com.mszlu.dubbo.service.impl;
import com.mszlu.dubbo.service.DubboUserService;
import org.apache.dubbo.config.annotation.DubboService;
@DubboService(version = "1.0.0",interfaceClass = DubboUserService.class)
public class DubboUserServiceImpl implements DubboUserService {
@Override
public void save() {
System.out.println("dubbo save running.....");
}
}
-
启动服务
7.3 服务消费方
7.3.1 添加依赖
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.mszlu</groupId>
<artifactId>dubbo-provider-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
7.3.2 添加dubbo配置
spring.application.name=union
## Dubbo Registry
dubbo.consumer.check=false
dubbo.registry.protocol=zookeeper
dubbo.registry.address=localhost:2181
7.3.3 消费方代码
@DubboReference(version = "1.0.0")
private DubboUserService dubboUserService;
@GetMapping("dubbo")
public String dubbo(){
dubboUserService.save();
return "success";
}
7.3.4 测试
.antMatchers("/user/dubbo").permitAll()
8. SpringBoot整合Swagger2
强大的api文档工具,让开发人员摆脱繁杂的文档苦海。
8.1 导入依赖
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
8.2 启动类添加@EnableSwagger2Doc注解
package com.mszlu.union;
import com.spring4all.swagger.EnableSwagger2Doc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableSwagger2Doc
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
8.2 配置
swagger.title=码神之路-swagger2
swagger.description=码神之路-swagger2 描述信息
swagger.version=2.9.2
swagger.license=Apache License, Version 2.0
swagger.licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.html
swagger.termsOfServiceUrl=https://github.com/dyc87112/spring-boot-starter-swagger
swagger.contact.name=码神之路
swagger.contact.url=http://blog.mszlu.com
swagger.contact.email=码神之路
swagger.base-package=com.mszlu
swagger.base-path=/**
各参数配置含义如下:
swagger.title :标题swagger.description :描述swagger.version :版本swagger.license :许可证swagger.licenseUrl :许可证URLswagger.termsOfServiceUrl :服务条款URLswagger.contact.name :维护人swagger.contact.url :维护人URLswagger.contact.email :维护人emailswagger.base-package :swagger扫描的基础包,默认:全扫描swagger.base-path :需要处理的基础URL规则,默认:/**
8.3 添加文档内容
在整合完Swagger之后,在http://localhost:8081/swagger-ui.html 页面中可以看到,关于各个接口的描述还都是英文或遵循代码定义的名称产生的。
这些内容对用户并不友好,所以我们需要自己增加一些说明来丰富文档内容。
如下所示,我们通过@Api ,@ApiOperation 注解来给API增加说明、通过@ApiImplicitParam 、@ApiModel 、@ApiModelProperty 注解来给参数增加说明。
package com.mszlu.union.pojo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description="用户实体")
public class User {
@ApiModelProperty("用户编号")
private Long id;
@ApiModelProperty("用户姓名")
private String name;
private String email;
@ApiModelProperty("用户年龄")
private Integer age;
}
package com.mszlu.union.controller;
import com.mszlu.dubbo.service.DubboUserService;
import com.mszlu.union.pojo.User;
import com.mszlu.union.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Api(tags = "用户管理")
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("findAll")
@ApiOperation(value = "获取用户列表")
public List<User> findAll(){
return userService.findAll();
}
@GetMapping("findAge")
public List<User> findAge(){
return userService.findAge();
}
@GetMapping("findById")
@ApiOperation(value = "查询用户信息",notes = "根据id查询用户信息")
@ApiImplicitParam(paramType = "get传参", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1")
public User findById(@RequestParam("id") Long id){
return userService.findById(id);
}
@GetMapping("findPage")
public List<User> findPage(@RequestParam("page") Integer page,
@RequestParam("pageSize") Integer pageSize){
return userService.findPage(page,pageSize);
}
@GetMapping("save")
public Long findAll(@RequestParam("name") String name){
return userService.save(name);
}
@GetMapping("send")
public String send(){
userService.send();
return "success";
}
@DubboReference(version = "1.0.0")
private DubboUserService dubboUserService;
@GetMapping("dubbo")
public String dubbo(){
dubboUserService.save();
return "success";
}
}
8.4 springSecurity框架忽略swagger2的相关url
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/swagger*/**").permitAll()
.antMatchers("/v2/**").permitAll()
.antMatchers("/csrf").permitAll()
05-SpringBoot原理
1. SpringBoot原理分析
1.1 @EnableAutoConfiguration
1.1.1 @Import(AutoConfigurationImportSelector.class)
使用@Import导入的类会被Spring加载到IOC容器中
@Import提供4种用法:
-
导入Bean @Import(User.class)
-
导入配置类 @Import(UserConfig.class)
@Configuration
public class UserConfig {
@Bean("user")
public User user() {
return new User();
}
}
-
导入 ImportSelector 实现类。一般用于加载配置文件中的类 @Import(MyImportSelector.class)
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.mszlu.domain.User"};
}
}
-
导入 ImportBeanDefinitionRegistrar 实现类。 @Import(MyImportBeanDefinitionRegistrar.class)
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user",beanDefinition);
}
}
-
在resources下新建 META-INF/spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mszlu.union.pojo.User
1.1.2 AutoConfigurationImportSelector代码分析
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
//这是spring-boot-autoconfigure包下的META-INF/spring.factories 其中关于Redis的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
package org.springframework.boot.autoconfigure.data.redis;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
1.1.3 @Conditional注解
从上方RedisAutoConfiguration的代码可以看到这些注解:
-
@ConditionalOnMissingBean(name = “redisTemplate”) 如果没有redisTemplate这个Bean的时候 才运行以下的代码 -
@ConditionalOnClass(RedisOperations.class) 如果没有RedisOperations这个类(类路径上找不到,也就是没导入相关的依赖),则不运行 -
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) 指定的类存在,且必须注册在spring中,是单例的
上面的这些都是由@Conditional这个注解实现的:条件,满足条件执行,不满足条件就不执行
1.1.3 总结 面试题
-
@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class)来加载配置类。 -
配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会扫描依赖 jar 包中 spring.factories 文件,自动加载 这些配置类,初始化Bean 到spring 容器中,业务直接引用即可。 -
并不是所有的Bean都会被初始化,在配置类中使用@Conditional来加载满足条件的Bean。 引入第三方jar包就相当于我本地有这jar包的代码,跟本地代码一样效果
思路:
- 创建一个maven工程,创建三个个模块
- 一个模块为demo-app,一个模块为demo-module,一个模块为demo-module-springboot-starter
- demo-module中定义一个类MyModule,其中有一个save方法,读取properties配置文件中的com.mszlu.version和com.mszlu.age的属性值
- app模块 引入demo-module-springboot-starter模块,不需要初始化MyModule,只需要配置com.mszlu.version和com.mszlu.age就可以直接初始化MyModule并调用save方法
1.2.1 代码
-
新建父工程 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mszlu</groupId>
<artifactId>starter-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>demo-module</module>
<module>demo-module-springboot-starter</module>
<module>demo-app</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
</project>
-
新建demo-module,编写代码 package com.mszlu.module;
public class MyModule {
private String version;
private Integer age;
public void save(){
System.out.println("my module save.... version:"+ version + ",age:" + age);
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
-
新建demo-module-springboot-starter模块 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>starter-parent</artifactId>
<groupId>com.mszlu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>demo-module-springboot-starter</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mszlu</groupId>
<artifactId>demo-module</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package com.mszlu.springbootstarter.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "com.mszlu")
public class ModuleConfig {
private String version;
private Integer age;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package com.mszlu.springbootstarter;
import com.mszlu.module.MyModule;
import com.mszlu.springbootstarter.config.ModuleConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(ModuleConfig.class)
public class ModuleAutoConfiguration {
@Bean
@ConditionalOnProperty(name = {"com.mszlu.version","com.mszlu.age"})
public MyModule myModule(ModuleConfig moduleConfig){
MyModule myModule = new MyModule();
myModule.setVersion(moduleConfig.getVersion());
myModule.setAge(moduleConfig.getAge());
return myModule;
}
}
-
在resources下新建META-INF/spring.factories 文件 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mszlu.springbootstarter.ModuleAutoConfiguration
spring.factories文件是帮助spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器。由于@ComponentScan 注解只能扫描spring-boot项目包内的bean并注册到spring容器中,因此需要@EnableAutoConfiguration 注解来注册项目包外的bean。而spring.factories文件,则是用来记录项目包外需要注册的bean类名。 -
新建demo-app模块 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>starter-parent</artifactId>
<groupId>com.mszlu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>demo-app</artifactId>
<dependencies>
<dependency>
<groupId>com.mszlu</groupId>
<artifactId>demo-module-springboot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
-
编写测试类 package com.mszlu.app;
import com.mszlu.module.MyModule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
@RequestMapping
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
@Autowired
private MyModule myModule;
@GetMapping
public String hello(){
myModule.save();
return "success";
}
}
-
测试 如果没有在application.properties中配置: com.mszlu.version=1.0
com.mszlu.age=20
那么无法执行,如果配了可以正常执行
2. SpringBoot 启动流程分析
SpringApplication.run(App.class,args);
上面的启动类,最终会调用:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
那么问题就变为了,new SpringApplication()的时候,做了哪些操作?
run的时候做了哪些操作?
2.1 new SpringApplication()
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
setInitializers((Collection)
getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2.2 run()
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
package com.mszlu.app.listener;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
public MySpringApplicationRunListener(SpringApplication application, String[] args) {
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
System.out.println("starting...项目启动中");
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
System.out.println("environmentPrepared...环境对象开始准备");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("contextPrepared...上下文对象开始准备");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("contextLoaded...上下文对象开始加载");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("started...上下文对象加载完成");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("running...项目启动完成,开始运行");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("failed...项目启动失败");
}
}
package com.mszlu.app.listener;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run");
System.out.println(Arrays.asList(args));
}
}
package com.mszlu.app.listener;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run");
System.out.println(Arrays.asList(args.getSourceArgs()));
}
}
org.springframework.boot.SpringApplicationRunListener=com.mszlu.app.listener.MySpringApplicationRunListener
|