SpringBoot

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标签

<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一致)
    • 如果子工程中的依赖没有写版本号,复用父工程中的版本号
    • 如果子工程中的依赖写了版本号,使用自定义的这个版本号
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

和我们之前写的dependency标签不一样的点 → 没有写版本号

但实际上有版本信息 → 父工程(的父工程)中的dependencyManagement中的来

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.7.7</version>
</parent>

当前SpringBoot有一个爷爷工程,这个爷爷工程就是专门管理依赖的版本信息的

<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

  • 不写版本号的情况下,版本信息是什么?

    • <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
      </dependency>
      
    • 在这里插入图片描述

    • <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>
      
  • 写了版本号的情况下,版本信息是什么?

    • <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 整合配置类

@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对应的值

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

在这里插入图片描述

#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

<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值的时候,发现你没有给他提供值

解决这个问题,提供数据源的值就行

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

# 将数据源修改为了Druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

还需要提供:Mapper接口的包目录

@MapperScan → 写在启动类上就行

@SpringBootApplication
@MapperScan("com.cskaoyan.mapper")
public class Demo3MybatisApplication {}

注意一个点:写Mapper包目录的时候,就写Mapper接口的包目录;写com.cskaoyan好不好,行不行 ?不行

如果要做额外的配置:prefix为mybatis

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就是自动配置类的列表

# /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文件夹/文件(文件名很长) → 这个文件里面的值 字符串的列表,这些字符串是 自动配置类的全限定类名

在这里插入图片描述
在这里插入图片描述

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

6.2.2 加载过程

首先来看启动类,启动类上包含了@SpringBootApplication注解

@SpringBootApplication
@MapperScan("com.cskaoyan.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

在@SpringBootApplication注解中包含了@EnableAutoConfiguration注解

@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选择器找到对应的自动配置类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

在AutoConfigurationImportSelector中包含了selectImports方法

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

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方法中

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这个方法中

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信息

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

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当没有某个组件的时候生效,生效就会导致注册一个默认组件;如果自行注册组件,那么这个默认组件失效;其实这个就是约定大于配置

@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文件,这个文件中包含了你可以做的配置项有哪些,并且这些配置的描述、默认值、值的格式等

在这里插入图片描述

值的格式

{
    "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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

rerun你的应用程序,重新启动一下

如果没有生成:在resources目录下新增一个文件夹 META-INF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值