一、SpringBoot的特点
Spring Boot 主要目标是:
- 为所有 Spring 的开发者提供一个非常快速的、广泛接受的入门体验
- 开箱即用(启动器starter-其实就是SpringBoot提供的一个jar包),但通过自己设置参数(.properties),即可快速摆脱这种方式。
- 提供了一些大型项目中常见的非功能性特性,如内嵌服务器、安全、指标,健康检测、外部化配置等
- 绝对没有代码生成,也无需 XML 配置。
更多细节,可以到官网查看。
二、 SpringBoot的核心功能
-
起步依赖
起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。
简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。
-
自动配置
Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。
三、SpringBoot原理分析
起步依赖原理分析
分析spring-boot-starter-parent
按住Ctrl点击pom.xml中的spring-boot-starter-parent,跳转到了spring-boot-starter-parent的pom.xml,xml配置如下(只摘抄了部分重点配置):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
按住Ctrl点击pom.xml中的spring-boot-starter-dependencies,跳转到了spring-boot-starter-dependencies的pom.xml,xml配置如下(只摘抄了部分重点配置):
<properties>
<activemq.version>5.15.11</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.77</appengine-sdk.version>
<artemis.version>2.10.1</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.13.2</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.1</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
... ... ...
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
... ... ...
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon-maven-plugin</artifactId>
<version>${johnzon.version}</version>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
</plugin>
... ... ...
</plugins>
</pluginManagement>
</build>
从上面的spring-boot-starter-dependencies的pom.xml中可以发现,一部分坐标的版本、依赖管理、插件管理已经定义好,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了。所以起步依赖的作用就是进行依赖的传递。spring-boot-starter-web分析源码也是一样,可自行查看
四、自动配置原理解析
按住Ctrl点击查看启动类MySpringBootApplication上的注解@SpringBootApplication
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class);
}
}
@SpringBootApplication的源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
.....
}
-
@SpringBootConfiguration:等同与@Configuration,既标注该类是Spring的一个配置类
-
@EnableAutoConfiguration:SpringBoot自动配置功能开启
@EnableAutoConfiguration源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
... ... ...
}
- @Import(AutoConfigurationImportSelector.class) 导入了AutoConfigurationImportSelector类
按住Ctrl点击查看AutoConfigurationImportSelector源码
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
其中,**SpringFactoriesLoader.loadFactoryNames **方法的作用就是从META-INF/spring.factories文件中读取指定类对应的类名称列表
spring.factories 文件中有关自动配置的配置信息如下:
... ... ...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
... ... ...
上面配置文件存在大量的以Configuration为结尾的类名称,这些类就是存有自动配置信息的类,而SpringApplication在获取这些类名后再加载
- 源码示例
以ServletWebServerFactoryAutoConfiguration为例来分析源码:
//声明这个类是一个配置类
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
//判断你是否引入了相关依赖,引入依赖后该条件成立,当前类的配置才会生效
@ConditionalOnClass(ServletRequest.class)
//满足项目的类是是Type.SERVLET类型,也就是一个普通web工程
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
... ... ...
}
@EnableConfigurationProperties(ServerProperties.class) 代表加载ServerProperties服务器配置属性类
进入ServerProperties.class源码如下:
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind.
*/
private InetAddress address;
... ... ...
}
prefix = “server” 表示SpringBoot配置文件中的前缀,SpringBoot会将配置文件中以server开始的属性映射到该类的字段中。
@ComponentScan
源码翻译:
配置组件扫描的指令。提供了类似与
<context:component-scan>
标签的作用通过basePackageClasses或者basePackages属性来指定要扫描的包。如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描包及子包
而@SpringBootApplication注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中。
五、SpringBoot的配置文件
SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置。SpringBoot默认会从Resources目录下加载application.properties或application.yml(application.yaml)文件。
application.yml配置文件
语法示例
普通数据:
-
语法: key: value
-
示例代码:
-
name: test
-
注意:value之前有一个空格
对象数据:
-
语法:
key:
key1: value1
key2: value2
或者:
key: {key1: value1,key2: value2}
-
示例代码:
-
person: name: test age: 18 addr: shenzhen #或者 person: {name: test,age: 18,addr: shenzhen}
-
注意:key1前面的空格个数不限定,在yml语法中,相同缩进代表同一个级别
Map数据 :
同上面的对象写法
数组(List、Set)数据:
-
语法:
key:
- value1
- value2
或者:
key: [value1,value2]
-
示例代码:
-
city: - shenzhen - shanghai - shandong - chongqing #或者 city: [shenzhen,shanghai,shandong,chongqing] #集合中的元素是对象形式 student: - name: zhangsan age: 18 score: 100 - name: lisi age: 28 score: 88 - name: wangwu age: 38 score: 90
-
注意:value1与之间的 - 之间存在一个空格
SpringBoot多环境配置
在实际开发的过程中,项目会经历很多的阶段(开发->测试->上线),每个阶段的配置也会不同,例如:端口、上下文根、数据库等,那么这个时候为了方便在不同的环境之间切换,SpringBoot提供了多环境配置,简单示例如下:
application-dev.properties
#开发环境
#设置内嵌Tomcat默认端口号
server.port=8080
application-product.properties
#生产环境
#配置内嵌Tomcat默认端口号
server.port=80
application-test.properties
#测试环境
#配置内嵌Tomcat端口号
server.port=8081
● 在总配置文件application.properties进行环境的激活
#SpringBoot的总配置文件
#激活开发环境
#spring.profiles.active=dev
#激活测试环境
#spring.profiles.active=test
#激活生产环境
spring.profiles.active=product
六、 SpringBoot配置信息的查询
SpringBoot的官方文档:https://docs.spring.io/spring-boot/docs/2.0.1.RELEASE/reference/htmlsingle/#common-application-properties
例如:
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
server.port=8080 # Server HTTP port.
server.servlet.context-path= # Context path of the application.
server.servlet.path=/ # Path of the main dispatcher servlet.
那么可以通过配置application.poperties 或者 application.yml 来修改SpringBoot的默认配置
例如:
application.yml文件
server:
port: 6666
七、springboot读取配置文件方式
@PropertySource配合@value使用
引入Druid连接池依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
创建一个jdbc.properties文件,编写jdbc属性:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test
jdbc.username=root
jdbc.password=root
然后编写代码:
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.url}")
String url;
@Value("${jdbc.driverClassName}")
String driverClassName;
@Value("${jdbc.username}")
String username;
@Value("${jdbc.password}")
String password;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
解读:
-
@Configuration
:声明我们JdbcConfig
是一个配置类 -
@PropertySource
:指定属性文件的路径是:classpath:jdbc.properties
若此例jdbc的属性放在application.yml,那么可以不使用
@PropertySource
,@Value
默认读取; -
通过
@Value
为属性注入值 -
通过@Bean将
dataSource()
方法声明为一个注册Bean的方法,Spring会自动调用该方法,将方法的返回值加入Spring容器中。
@ConfigurationProperties使用
在上面的案例中,我们实验了java配置方式。不过属性注入使用的是@Value注解。这种方式虽然可行,但是不够强大,因为它只能注入基本类型值。
在SpringBoot中,提供了一种新的属性注入方式,支持各种java基本数据类型及复杂类型的注入。
新建一个类,用来进行属性注入:
@ConfigurationProperties(prefix = "jdbc")
public class JdbcProperties {
private String url;
private String driverClassName;
private String username;
private String password;
// ... 略
// getters 和 setters
}
-
在类上通过@ConfigurationProperties注解声明当前类为属性读取类
-
prefix="jdbc"
读取属性文件中,前缀为jdbc的值。 -
在类上定义各个属性,名称必须与属性文件中
jdbc.
后面部分一致 -
需要注意的是,这里并没有指定属性文件的地址,所以我们需要把jdbc.properties名称改为application.properties,这是SpringBoot默认读取的属性文件名:
在JdbcConfig中使用这个属性:
@Configuration
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfig {
//@Autowired注入
//private JdbcProperties prop;
//构造函数注入
//private JdbcProperties prop;
//public JdbcConfig(Jdbcproperties prop){
//this.prop = prop;}
//声明有@Bean的方法参数注入
@Bean
public DataSource dataSource(JdbcProperties jdbc) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(jdbc.getUrl());
dataSource.setDriverClassName(jdbc.getDriverClassName());
dataSource.setUsername(jdbc.getUsername());
dataSource.setPassword(jdbc.getPassword());
return dataSource;
}
}
- 通过
@EnableConfigurationProperties(JdbcProperties.class)
来声明要使用JdbcProperties
这个类的对象 - 然后使用@Autowired注入或构造函数注入或声明有@Bean的方法参数注入
@ConfigurationProperties更优雅的注入
如果一些属性只有一个Bean需要使用,那就无需将其注入到一个类(JdbcProperties)中。而是直接在需要的地方声明即可:
@Configuration
public class JdbcConfig {
@Bean
// 声明要注入的属性前缀,SpringBoot会自动把相关属性通过set方法注入到DataSource中,前提是该类必须有对应属性的set方法!
@ConfigurationProperties(prefix = "jdbc")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
}
直接把@ConfigurationProperties(prefix = "jdbc")
声明在需要使用的@Bean
的方法上,然后SpringBoot就会自动调用这个Bean(此处是DataSource)的set方法,然后完成注入。使用的前提是:该类必须有对应属性的set方法!
八、SpringBoot事务管理
SpringBoot 使用事务非常简单,底层依然采用的是Spring本身提供的事务管理
• 在入口类中使用注解 @EnableTransactionManagement 开启事务支持
• 在访问数据库的Service方法上添加注解 @Transactional 即可
例如
@SpringBootApplication
@EnableTransactionManagement //SpringBoot开启事务的支持
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Override
@Transactional //添加此注解说明该方法添加的事务管理
public int update(Student student) {
int updateCount = studentMapper.updateByPrimaryKeySelective(student);
System.out.println("更新结果:" + updateCount);
//在此构造一个除数为0的异常,测试事务是否起作用
int a = 10/0;
return updateCount;
}
九、SpringBoot打包方式
SpringBoot打war包部署
1.程序入口类需扩展继承 SpringBootServletInitializer类并覆盖configure方法(这是使用外置tomcat打包)
@SpringBootApplication
public class Application extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
//参数为当前Spring Boot启动类Application.class
return builder.sources(Application.class);
}
}
2.在 pom.xml中添加(修改)打包方式为war
<packaging>war</packaging>
3.在 pom.xml中配置springboot打包的插件(默认自动加)
<!--SpringBoot 的打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
4.在pom.xml中配置将配置文件编译到类路径
<!--mybatis的mapper.xml-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<!--src/main/resources下的所有配置文件编译到classes下面去-->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<!--源文件位置-->
<directory>src/main/webapp</directory>
<!--编译到META-INF/resources,该目录不能随便写-->
<targetPath>META-INF/resources</targetPath>
<includes>
<!--要把哪些文件编译过去,**表示webapp目录及子目录,*.*表示所有-->
<include>**/*.*</include>
</includes>
</resource>
5.在pom.xml的build标签下通过finalName指定打war包的名字
<!--指定打war包的名字-->
<finalName>JavaDemo-springboot-war</finalName>
6.通过Maven package命令打war包到target目录下
7、将target目录下生成的war包拷贝到tomcat的webapps目录,并启动tomcat,大功告成!
SpringBoot打Jar包部署
1.在 pom.xml中添加(修改)打包方式为war
<packaging>war</packaging>
2.在 pom.xml中配置springboot打包的插件(默认自动加)
<!--SpringBoot 的打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
3、通过maven- package命令打包
4、通过java -jar jar包名称命令运行
十、定时任务@Scheduled注解使用
cron表达式语法
[秒] [分] [小时] [日] [月] [周] [年]
注:[年]不是必须的域,可以省略[年],则一共6个域
通配符说明:
*
表示所有值。 例如:在分的字段上设置 *,表示每一分钟都会触发。?
表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为”?” 具体设置为 0 0 0 10 * ?-
表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发。,
表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发/
用于递增触发。如在秒上面设置”5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置’1/3’所示每月1号开始,每隔三天触发一次。L
表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于”7”或”SAT”。如果在”L”前加上数字,则表示该数据的最后一个。例如在周字段上设置”6L”这样的格式,则表示“本月最后一个星期五”W
表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上置”15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“)。#
序号(表示每月的第几个周几),例如在周字段上设置”6#3”表示在每月的第三个周六.注意如果指定”#5”,正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;小提示:’L’和 ‘W’可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同。
示例
在启动类开启计划任务
@EnableScheduling//开启计划任务 表达式参考http://cron.qqe2.com/
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
服务类
//@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
//该注解的方法在整个Bean初始化中的执行顺序:
//Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
@PostConstruct
@Override
public void init() {
initAccessToken();
}
/**
* 初始化AccessToken 一小时执行一次
*/
@Scheduled(cron = "0 0 0/1 * * ?")
@Override
public void initAccessToken() {
log.error("<====初始化 AccessToken 一分钟一次 !==>");
try {
WxParament.ACCESS_TOKEN = GetToken.getAccessToken(ymlParament.getH_app_id(), ymlParament.getH_app_secret());
} catch (Exception e) {
log.error("<====initAccessToken初始化失败!==>" + e);
e.printStackTrace();
}
log.info("<====初始化initAccessToken成功,值为==>" + WxParament.ACCESS_TOKEN);
}