SpringBoot

文章目录

  • [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)
    • [4.1 整合配置类](#4.1 整合配置类)
    • [4.2 静态资源处理](#4.2 静态资源处理)
    • [4.3 Filter](#4.3 Filter)
    • [4.4 Tomcat配置](#4.4 Tomcat配置)
    • 其他配置
    • 小结
  • [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 内容概要

  1. 掌握IDEA中创建SpringBoot应用程序
  2. 理解SpringBoot中key=value形式配置文件
  3. 理解SpringBoot应用中的starter依赖功能
  4. 理解SpringBoot约定大于配置原理
  5. 熟悉搭建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

相关推荐
無限進步D5 小时前
Java 运行原理
java·开发语言·入门
難釋懷5 小时前
安装Canal
java
是苏浙5 小时前
JDK17新增特性
java·开发语言
不光头强5 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp8 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多8 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood8 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员9 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai