SpringBoot的诞生
什么是Spring?
现代化的java开发主要就是面向Spring开发。
Spring是一个开源框架,它由Rod Johnson在2003年创建。它是为了简化企业级开发而创建的。
Spring是十分优雅的,这是因为它的创始人是一位音乐学博士,某种意义上说,它是一个含有艺术基因的框架。
Spring是如何简化开发的?
1、JavaBean代表着一个个要new的对象。
2、Ioc控制反转思想,所有的对象都去容器中去getBean即可。
3、AOP面向切面编程,降低入侵性。
4、提供了许多的模板类简化开发,很多功能无需手动编写步骤。
什么是SpringBoot?
传统的JavaWeb开发:Tomcat+Servlet+JSP,web.xml中需要自己配置大量的Servlet,需要自己解决管理版本依赖问题。
SSH+SSM:框架,简化了一些固有的开发,但需要配置大量的xml配置文件。
于是人们开始思考:有一天要是能够实现自动配置就好了!
于是SpringBoot应运而生。SpringBoot的主要特点有:
- 自动配置。
- 它不是一个新的事物,它是Spring的增强版。
- 约定大于配置。
- 集成了市场上所有常用的依赖,并且自动管理版本依赖。
- 内置了Web容器。
从Hello World开始
Spring官方提供了初识项目生成器,供开发者自动生成一个SpringBoot项目。
上述网站被IDEA集成了,因此我们平时开发直接使用IDEA即可。
构建SpringBoot项目的步骤
1、使用IDEA创建一个SpringBoot应用。
2、打开启动类,启动项目进行测试。
3、编写一个controller,然后再次启动测试。
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "Hello World";
}
}
和传统开发进行对比
SSM框架构建项目步骤:
1、配置Tomcat。
2、在Web.xml中注册Spring。
3、添加或者扫描我们需要的所有依赖是否存在,不存在就需要配置依赖。
4、编写Spring、SpringMVC、MyBatis等其他所有框架的配置。
5、配置完毕,需要编写无关的业务代码。
6、整体框架搭建完毕,才开始编写业务代码。
7、启动测试。
SpringBoot构建项目步骤:
1、构建项目,一键生成。
2、编写业务代码。
3、启动测试。
对比之后可以发现,使用了SpringBoot之后一切都变得十分简单了,正如官网所说:you can "just run"。
项目如何发布到服务器运行?
单体应用直接打包成jar包就可以了,命令:mvn package
通过java -jar
或者 java -jar nohup
(后台运行)执行即可。
初探原理
通过分析源码,我们可以窥探出SpringBoot的一部分原理。
pom.xml
只要是Maven项目都先分析pom.xml文件。
pom.xml
1、发现一个父依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
2、点进父依赖,查看代码分析父依赖作用:资源过滤,插件管理。
<!--发现未知文件 yml-->
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
3、仔细查看代码后发现它还有一个父依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
点进去可以发现它定义了许多依赖的版本号,说明它才是真正的版本控制中心。
这表明我们以后导入的依赖都不需要编写版本号,如果自动依赖配置中不存在,才需要手动导入依赖版本。
启动器
回到最外层的pom文件中,发现有一个spring-boot-starter-web
依赖,spring-boot-starter是SpringBoot的启动器,它的作用是:导入所有的依赖。spring-boot-starter-xxx,后面的xxx就是对应的场景依赖。本例中的spring-boot-starter-web
就是导入了Web模块运行所需要的全部场景依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
具体存在哪些spring-boot-starter呢?可以参考官网:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/html/using-spring-boot.html#using-boot-starter
插件
插件都配置在build标签下的plugins标签中。
<!--插件-->
<build>
<plugins>
<!--打包插件,可以将项目打成jar包或者war包-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
启动类
默认的启动类都被@SpringBootApplication注解所标注。
@SpringBootApplication //一个注解
public class SpringBoot01Application {
public static void main(String[] args) {
//一个方法
SpringApplication.run(SpringBoot01Application.class, args);
}
}
@SpringBootApplication
标注了此注解,就表示这个类是我们的一个启动类。点进去我们看到类上有三个注解。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan() // 对应xml 中的 ComponentScan 标签;
public @interface SpringBootApplication {
}
JavaConfig回顾
Spring4之后官方推荐我们使用 JavaConfig。
传统的applicationContext.xml配置:
<bean id="" class="">
<import >
<mvc:component-scan base-package="">
注解配置类applicationContext:
@Configuration
@import
@ComponentScan()
class applicationContext{
// 方法名就是 xml 中对应的id,返回值类型就是对应的 class
@Bean
public User uesr(){ }
}
@ComponentScan
此注解就是扫描包,将扫描包中的bean注入到Spring中。
@SpringBootConfiguration
点进这个注解可以发现,该类上标注了@Configuration注解,再点进 Configuration类,该类上标注了@Componet注解。
@Configuration // 这就是个配置类
public @interface SpringBootConfiguration { }
@Component // 这是一个组件
说明:
1、主启动类也是Spring的一个组件。
2、主启动类也是一个配置类。
@EnableAutoConfiguration(重点)
@EnableAutoConfiguration : 开启自动配置功能。
@AutoConfiurationPackae : 自动配置和注册包。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
}
AutoConfigurationImportSelector
点进@EnableAutoConfiguration,在此类上我们找到了@Import(AutoConfigurationImportSelector.class)注解,点进AutoConfigurationImportSelector.类,来分析下这个类。
首先谈谈分析一个类的方法:
1、分析构造器。
2、分析初始化常用方法。
3、如果有继承,重点分析一下被重写的方法。
通过分析源码我们可以知道,项目在加载的时候会读取配置,返回具体的结果,但是有没有生效,我们并不清楚。
整个源码分析过程如下图所示。
SpringApplication
构造器
回到启动类,点进SpringApplication类,先来看一下它的构造器。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1、推断当前引用的类型,是否是Web应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2、加载初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 3、设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 4、推断main方法
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication的作用:
1、推断当前引用的类型,是否是Web应用。
2、加载初始化器。
3、设置监听器。
4、推断main方法。
run方法
回到启动类,点进run方法,一直点进去,直到进入到如下方法。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 1、监听器启动
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 初始化环境配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印 Banner
Banner printedBanner = printBanner(environment);
// 创建上下文
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(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, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
run方法思路思考图如下图所示。
Yaml学习
配置文件
SpringBoot默认使用一个全局的配置文件, application*. 这个配置文件的名称是固定的。配置文件有properties和yaml两种文件格式:
- application.properties,语法:key = value
- application.yml,语法: key: value
配置文件的作用是修改SpringBoot自动配置的默认值,但实际上很多配置SpringBoot都已经给我们自动配置好了,我们只需要偶尔修改一些内容就可以通过这个配置文件。
yaml
yaml是一个可读性高,用来表达数据序列化的语言。这种语言以数据为中心。
数据一般包括:对象、map、list、key/value
传统的properties配置使用key=value格式,十分单一,而xml配置又比较繁琐,比如配置服务器端口用xml方式可以这样表示:
<server>
<port>8080</port>
</servet>
yaml配置的好处:数据类型支持较多,符合Java程序员的审美。SpringBoot官方推荐我们使用yaml配置。
基本语法
注意事项:
1、空格要求很严格(key和value之间必须有一个空格)。
2、缩进严格要求。
3、大小写严格要求。
key/value
key:value
字符串需要注意""和''的区别:
使用""转义字符会生效,如:
name: "coding\nlove"
结果:
coding
love
使用''转义字符不会生效,如:
name: "coding\nlove"
结果:
coding\nlove
对象、map
key:
v1: 1
v2: 2
比如:
student:
name: wunian
age: 18
happy: true
上述例子也可以缩减为行内写法,这种写法和JSON配置一样。
student: {name: wunian,age: 18,happy:true}
list、数组类型
key:
- v1
- v2
- v3
比如:
color:
- green
- red
- blue
上述例子也可以缩减为行内写法,这种写法和JSON配置一样。
color: [green,red,blue]
需要注意的是,在SpringBoot中,yaml配置和properties配置可以同时配置,简单的配置可以写在properties文件中,复杂的写在yaml文件中,这样就可以互补了。
properties配置的优先级大于yaml配置。因为yaml配置先加载,properties配置后加载,如果有相同的配置项的话properties中的配置会生效。
注入配置文件
编写配置类的步骤:
1、编写配置文件。
person:
name: wunian
age: 18
happy: true
birth: 2020/3/20
maps: {k1:v1,k2:v2}
lists:
- coder
- boy
- game
dog:
name: 大黄
age: 5
2、编写实体类。
@Data
@ConfigurationProperties(prefix = "person")
//@PropertySource(value="classpath:person.properties")
@Validated //数据校验
public class Person {
@NotNull
private String name;
@NotNull
private Integer age;
@NotNull
private Boolean happy;
@NotNull
private Date birth;
@Size(1,10)
private Map<String, Object> maps;
@NotEmpty
private List<Object> lists;
private Dog dog;
}
3、测试。
@SpringBootTest
class SpringBoot01ApplicationTests {
@Autowired
Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
@ConfigurationProperties注解的作用:
- 绑定配置文件中的对象。
- 将对象的属性值配置文件的值一一对应,然后注入。
- 这是一种批量注入的方式,更加快捷和方便,推荐使用。
配置的思想:配置文件如果存在值,就使用配置文件中的值,如果不存在,就设置为默认值或者null。
加载指定的配置文件
@PropertySource注解的作用:加载指定的配置文件,value表示要填写指定配置文件中的值,如:
@PropertySource(value="classpath:person.properties")
在配置中使用随机数、占位符
yaml 配置文件中可以使用一些随机数、占位符,如:
person:
name: wunian${random.uuid}
age: ${random.int}
happy: true
birth: 2020/3/20
maps: {k1:v1,k2:v2}
lists:
- coder
- boy
- game
dog:
#如果person.age存在值就设置值为person.age+大黄,否则设置值为默认名是大黄
name: ${person.age:默认名是}大黄
age: 5
回顾传统的Properties配置
1、properties文件一定要注意乱码问题,可以打开File-->Settings-->File Encodings
中设置编码格式。
2、测试properties配置。
@Data
@Component
@PropertySource("classpath:user.properties")
public class User {
@Value("${user1.name}") //从配置文件读取
private String name;
@Value("#{2*9}") //SPEL表达式
private Integer age;
@Value("男") //普通字符串
private String sex;
}
@Value和@ConfigurationProperties的对比
@Value和@ConfigurationProperties分别属于properties和yaml的配置方式,它们之间的区别如下表所示:
@Value | @ConfigurationProperties | |
---|---|---|
功能 | 一个个指定 | 批量注入 |
松散绑定 | 不支持 | 支持 |
SPEL | 支持 | 不支持,有自己的占位符 |
JSR303数据校验 | 不支持 | 支持 |
复杂类型封装 | 不支持 | 支持 |
JSR303数据校验
JSR是Java Specification Requests的缩写,意为Java 规范提案。是指向Java Community Process提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务,它可以验证实体类中的变量值的输入格式是否正确。
使用JSR303数据校验必须开启@Validated注解支持。
@Validated //数据校验
public class Person { }
常见参数如下:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
在SpringBoot底层所有配置类都使用的是@ConfigurationProperties注解来绑定配置文件。
最佳实践
1、如果配置类十分简单,使用@Value,在只需要获取个别值的情况下,@Value依旧最高效。
2、如果配置比较繁琐,使用@ConfigurationProperties。
多环境配置
什么是多环境配置?
真实开发的时候,配置文件一定不止一个,我们可能在开发、测试、生产环境下都有一套配置,配置文件的命名固定格式为:application-xxx
,比如:
application-dev.properties
application-test.properties
application-prod.properties
以上三个配置文件分别表示开发、测试、生产环境下的配置。
properties配置
在application.properties文件中配置需要激活的环境即可,比如激活测试环境配置如下:
# application-xxx 的命名格式!
# 选择激活那个配置文件
spring.profiles.active=test
yaml配置
yaml不需要多个配置文件,只需要多文档块,使用---分割文档块。
server:
port: 8080
spring:
profiles:
active: dev
#使用---分割文档块
---
server:
port: 8081
spring:
profiles: dev #dev环境
---
server:
port: 8082
spring:
profiles: test #test环境
---
server:
port: 8083
spring:
profiles: prod #prod环境
自动配置原理
我们可以配置的文件内容非常多,想查看所有的配置项可以参考官方文档:https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/htmlsingle/#common-application-properties
@ConditionalOnXX
@ConditionalOnXX,条件判断注解,即通过条件来判断这个类是否生效。所有的@ConditionalOnXX注解的作用如下图所示:
原理
接下来又是分析源码的时候了。
HttpEncodingAutoConfiguration类源代码分析:
// Configuration 表示这是一个配置类,和以前编写的配置文件一样!
@Configuration(proxyBeanMethods = false)
// 指定配置文件,HttpProperties 对应我们编写的配置文件,假设配置文件中有就有配置文件的,没有就用默认值
@EnableConfigurationProperties(HttpProperties.class)
// @ConditionalOnXX Spring底层注解;
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
// 如果 CharacterEncodingFilter 这个类,这个配置类才生效!
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
// 注入绑定 HttpProperties 配置文件
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
// 注入对应的bean
// 如果自己配置了配置文件,就会注入到SpringBoot自动帮我们配置的bean中!
// 注册bean的时候,SpringBoot自动帮我们关联了 HttpProperties
// 我们编写的配置就可以直接生效!
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
private static class LocaleCharsetMappingsCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final HttpProperties.Encoding properties;
LocaleCharsetMappingsCustomizer(HttpProperties.Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
总结
1、SpringBoot启动会加载大量的自动配置类,自动配置类都是在spring.factories.properties文件中注册的。
2、我们只需要判断我们的类是否存在在这里面,如果不存在我们需要手动导入,如果存在导入启动器即可。
3、我们的配置文件可以自动配置生效,依赖于两个类:
- xxxAutoConfiguation : 自动配置类,根据条件 @ConditionalOnXX 判断是否生效,如果生效则成功注入bean。
- xxxProperties:封装配置文件中的相关属性。
4、给容器中自动配置类配置属性的时候,会通过 xxxProperties 类来获取某用户配置文件中的属性,如果没有则使用默认的,如果有则使用自己配置的。
如何查看框架加载了哪些类?
只需要在配置文件中将debug设置为true即可:
debug=true
重新启动项目,我们可以看到控制台打印出了三种情况下的类:
- Positive matches(匹配成功的类)
- Negative matches(没有匹配成功的类)
- Unconditional classes(没有条件的类)
注意:虽然在启动时加载了大量的类,但是有些类并不一定生效,因为条件不满足。
自定义starter
启动器其实就是jar包。
命名规则
官方命名:
- spring-boot-starter-xxx
- spring-boot-starter-web
自己命名:
- xxx-spring-boot-starter,如:mybatis-spring-boot-starter
自己开发一个启动器
1、启动器其实是两个相互依赖的项目,一个控制版本(xxx-spring-boot-starter),一个提供服务(xxx-spring-boot-starter-autoconfigure)。
2、新建一个空项目。
3、在空项目中构建两个module,一个是starter(Maven项目),一个是autoconfigure(SpringBoot项目)。
编写启动器
1、autoconfigure中pom中导入springboot启动器。
<!--所有启动器的基础模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
2、starter中导入autoconfigure的依赖,后面会把autoconfigure打包,之后starter中什么都不用干,只要编写autoconfigure模块即可。
<dependency>
<groupId>com.wunian</groupId>
<artifactId>wunian-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
3、xxxService(具体业务)。
public class HelloService {
//注入配置类 HelloProperties
HelloProperties helloProperties;
public HelloProperties getHelloProperties() {
return helloProperties;
}
public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
//具体业务
public String sayHello(String name){
return helloProperties.getPrefix()+name+helloProperties.getSuffix();
}
}
4、xxxProperties
@ConfigurationProperties(prefix = "wunian.hello")
public class HelloProperties {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
5、xxxAutoConfiguration
@Configuration
@ConditionalOnWebApplication //只有在web项目下才会生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
@Autowired
HelloProperties helloProperties;
@Bean
public HelloService helloService(){
HelloService helloService=new HelloService();
helloService.setHelloProperties(helloProperties);
return helloService;
}
}
6、将启动类放到resources/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wunian.HelloServiceAutoConfiguration
7、将自己编写的starter和autoconfigure安装到自己本地的Maven仓库即可!命令:maven install
新建一个项目测试自己的启动器
1、新建一个SpringBoot Web项目(导入Spring Web依赖)。
2、导入自己的启动器。
<!--导入自己的启动器-->
<dependency>
<groupId>com.wunian</groupId>
<artifactId>wunian-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
3、测试启动器功能。
HelloController
//测试自定义启动器
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@RequestMapping("/hello")
public String hello(){
return helloService.sayHello("==wunian==");
}
}
application.properties
wunian.hello.prefix="Wunian"
wunian.hello.suffix=".jsp"