在您第1次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?
在你第n次使用Spring框架的时候,是否觉得一堆反复黏贴的配置有一些厌烦?
那么您就不妨来试试使用SpringBoot来让你更易上手,更简单快捷地构建Spring应用!
SpringBoot让我们的Spring应用变的更轻量化。
我们不必像以前那样繁琐的构建项目、打包应用、部署到Tomcat等应用服务器中来运行我们的业务服务。
通过SpringBoot实现的服务,只需要依靠一个Java类,把它打包成jar,并通过java -jar
命令就可以运行起来。
这一切相较于传统Spring应用来说,已经变得非常的轻便、简单。
总结一下SpringBoot的主要优点:
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+的容器中
快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。
教程使用的Idea版本:2019.3
**第一步:**创建maven项目
pom.xml :
<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.0modelVersion>
<groupId>com.xiaopizhugroupId>
<artifactId>helloSpringBootartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
parent>
project>
注意上方的parent必须加,其中定义了springboot官方支持的n多依赖,基本上常用的已经有了,所以接下来导入依赖的时候,绝大部分都可以不加版本号。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
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的核心。
看到如上配置,证明启动成功,tomcat端口号默认为8080。
**第五步:**如果想要修改端口号,可以在resources目录下新建application.properties
server.port=8082
src/main/java : 编写java代码,注意启动类需要放在项目的根包下。
src/main/resources: 放置资源的目录,比如springboot的配置文件,静态文件,mybatis配置,日志配置等。
src/test/java: 测试代码
**第一步:**创建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
**第一步:**添加spring boot测试依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
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调用,模拟请求。
**第一步:**添加打包(maven构建springboot)插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
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
jar tvf helloSpringBoot-1.0-SNAPSHOT.jar
Spring Boot框架本身并没有对工程结构有特别的要求,但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑。
尤其是Spring包扫描机制的存在,如果您使用最佳实践的工程结构,可以免去不少特殊的配置工作。
以下结构是比较推荐的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,并进行初始化了。
那么如果,我们一定要加载非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等。
这里讨论一下,除了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用来管理业务流程。
在前面的入门案例中,我们轻松的实现了一个简单的RESTful API应用,体验了一下Spring Boot给我们带来的诸多优点,我们用非常少的代码量成功的实现了一个Web应用,这是传统的Spring应用无法办到的。
配置方面,使用了application.properties 这个Spring Boot可以识别的配置文件,做了tomcat监听端口的修改,其他配置暂时没有用到。
在实际的工作中,开发一个Spring Boot程序需要修改配置文件的内容来达到不同的需求,到了后期如果学习到了Spring Cloud,在Spring Cloud中,其实有大量的工作都会是针对配置文件的。
所以我们有必要深入的了解一些关于Spring Boot中的配置文件的知识,比如:它的配置方式、如何实现多环境配置,配置信息的加载顺序等。
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为主。
我们除了可以在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;
// 省略getter和setter
}
@Value
注解加载属性值的时候可以支持表达式来进行配置:
${...}
,大括号内为PlaceHolder,上面示例中使用的方式yml格式:
book:
name: SpringBoot
author: god_coder
在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》
。
#yml格式
book:
name: SpringBoot
author: god_coder
desc: ${book.author} is writing《${book.name}》
在一些特殊情况下,有些参数我们希望它每次加载的时候不是一个固定的值,比如:密钥、服务端口等。在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;
在之前的快速入门案例中,我们介绍了使用命令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中实现多环境的配置。
注意:命令行参数在需要临时改变一些参数的时候非常适用
我们在开发任何应用的时候,一般都会有多套环境,比如:开发、测试、生产等。
其中每个环境的数据库地址、服务器端口等等配置都会不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常繁琐且容易发生错误的事。
对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,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环境设置为3333spring.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
中配置各个环境不同的内容在上面的例子中,我们将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格式配置在系统环境变量中的内容。java:comp/env
中的JNDI
属性。System.getProperties()
获得的内容。random.*
配置的属性 RandomValuePropertySource{profile}
环境的配置文件内容,例如:application-{profile}.properties
或是YAML
定义的配置文件application.properties
和YAML
配置内容{profile}
环境的配置文件内容,例如:application-{profile}.properties
或是YAML
定义的配置文件application.properties
和YAML
配置内容@Configuration
注解修改的类中,通过@PropertySource
注解定义的属性SpringApplication.setDefaultProperties
定义的内容优先级按上面的顺序有高到低,数字越小优先级越高。
可以看到,其中第9项和第10项都是从应用jar包之外读取配置文件,所以,实现外部化配置的原理就是从此切入,为其指定外部配置文件的加载位置来取代jar包之内的配置内容。
通过这样的实现,我们的工程在配置中就变的非常干净,我们只需要在本地放置开发需要的配置即可,而其他环境的配置就可以不用关心,由其对应环境的负责人去维护即可。
在Spring Boot 2.0中推出了Relaxed Binding 2.0,对原有的属性绑定功能做了非常多的改进以帮助我们更容易的在Spring应用中加载和读取配置信息。下面本文就来说说Spring Boot 2.0中对配置的改进。
在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
在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
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
简单类型
在环境变量中通过小写转换与.
替换_
来映射配置文件中的内容,比如:环境变量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]
简单类型
系统属性与文件配置中的类似,都以移除特殊字符并转化小写后实现绑定,比如下面的命令行参数都会实现配置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
上文介绍了Spring Boot 2.0中对属性绑定的内容,可以看到对于一个属性我们可以有多种不同的表达,但是如果我们要在Spring应用程序的environment中读取属性的时候,每个属性的唯一名称符合如下规则:
.
分离各个元素.
将前缀与属性名称分开-
来分隔单词[
和]
,用于List的索引所以,如果我们要读取配置文件中spring.jpa.database-platform
的配置,可以这样写:
this.environment.containsProperty("spring.jpa.database-platform")
而下面的方式是无法获取到spring.jpa.database-platform
配置内容的:
this.environment.containsProperty("spring.jpa.databasePlatform")
注意:使用@Value
获取配置内容的时候也需要这样的特点
在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配置
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);
spring.datasource.*
已被 spring.sql.init.*
属性替代。server.error.include-message
开启。logging.register-shutdown-hook
属性可以在 jvm 退出时释放日志资源。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, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
<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.0modelVersion>
<groupId>com.mszlugroupId>
<artifactId>unionartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
project>
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
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);
}
}
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;
}
}
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();
}
}
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();
}
}
http://shardingsphere.apache.org/index_zh.html
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starterartifactId>
<version>5.0.0-alphaversion>
dependency>
##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
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)
);
## 分表
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
#docker安装
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
#-p 6379:6379 端口映射:前表示主机部分,:后表示容器部分。
# --name redis 指定该容器名称,查看和进行操作都比较方便。
# -v 挂载目录,规则与端口映射相同。
# -d redis 表示后台启动redis
# redis-server /opt/docker/redis/redis.conf #以配置文件启动redis,加载容器内的conf文件,最终找到的是挂载的目录/usr/local/docker/redis/redis.conf
docker start redis
#进入容器
docker exec -it redis /bin/bash
#验证
redis-cli
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
## redis配置
spring.redis.port=6379
spring.redis.host=192.168.200.100
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
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;
}
}
#docker 拉取
docker pull foxiswho/rocketmq:4.8.0
#启动nameserver
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
#broker.conf
brokerIP1=192.168.200.100
namesrvAddr=192.168.200.100:9876
brokerName=broker_all
#启动broker
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
#rocketmq-console-ng
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
#启动访问 http://192.168.200.100:8180/
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-clientartifactId>
<version>4.8.0version>
dependency>
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
#rocketmq配置
rocketmq.name-server=192.168.200.100:9876
rocketmq.producer.group=springboot_group
//生产者
@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);
}
}
//MessageModel.CLUSTERING=集群模式,一个消息只会被一个消费者消费(默认),注意不同consumerGroup会重复消费一次
//MessageModel.BROADCASTING=广播模式,一个消息会被多个消费者消费
//一个消费者时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不是连续的。
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎
https://docs.spring.io/spring-data/elasticsearch/docs/4.2.1/reference/html/#new-features
看官方的对应版本。
#下载es的镜像
docker pull elasticsearch:7.12.1
#创建es的容器 并启动 single-node单机
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/
#下载kibana的镜像
docker pull kibana:7.12.1
#准备kibana的配置文件
docker inspect es的容器id(docker ps -a 查看)
# 找到 "IPAddress": "172.17.0.2" 找出es对应的容器ip地址
# Default Kibana configuration for docker target
server.name: kibana
server.host: "0"
elasticsearch.hosts: [ "http://172.17.0.2:9200" ]
xpack.monitoring.ui.container.elasticsearch.enabled: true
#启动kibana
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
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
//标示映射到Elasticsearch文档上的领域对象
public @interface Document {
//索引库名次,mysql中数据库的概念
String indexName();
//文档类型,mysql中表的概念
String type() default "";
//默认分片数
short shards() default 5;
//默认副本数量
short replicas() default 1;
}
//表示是文档的id,文档可以认为是mysql中主键的概念
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//不会进行分词建立索引的类型
}
#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
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);
//搜索title字段
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;
//注意indexName要小写
@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;
//使用Template进行关键字查询
//关于正则表达式可以参考https://www.runoob.com/java/java-regular-expressions.html
//.*data.* 可以匹配ddata, dataa等
@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));
}
}
关键字 | 使用示例 | 等同于的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}}}} |
Spring Security 是 Spring 家族中的一个安全管理框架,提供了权限的解决方案,通过一些简单的配置以及代码,就可以轻松实现。
安全:认证+授权
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
//启动会在控制台出现
Using generated security password: 98687887-01bf-41b5-bb6e-63009367be0f
同时访问http://localhost:8080/user/findAll ,会出现一个登陆页面
这时候,用户名输入user,密码输入上方控制台打印的密码,即可登录,并且正常访问接口。
对登录的用户名/密码进行配置,有三种不同的方式:
在 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 从数据库中加载
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //开启登录认证
.antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/login").permitAll()
.anyRequest().authenticated() // 其他所有的请求 只需要登录即可
.and().formLogin()
.loginPage("/login.html") //自定义的登录页面
.loginProcessingUrl("/login") //登录处理接口
.usernameParameter("username") //定义登录时的用户名的key 默认为username
.passwordParameter("password") //定义登录时的密码key,默认是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(); //csrf关闭 如果自定义登录 需要关闭
}
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>
表结构
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);
//...
}
测试
权限相关表结构
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;
-- ----------------------------
-- Records of role
-- ----------------------------
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;
-- ----------------------------
-- Records of user_role
-- ----------------------------
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;
-- ----------------------------
-- Records of permission
-- ----------------------------
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;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
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<>();
//查询角色和角色对应的权限 并赋予当前的登录用户,并告知spring security框架
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);
}
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>
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 {}
测试
修改配置
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.userDetailsService(securityUserService);
http.authorizeRequests() //开启登录认证
// .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/login").permitAll()
//使用访问控制 自己实现service处理,会接收两个参数
.anyRequest().access("@authService.auth(request,authentication)")
// .anyRequest().authenticated() // 其他所有的请求 只需要登录即可
}
编写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;
}
}
测试
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架构
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.8version>
dependency>
<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.0modelVersion>
<groupId>com.mszlugroupId>
<artifactId>dubbo-providerartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.8version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.1.0version>
dependency>
dependencies>
project>
# 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
搭建通用的接口模块 dubbo-provider-service
<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.0modelVersion>
<groupId>com.mszlugroupId>
<artifactId>dubbo-provider-serviceartifactId>
<version>1.0-SNAPSHOTversion>
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.....");
}
}
启动服务
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.8version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>5.1.0version>
dependency>
<dependency>
<groupId>com.mszlugroupId>
<artifactId>dubbo-provider-serviceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
spring.application.name=union
## Dubbo Registry
dubbo.consumer.check=false
dubbo.registry.protocol=zookeeper
dubbo.registry.address=localhost:2181
@DubboReference(version = "1.0.0")
private DubboUserService dubboUserService;
@GetMapping("dubbo")
public String dubbo(){
dubboUserService.save();
return "success";
}
.antMatchers("/user/dubbo").permitAll()
强大的api文档工具,让开发人员摆脱繁杂的文档苦海。
<dependency>
<groupId>com.spring4allgroupId>
<artifactId>swagger-spring-boot-starterartifactId>
<version>1.9.1.RELEASEversion>
dependency>
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);
}
}
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规则,默认:/**在整合完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;
//User 对象 如何和 user表关联起来
@Data
//@TableName("user")
//tb_user
//@TableName("tb_user") mybatis-plus.global-config.db-config.table-prefix=tb_
@ApiModel(description="用户实体")
public class User {
@ApiModelProperty("用户编号")
private Long id;
@ApiModelProperty("用户姓名")
//@TableField("name")// 表的字段
private String name;
//表字段 是user_name
//@TableField("user_name") 可以不加 _会替换为驼峰写法
//private String userName;
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 = "获取用户列表")
//@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")
@ApiOperation(value = "查询用户信息",notes = "根据id查询用户信息")
@ApiImplicitParam(paramType = "get传参", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1")
//@PreAuthorize("hasRole('ADMIN')")
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";
}
//问注册中心 要服务提供方的地址
//发起rpc调用
//返回的数据 进行解析
@DubboReference(version = "1.0.0")
private DubboUserService dubboUserService;
@GetMapping("dubbo")
public String dubbo(){
dubboUserService.save();
return "success";
}
}
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/swagger*/**").permitAll()
.antMatchers("/v2/**").permitAll()
.antMatchers("/csrf").permitAll()
使用@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
@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) {
//读取META-INF/spring.factories 中的配置,根据其中的配置找到对应需要加载的配置类
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
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis support.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Christian Dupuis
* @author Christoph Strobl
* @author Phillip Webb
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Marco Aust
* @author Mark Paluch
* @since 1.0.0
*/
@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;
}
}
从上方RedisAutoConfiguration的代码可以看到这些注解:
@ConditionalOnMissingBean(name = “redisTemplate”)
如果没有redisTemplate这个Bean的时候 才运行以下的代码
@ConditionalOnClass(RedisOperations.class)
如果没有RedisOperations这个类(类路径上找不到,也就是没导入相关的依赖),则不运行
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
指定的类存在,且必须注册在spring中,是单例的
上面的这些都是由@Conditional这个注解实现的:条件,满足条件执行,不满足条件就不执行
@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class)来加载配置类。
配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会扫描依赖 jar 包中 spring.factories 文件,自动加载 这些配置类,初始化Bean 到spring 容器中,业务直接引用即可。
并不是所有的Bean都会被初始化,在配置类中使用@Conditional来加载满足条件的Bean。
引入第三方jar包就相当于我本地有这jar包的代码,跟本地代码一样效果
思路:
新建父工程
<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.0modelVersion>
<groupId>com.mszlugroupId>
<artifactId>starter-parentartifactId>
<version>1.0-SNAPSHOTversion>
<modules>
<module>demo-modulemodule>
<module>demo-module-springboot-startermodule>
<module>demo-appmodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
parent>
<packaging>pompackaging>
<properties>
<java.version>1.8java.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模块
<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-parentartifactId>
<groupId>com.mszlugroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>demo-module-springboot-starterartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.mszlugroupId>
<artifactId>demo-moduleartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
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模块
<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-parentartifactId>
<groupId>com.mszlugroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>demo-appartifactId>
<dependencies>
<dependency>
<groupId>com.mszlugroupId>
<artifactId>demo-module-springboot-starterartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
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
那么无法执行,如果配了可以正常执行
SpringApplication.run(App.class,args);
上面的启动类,最终会调用:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
那么问题就变为了,new SpringApplication()的时候,做了哪些操作?
run的时候做了哪些操作?
//primarySources 启动类
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 这可以看出来,启动类可以配置多个,放入set中去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断是否是一个web程序
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从META-INF下的spring.factories文件中获取所有BootstrapRegistryInitializer
//初始化启动注册器,用于为ApplicationContext的创建做准备工作,运行在启动期间和环境加载之后
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
//从 spring.factories 加载所有 ApplicationContextInitializer
//用于在spring容器刷新之前初始化Spring ConfigurableApplicationContext的回调接口。
//通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
setInitializers((Collection)
getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 从 spring.factories 加载所有 ApplicationListener
//spring 事件监听与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断主类
this.mainApplicationClass = deduceMainApplicationClass();
}
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//spring的计时器 ms级别
StopWatch stopWatch = new StopWatch();
//计时开始
stopWatch.start();
//创建 DefaultBootstrapContext 会基于 bootstrapRegistryInitializers 初始化
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//属性 java.awt.headless 处理
//Headless模式是系统的一种配置模式。在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式。
configureHeadlessProperty();
//springboot启动监听器 ,可以自定义 并放入META-INF下的spring.factories中 观察spring的启动流程
//主要是就是用来发布各种 SpringApplicationEvent
SpringApplicationRunListeners listeners = getRunListeners(args);
//监听开始
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
基于 args 构造 ApplicationArguments,即参数行命令
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
/**
* 构造对应的 ConfigurableEnvironment 返回:
* 其中发布了核心事件 ApplicationEnvironmentPreparedEvent
* 对应监听器完成了配置文件的读取解析
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// spring.beaninfo.ignore 属性相关,值为“true”表示跳过对BeanInfo类的搜索
configureIgnoreBeanInfo(environment);
// 打印 banner 并返回 Banner 对象 彩蛋
Banner printedBanner = printBanner(environment);
// 根据 webApplicationType 创建对应的容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
/**
* 在执行完所有 ApplicationContextInitializer 后
* 发布事件 ApplicationContextInitializedEvent
* 在加载完所有资源类后发布事件 ApplicationPreparedEvent
*/
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//调用容器的 refresh 方法,单例会在该阶段后创建
refreshContext(context);
afterRefresh(context, applicationArguments);
//停止计时
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发布 ApplicationStartedEvent
listeners.started(context);
//执行所有 CommandLineRunner 和 ApplicationRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//执行完所有 CommandLineRunner 和 ApplicationRunner 后,
//发布事件 ApplicationReadyEvent
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;
/**
* 当项目启动后执行run方法。
*/
@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