文章目录
- [1 前言](#1 前言)
-
- [1.1 内容概要](#1.1 内容概要)
- [1.2 前置知识准备](#1.2 前置知识准备)
- [2 SpringBoot介绍](#2 SpringBoot介绍)
- [3 创建一个SpringBoot应用](#3 创建一个SpringBoot应用)
-
- [3.1 官网](#3.1 官网)
- [3.2 IDEA](#3.2 IDEA)
- [3.3 pom.xml文件](#3.3 pom.xml文件)
-
- [3.3.1 parent标签](#3.3.1 parent标签)
- [3.3.2 starter依赖](#3.3.2 starter依赖)
- [4 整合SpringMVC](#4 整合SpringMVC)
- [5 整合MyBatis](#5 整合MyBatis)
- [6 约定大于配置原理](#6 约定大于配置原理)
-
- [6.1 注解](#6.1 注解)
- [6.2 自动配置类](#6.2 自动配置类)
-
- [6.2.1 文件](#6.2.1 文件)
- [6.2.2 加载过程](#6.2.2 加载过程)
- [6.2.3 自动配置类](#6.2.3 自动配置类)
- [6.3 配置文件配置项](#6.3 配置文件配置项)
1 前言
1.1 内容概要
- 掌握IDEA中创建SpringBoot应用程序
- 理解SpringBoot中key=value形式配置文件
- 理解SpringBoot应用中的starter依赖功能
- 理解SpringBoot约定大于配置原理
- 熟悉搭建SpringBoot应用后SpringMVC和MyBatis的使用
1.2 前置知识准备
- Spring配置类注册组件
- SpringMVC配置类和WebMvcConfigurer接口
- maven父工程中的dependencyManagement标签
- SpringMVC静态资源映射配置
2 SpringBoot介绍
Spring阶段最困扰大家的事情是什么? 配置
- 快速搭建一个独立的生产级别的Spring应用
- 快速引入项目相关依赖
- 开箱即用,约定大于配置,大多数应用只需要极少的Spring配置
- 内置JavaEE容器,可以以Jar包的方式启动
核心点约定大于配置
提供一些约定项(其实就是默认值),在应用程序启动过程中,向容器中注册默认组件
3 创建一个SpringBoot应用
3.1 官网
start.spring.io选择groupId、ArtifactId、版本号、扫描包、JDK版本、项目构建方式、开发语言、引入的其他依赖来创建SpringBoot应用,点击Generate会下载一个zip压缩包,解压开就是一个SpringBoot应用,同时也是一个Maven工程
解压后会包含这样的文件,包含src目录、pom.xml文件、帮助文档、Git忽略管理配置文件、Maven相关文件
3.2 IDEA
其实需要的配置项和在官网上创建是完全一致的,只不过选择是在IDEA中选择,另外可以选择Project和Module的路径。
新建一个新的Project,其中starter service URL就是Spring官网创建SpringBoot应用的链接
选择基本的配置项
选择依赖和SpringBoot应用的版本
配置Project和Module路径
选择Finish的话,就会在对应的目录创建文件夹,并且将下载下来的zip压缩包解压到指定目录下,通过在IDEA中打开对应的应用。
3.3 pom.xml文件
3.3.1 parent标签
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
父工程
所有的SpringBoot应用都有这样的一个父工程,parent标签里有一个version标签,SpringBoot应用的版本号;修改version标签里的值就是修改使用的使用SpringBoot版本
使用父工程的话,可以共享父工程里的配置 → 相同配置的解耦
父工程打包方式是pom,可以在本地仓库里找到这个文件
父工程中的标签
- dependencies → 子工程里会引用父工程里依赖,SpringBoot应用中其实没有用到这个标签
- dependencyManagement → 写的dependency标签的写法和我们前面的写法是一致的 → 提供的是依赖的版本信息,如果父工程中写了一个依赖,而子工程中也写了相同的依赖(groupid和artifactId一致)
- 如果子工程中的依赖没有写版本号,复用父工程中的版本号
- 如果子工程中的依赖写了版本号,使用自定义的这个版本号
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
和我们之前写的dependency标签不一样的点 → 没有写版本号
但实际上有版本信息 → 父工程(的父工程)中的dependencyManagement中的来
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.7</version>
</parent>
当前SpringBoot有一个爷爷工程,这个爷爷工程就是专门管理依赖的版本信息的
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.7</version>
</dependency>
</dependencies>
</dependencyManagement>
这是不是约定大于配置?是
在引入一个依赖 mysql-connector-java
-
不写版本号的情况下,版本信息是什么?
xml<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
xml<properties> <mysql.version>8.0.31</mysql.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> <exclusions> <exclusion> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement>
-
写了版本号的情况下,版本信息是什么?
xml<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
提供默认的版本号信息给我们带来什么好处?
开发方便一些,兼容性
3.3.2 starter依赖
引入的依赖中,artifactid中有一个starter这样的一个词,这样的依赖就叫其starter依赖
- spring-boot-starter SpringBoot本身的依赖,所有的SpringBoot应用都有这个依赖
- spring-boot-starter-xxx SpringBoot官方提供的依赖(groupid → org.springframework.boot),提供的是SpringBoot对xxx技术的支持
- 比如spring-boot-starter-web 就是SpringBoot对web技术的支持
- 比如spring-boot-starter-tomcat就是SpringBoot对Tomcat的支持
- 比如spring-boot-starter-json 就是SpringBoot对Json的支持
- xxx-spring-boot-starter 第三方框架提供的依赖,提供的是SpringBoot对xxx技术的支持
- 比如mybatis-spring-boot-starter,SpringBoot对MyBatis技术的支持
- 比如pagehelper-spring-boot-starter
通常在SpringBoot中要使用某一项技术,只需要引入其starter依赖就可以了
为什么引入其starter依赖就可以了?
- starter依赖中关联了其他依赖,当我们引入starter依赖的时候,会将该技术所需要的其他的依赖一同引入进来
- 举个例子:使用mybatis的话,引入mybatis-spring-boot-starter,mybatis、mybatis-spring、spring-jdbc都会被引入进来
- starter依赖中通常会包含另外一个依赖autoconfigure依赖
- autoconfigure依赖能够帮我们做自动配置,自动配置里最主要的是自动注册默认的组件
4 整合SpringMVC
spring-boot-starter-web
4.1 整合配置类
java
@ComponentScan("com.cskaoyan.controller")
@EnableWebMvc
public class MvcConfiguration implements WebMvcConfigurer{
}
// SpringMVC阶段我们写的配置类
配置类在SpringBoot阶段是可以使用的,但是有些内容产生了变化
- 不需要写扫描包目录了 → springboot提供的默认的扫描包目录:启动类所在的包目录
- 在配置类上增加@EnableWebMvc或@Configuration
- 如果使用@EnableWebMvc意味着全面接管SpringMVC的相关配置,默认配置失效
- 如果使用**@Configuration**意味着做的是配置项的补充 → 建议使用
4.2 静态资源处理
WebMvcConfigurer接口中的addResourceHandlers(registry)
registry.addResourceHandler("映射范围").addResourceLocations("静态资源所处的位置")
上面这种方式可以在配置类中使用
SpringBoot也给我们做了默认的配置
- mapping映射范围:/**
- location资源所处的位置:classpath:/public/、classpath:/static/、classpath:/META-INF/等
SpringBoot给我们提供了默认配置使用的是默认值;我们仍然可以使用其默认的配置,我们可以指定自定义的值
在SpringBoot的配置文件中可以提供指定的值
配置文件是properties → key=value
我们通过指定的key提供value,SpringBoot可以自动读取这些key对应的值
properties
spring.web.resources.static-locations=file:d:/tmp/
spring.mvc.static-path-pattern=/pic/**
静态资源处理
- 啥都不做采用默认值
- 配置文件中按照指定的key来提供对应的值
- 也可以写配置类
4.3 Filter
前面做Javaconfig的时候是在AACDSI,在SpringBoot应用中只需要注册到容器中就生效
有什么好处?
配置起来方便;也可以使用容器中的其他组件
Filter这么方便HandlerInterceptor应该也挺方便的吧?
还是配置类的配置方式 → addInterceptors方法
4.4 Tomcat配置
端口号:server.port
上下文路径:server.servlet.context-path
properties
#Tomcat配置
server.servlet.context-path=/demo2
server.port=8090
其他配置
- 配置类
- 配置文件
- prefix为:spring.web
- prefix为:spring.mvc
- 2早期的一些SpringBoot版本:spring.resources → 它现在变了 spring.web.resources
小结
spring-boot-starter-web
静态资源处理配置 → 尤其关注location要写file路径
端口号配置
5 整合MyBatis
mybatis-spring-boot-starter
xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
应用程序中引入mybatis-spring-boot-starter这个依赖,启动程序的时候报错了
先思考一个问题:starter依赖的功能是什么?
- 引入mybatis这项技术所需要的依赖
- spring-boot-autoconfigure依赖 → 自动配置
刚刚报错的原因就是因为自动配置;自动配置MyBatis过程中需要注册一些组件,这些组件会被自动注册,其中有一个组件DataSource → datasource.set值的时候,发现你没有给他提供值
解决这个问题,提供数据源的值就行
properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
SpringBoot会根据这些默认向容器中注册DataSource组件:Hikari
如果要修改数据源的类型:spring.datasource.type
properties
# 将数据源修改为了Druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
还需要提供:Mapper接口的包目录
@MapperScan → 写在启动类上就行
java
@SpringBootApplication
@MapperScan("com.cskaoyan.mapper")
public class Demo3MybatisApplication {}
注意一个点:写Mapper包目录的时候,就写Mapper接口的包目录;写com.cskaoyan好不好,行不行 ?不行
如果要做额外的配置:prefix为mybatis
yaml
mybatis:
type-handlers-package: com.cskaoyan.typehandler
configuration:
cache-enabled: true
lazy-loading-enabled: true
6 约定大于配置原理
SpringBoot实现约定大于配置主要做的事情是帮我们注册一些默认的组件,而默认的组件是自动自动配置类来进行配置的
那么SpringBoot应用加载哪一些自动配置类呢,主要加载的使用autoconfigure依赖中的/META-INF/spring.factories文件里提供了自动配置类的列表
通过org.springframework.boot.autoconfigure.EnableAutoConfiguration这个key找到对应的value就是自动配置类的列表
properties
# /META-INF/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
# 截取了其中一部分
6.1 注解
@ConditionalOnXXX → 满足XXX条件其他的注解生效
@ConditionalOnMissing → 满足缺少XXX条件,其他的注解生效
6.2 自动配置类
6.2.1 文件
配置类信息:
autoconfigure依赖/META-INF文件夹
- spring.factories → key "xxxAutoConfiguration" 对应的值是字符串的列表,这些字符串是 自动配置类的全限定类名
- spring文件夹/文件(文件名很长) → 这个文件里面的值 字符串的列表,这些字符串是 自动配置类的全限定类名
properties
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
6.2.2 加载过程
首先来看启动类,启动类上包含了@SpringBootApplication注解
java
@SpringBootApplication
@MapperScan("com.cskaoyan.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在@SpringBootApplication注解中包含了@EnableAutoConfiguration注解
java
@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 {
在@EnableAutoConfiguration中包含了@Import({AutoConfigurationImportSelector.class}),通过Selector选择器找到对应的自动配置类
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
在AutoConfigurationImportSelector中包含了selectImports方法
java
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
然后再看selectImports方法里的this.getAutoConfigurationEntry(annotationMetadata),在该方法中包含这样的一行代码,获得配置类的字符串的List
java
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//这一行就是获得配置类(全限定类名)的List信息
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
进入到getCandidateConfigurations方法中
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 加载/META-INF/spring.factories文件中的自动配置类
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
// 加载/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的自动配置类
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
要看/META-INF/spring.factories接下来进入到loadFactoryNames这个方法中
java
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
最后来看loadSpringFactories这个方法,这个方法的返回值为Map,这个Map的key为字符串,value为字符串列表List,这个key其实就是需要EnableAutoConfiguration,value就是自动配置类的字符串List信息
java
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
///META-INF/spring.factories
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//略掉很多代码
}
}
}
到这里大家其实可以看到最终加载的就是/META-INF/spring.factories文件
要看/META-INF/spring/xxx.imports
java
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
///META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
String location = String.format("META-INF/spring/%s.imports", annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
ArrayList importCandidates = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}
6.2.3 自动配置类
加载自动配置类,并且配置的生效是有条件的,包含@ConditionalOnXXX和@ConditionalOnMissingXXX这样的注解,满足一定的条件时生效和不满足一定的条件的时候生效
比如@ConditionalOnClass也就是包含对应的类的时候生效(也就是导包以后生效)
@ConditionalOnMissingBean当没有某个组件的时候生效,生效就会导致注册一个默认组件;如果自行注册组件,那么这个默认组件失效;其实这个就是约定大于配置
java
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({JdbcTemplate.class, TransactionManager.class})
@AutoConfigureOrder(2147483647)
@EnableConfigurationProperties({DataSourceProperties.class})
public class DataSourceTransactionManagerAutoConfiguration {
public DataSourceTransactionManagerAutoConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnSingleCandidate(DataSource.class)
static class JdbcTransactionManagerConfiguration {
JdbcTransactionManagerConfiguration() {
}
@Bean
@ConditionalOnMissingBean({TransactionManager.class})
DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = this.createTransactionManager(environment, dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> {
customizers.customize(transactionManager);
});
return transactionManager;
}
private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
return (DataSourceTransactionManager)((Boolean)environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE) ? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource));
}
}
}
6.3 配置文件配置项
都在autoconfigure依赖中的/META-INF路径下,包含了(additional-)spring-configuration-metadata.json文件,这个文件中包含了你可以做的配置项有哪些,并且这些配置的描述、默认值、值的格式等
值的格式
json
{
"name": "spring.web.resources.static-locations",
"type": "java.lang.String[]",
"description": "Locations of static resources. Defaults to classpath:[\/META-INF\/resources\/, \/resources\/, \/static\/, \/public\/].",
"sourceType": "org.springframework.boot.autoconfigure.web.WebProperties$Resources",
"defaultValue": [
"classpath:\/META-INF\/resources\/",
"classpath:\/resources\/",
"classpath:\/static\/",
"classpath:\/public\/"
]
}
但是呢,让你写json文件,是为难你,引入一个依赖,这个依赖会帮我们新增对应的json
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
rerun你的应用程序,重新启动一下
如果没有生成:在resources目录下新增一个文件夹 META-INF