SpringBoot简单应用及小知识点
本文开始搭建简单的SpringBoot应用,并就涉及的部分小知识点进行描述,本文主要内容为:
参考SpringBoot官网搭建一个简单的父子工程,父工程和子工程pom依赖及版本管理,SpringBoot默认包扫描,yml文件解析和配置修改,配置文件加载顺序,外部配置文件的加载,jar或war打包运行原理;@SpringBootApplication注解和@EnableAutoConfiguration注解及@ConditionXXXX条件注解意义。
(1)SpringBoot官网搭建一个简单的父子工程:https://docs.spring.io/spring-boot/docs/2.1.14.RELEASE/reference/html/getting-started.html文档对不同版本的SpringBoot所支持的JDK,Maven,Gradle,Servlet容器的版本都做了介绍,下面我们以maven构建为例进行搭建:
a)首先创建一个maven父工程,同时新建一个maven子工程,项目结构如下:
在父pom.xml中引入所需要的的jar依赖,主要为spring-boot-starter-parent以及spring-boot-starter-web,如果使用内置Tomcat进行项目打包发布运行,则还需要引入相应的plugin插件;同时声明包含的子工程:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>myspringboot</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>springboot_1</module> </modules> <!-- Inherit defaults from Spring Boot --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> </parent> <!-- dependencies会被子工程完全引用 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!-- 子工程引用时只会继承版本号,不需要在子工程声明版本号 --> <dependencyManagement> </dependencyManagement> <!-- 以可执行jar运行所需插件 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
b)在新建的子工程pom.xml中,引入父工程的pom,同时加入子工程自己所需的依赖,注意子工程里的依赖不需要加版本号,因为已经在父工程中统一管理了(这是一个父子工程pom管理的知识点,后面会说明):
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>springboot_1</artifactId> <!-- 引入父工程 --> <parent> <artifactId>myspringboot</artifactId> <groupId>com.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <!-- Add typical dependencies for a web application --> <!-- 子工程不用加版本号,因为springboot的父pom内用dependencyManagement已经管理维护了对应适配的版本号 --> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> </dependencies> </project>
c)在子工程的代码创建相应包结构,如本例com.springboot1包,新建处理请求的控制器类(@RestController),提供服务方法:
package com.springboot1;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Example {
@RequestMapping("/")
String home() {
return "Hello World!";
}
}
d)新建SpringBoot启动类(App.class),添加@SpringBootApplication声明为启动配置类,然后运行main方法即可使用内置默认容器Tomcat发布这个应用了:
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类:放在最外层包,可默认扫描当前包及子目录下的文件,可省略ComponentScan注解
* 声明为SpringBoot工程,并开启自动配置
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
e)可以在resources/application.yml中修改相关配置如修改tomcat默认端口为9090,发布成功后在浏览器可直接访问对应服务:
以上一个最简单的SpringBoot应用就搭建完成并发布,展示了最基本的配置过程。总结就是创建maven工程-->引入SpringBoot依赖和Starter依赖,插件依赖,子工程自身需要依赖-->创建启动类-->创建各个服务发布所需的业务Controller-->按需修改yml配置文件-->启动服务并访问。
(2)父工程和子工程pom依赖及版本管理:
上面提供,子工程在引入相关自己需要的依赖如javax.servlet-api时,是不需要另外声明版本号的,这又是为什么呢?因为子工程所需依赖的版本号都已经在父工程spring-boot-starter-parent当中管理了,可以点开查看内部有哪些管理依赖:
可以看到在上面父工程引入的springboot2.0.2版本中,管理的javax.servlet-api是3.1.0版本,在子工程maven引入的这个依赖自然也就是3.1.0版本。在父pom.xml是通过<dependencyManagement>标签来管理版本号,那么和<dependencies>标签有何区别?
<dependencies>标签:父工程使用这个标签管理的依赖,那么在子工程中也会自动引入这个依赖,存在强依赖关系;
<dependencyManagement>标签:父工程使用这个标签管理的依赖,在子工程引用时只会继承对应依赖的版本号,故不需要在子工程声明版本号。
(3)SpringBoot默认包扫描:
对于SpringBoot包扫描,存在一个默认的扫描路径,那就是用@SpringBootApplication修饰的启动配置类所在包及其子包,这个是在@SpringBootApplication注解内@ComponentScan注解定义的:
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
这也就是说,为什么一般要将SpringBoot启动类默认都放在代码工程的根目录上,因为这样就可以默认扫描全部的代码目录了,而不需要在App.class启动类上再添加@ComponentScan注解来显示声明包扫描。
(4)yml文件解析和配置修改,配置文件加载顺序,外部配置文件的加载:
关于yml配置文件,有多种语法规则,例如字符,K-V键值对,json对象,数组,集合,对象,复合对象,函数嵌入转换等。解析时K-V之间以空格分开,多个K-V之间用逗号分隔:
server: port: 9090 #字符 myserver: server1 #json user: {name: 123,password: 123} #json集合 users: - {name: 123,password: 123} - {name: 456,password: 456} #对象 student: name: yang age: 20 #对象集合 dogs: - dog1 - dog2 #嵌入日期转换函数 date: 2020-06-10 spring: jackson: date-format: yyyy-MM-dd HH:mm:ss
那么怎么使用配置文件中定义的配置信息?
a)可使用@ConfigurationProperties(prefix = "xxxx")注解修饰在指定类上,这个类的属性就将配置文件中xxxx下的yml配置属性自动绑定到这个类中:
package com.testyml;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 自定义配置类获取配置文件内定义的配置信息
*/
@Component
@ConfigurationProperties(prefix = "student")
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
b)也可使用@Value注解获取:
package com.testyml;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestYmlController {
@Value("${myserver}") private String serverName;
@Autowired
private Student student;
@RequestMapping("/yml")
void test() {
System.out.println("student="+student);
System.out.println("serverName="+serverName);
}
}
访问该localhost:9090/yml会输出:
student=Student{name='yang', age=20}
serverName=server1
其中@ConfigurationProperties支持松散写法(即属性名称不强制驼峰命令,大小写,下划线等),同时还支持JSR303数据校验:JSR303是一个校验规范,用于专门检查值是否满足指定条件,包括数值,日期,邮箱,字符串格式等等校验,这些可以在实体类上通过@Validated注解声明支持JSR303规范,然后在类的属性中就可以使用这些JSR303所提供的各种校验注解如@Email,@NotNull等
那么如果属性不通过校验,就会在启动时出现对应报错信息:
@Value支持SpEL表达式写法,如@Value{#{${b}+${c}}}等,${XXX}是去配置文件找XXX属性的值,而#{}是去识别一个SpEL表达式(如加减乘除运算,三元表达式等)。
对于外部yml配置文件,要怎么加载呢?
对于外部yml配置文件,可以通过外部配置类用@PropertySource("classpath:外部.yml") 注解进行加载,然后在这个外部配置类的属性内通过@Value获取外部配置文件对应的配置信息:
配置文件加载顺序?
在SpringBoot项目中,yml 和 properties 文件是一样的原理,且一个项目一般上要么 yml 或者 properties,推荐使用 yml,更简洁。
一、bootstrap ,yml ,properties加载顺序上的区别
SpringBoot中有以下两种配置文件bootstrap (.yml 或者 .properties),application (.yml 或者 .properties)
(1)bootstrap.yml(bootstrap.properties)先加载
(2)application.yml(application.properties)后加载
(3)同一种类型的.yml的配置文件加载顺序优先于.properties的配置文件
bootstrap.yml 用于应用程序上下文的引导阶段。
bootstrap.yml 由父 Spring ApplicationContext加载。
父 ApplicationContext 在 application.yml 之前被加载。
bootstrap.yml 和 application.yml 都可以用来配置参数。
-
bootstrap.yml 可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。
-
application.yml 可以用来定义应用级别的,如果搭配 spring-cloud-config 使用 application.yml 里面定义的文件可以实现动态替换。
总结:bootstrap.yml 文件里面放系统级别的配置,基本不会发生改变,并且在使用 spring cloud 的时候,一定要把 config server 的属性配置到 bootstrap.yml 文件里面,因为 bootstrap.yml 加载顺序优先于 application.yml 文件,这样的话,就可以在 application.yml 加载之前加载远程的配置,然后去覆盖 application.yml (如果有)中的配置;然后 application.yml 配置文件中的配置就是应用的自动化配置了;
所以,如果项目中的配置比较多的时候建议 bootstrap.yml 和 application.yml 这两个配置文件一起使用最好;
如果项目中的配置比较少,就建议直接使用 application.yml 一个配置文件就够了;
(5)jar或war打包运行原理:
在工程内运行,可使用mvn spring-boot:run命令运行,但是一般是打包成可运行jar或war的方式进行运行。
对于打成可运行jar,则需要引入下面spring-boot-maven-plugin的插件依赖协助打成jar:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin>
生成的jar文件后,如果要在外部运行可以通过jar -cvf xxx.jar运行;
对于打成war运行方式,则是在启动类App.class继承SpringBootServletInitializer,并重写configure方法:
@SpringBootApplication public class App extends SpringBootServletInitializer { //以war包启动时,启动类继承SpringBootServletInitializer并重写configure, @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return super.configure(builder).sources(App.class); }
public static void main(String[] args) { SpringApplication.run(App.class); } }
(6)@SpringBootApplication注解和@EnableAutoConfiguration注解及@ConditionXXXX条件注解意义:
@SpringBootApplication和@EnableAutoConfiguration两个注解分别是声明为配置启动类和自动配置的核心注解,其中@SpringBootApplication包括了@EnableAutoConfiguration的功能,本节不做讨论放在自动配置原理中讨论;
此外SpringBoot内部提供了特有的注解:条件注解(Conditional Annotation)。
比如@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnExpression、@ConditionalOnMissingBean等。
条件注解存在的意义在于动态识别(也可以说是代码自动化执行)。比如@ConditionalOnClass会检查类加载器中是否存在对应的类,如果有的话被注解修饰的类就有资格被Spring容器注册,否则会被忽略不注册。
基于Class的条件注解
SpringBoot提供了两个基于Class的条件注解,对应的条件类是OnClassCondition:
@ConditionalOnClass(类加载器中存在指明的类)或者@ConditionalOnMissingClass(类加载器中不存在指明的类)。
被这两个注解修饰的类,就要先判断指定的类是不是满足对应条件(存在,或不存在),当成立时这个修饰类才会被Spring容器加载注册。
基于Bean的条件注解
基于Bean的条件注解,它们对应的条件类是ConditionOnBean:
@ConditionalOnBean(Spring容器中存在指明的bean)
@ConditionalOnMissingBean(Spring容器中不存在指明的bean)
@ConditionalOnSingleCandidate(Spring容器中存在且只存在一个指明的bean)
SpringBoot还提供了其他比如ConditionalOnJava、ConditionalOnNotWebApplication、ConditionalOnWebApplication、ConditionalOnResource、ConditionalOnProperty、ConditionalOnExpression等条件注解。
各种条件注解的总结
条件注解 | 对应的Condition处理类 | 处理逻辑 |
---|---|---|
@ConditionalOnBean | OnBeanCondition | Spring容器中是否存在对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找 |
@ConditionalOnClass | OnClassCondition | 类加载器中是否存在对应的类。可以通过Class指定(value属性)或者Class的全名指定(name属性)。如果是多个类或者多个类名的话,关系是”与”关系,也就是说这些类或者类名都必须同时在类加载器中存在 |
@ConditionalOnExpression | OnExpressionCondition | 判断SpEL 表达式是否成立 |
@ConditionalOnJava | OnJavaCondition | 指定Java版本是否符合要求。内部有2个属性value和range。value表示一个枚举的Java版本,range表示比这个老或者新于等于指定的Java版本(默认是新于等于)。内部会基于某些jdk版本特有的类去类加载器中查询,比如如果是jdk9,类加载器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,类加载器中需要存在java.util.function.Function;如果是jdk7,类加载器中需要存在java.nio.file.Files;如果是jdk6,类加载器中需要存在java.util.ServiceLoader |
@ConditionalOnMissingBean | OnBeanCondition | Spring容器中是否缺少对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找。还多了2个属性ignored(类名)和ignoredType(类名),匹配的过程中会忽略这些bean |
@ConditionalOnMissingClass | OnClassCondition | 跟ConditionalOnClass的处理逻辑一样,只是条件相反,在类加载器中不存在对应的类 |
@ConditionalOnNotWebApplication | OnWebApplicationCondition | 应用程序是否是非Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等 |
@ConditionalOnProperty | OnPropertyCondition | 应用环境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing属性。prefix表示属性名的前缀,name是属性名,havingValue是具体的属性值,matchIfMissing是个boolean值,如果属性不存在,这个matchIfMissing为true的话,会继续验证下去,否则属性不存在的话直接就相当于匹配不成功 |
@ConditionalOnResource | OnResourceCondition | 是否存在指定的资源文件。只有一个属性resources,是个String数组。会从类加载器中去查询对应的资源文件是否存在 |
@ConditionalOnSingleCandidate | OnBeanCondition | Spring容器中是否存在且只存在一个对应的实例。只有3个属性value、type、search。跟ConditionalOnBean中的这3种属性值意义一样 |
@ConditionalOnWebApplication | OnWebApplicationCondition | 应用程序是否是Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等 |
这个概念非常重要,因为不管在实际应用还是在源码解析中这个概念有大量使用,因此必须掌握,关于详细说明和条件注解的源码,**机制,可以参考下面作者的语雀链接(本篇关于这个条件注解的内容也是转自下面语雀链接):
https://fangjian0423.github.io/2017/05/16/springboot-condition-annotation/