目 录CONTENT

文章目录

SpringBoot 学习笔记

Nicholas
2024-05-28 / 0 评论 / 1 点赞 / 13 阅读 / 46598 字

SpringBoot笔记

SpringBoot官网】https://spring.io/projects/spring-boot

SpringBoot官方文档】https://docs.spring.io/spring-boot/docs/current/reference/html/

一、SpringBoot简介

1.1、SpringBoot简介

SpringBoot是一个Java Web的开发框架,和SpringMVC类似。对比其他Java Web框架的好处,官方说是简化开发,约定大于配置, you can "just run"。使用SpringBoot能迅速地开发Web应用,几行代码开发一个接口。

所有的技术框架发展似乎都遵循一条主线规律:从一个复杂应用场景衍生出一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

这就是Java企业级应用:J2EE -> spring -> springboot的过程。

随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的背景下被抽象出来的开发框架,目的是让大家更容易地使用 Spring 、更容易地集成各种常用的中间件、开源软件。

SpringBoot 基于 Spring 开发,SpirngBoot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合,用于提升 Spring 开发者体验的工具。

SpringBoot 的核心思想是约定大于配置,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),SpringBoot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是 SpringBoot 其实不是什么新的框架,它默认配置了很多框架的使用方式,就像 maven 整合了所有的 jar 包,SpringBoot整合了所有的框架 。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门。
  • 开箱即用,提供各种默认配置来简化项目配置。
  • 内嵌式容器简化Web项目。
  • 没有冗余代码生成和XML配置的要求。

1.2、微服务架构

微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成―系列小服务的组合,可以通过http等方式进行互通。

1.2.1、单体应用架构

所谓单体应用架构(all in one)是指,我们将一个应用中的所有应用服务都封装在一个应用中。

无论是ERP、CRM或是其他系统,都把数据库访问,web访问等等各个功能放到一个 war 包内。这样做的好处是:易于开发和测试,也十分方便部署;当需要扩展时,只需要将 war 包复制多份,然后放到多个服务器上,再做个负载均衡就可以了。单体应用架构的缺点是:哪怕只要修改一个非常小的地方,都需要停掉整个服务,重新打包、部署这个应用 war 包。特别是对于一个大型应用,我们不可能把所有内容都放在一个应用里面,如何维护、如何分工合作都是问题。

1.2.2、微服务架构

所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。

这样做的好处是:节省了调用资源;每个功能元素的服务都是一个可替换的、可独立升级的软件代码。

具体可参见Martin Fowler的论文 https://martinfowler.com/articles/microservices.html

20240528165636_036067.png

1.2.3、如何构建微服务

一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过请求相互调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。

但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套产品:

  • 构建一个个功能独立的微服务应用单元,可以使用SpringBoot,它可以帮我们快速构建一个应用。
  • 大型分布式网络服务的调用,这部分由SpringCloud来完成,实现分布式。
  • 在分布式中间,进行流式数据计算、批处理,有SpringCloud Data Flow
  • Spring为我们提供了整个从开始构建应用到大型分布式应用的全流程方案。

二、Hello World程序

2.1、创建应用

可以使用Spring官网提供的Spring Initializr https://start.spring.io/,初始化好之后,下载解压导入`IDEA`即可。(不常用)

一般开发中,我们可以直接在IDEA中按照如下流程创建SpringBoot项目。

20240528165636_225886.png 20240528165636_241673.png

2.2、目录解析

创建完成后,得到的目录结构如下。

20240528165636_057826.png

其中,Springboot01HelloworldApplication.java为程序的主入口。

package org.wzq.springboot01helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication   // 标注这个类是一个 SpringBoot 的应用
public class Springboot01HelloworldApplication {

    public static void main(String[] args) {   // 启动 SpringBoot 应用
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}

application.propertiesSpringBoot的核心配置文件。

spring.application.name=springboot-01-helloworld

pom.xmlmaven的配置文件,引入了项目所需的依赖。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- 继承 spring-boot-starter-parent 的依赖管理,控制版本与打包等内容 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.wzq</groupId>
    <artifactId>springboot-01-helloworld</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-01-helloworld</name>
    <description>springboot-01-helloworld</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <!-- 使用 Spring MVC 构建 Web(包括 RESTful)应用程序的入门工具。使用 Tomcat 作为默认嵌入式容器。 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 用于编写单元测试的依赖包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 配合 spring-boot-starter-parent 可以把 SpringBoot 应用打包成 JAR 来直接运行 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.3、编写Controller

Springboot01HelloworldApplication.java同级目录下创建controller包。

package org.wzq.springboot01helloworld.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("hello")
public class HelloController {
    
    @GetMapping("/hello")
    @ResponseBody
    public String hello(){
        return "Hello World!";
    }
}

启动程序后,浏览器即可访问。

20240528165636_073054.png

2.4、生成JAR

通过maven菜单执行package命令。

20240528165636_256883.png

打包成功后,可以在target目录下看到生成的jar包。

20240528165636_088573.png

直接通过java -jar命令执行该jar包即可启动Web服务。

三、修改端口号与启动图像

直接在application.properties文件中添加配置。

# 更改项目的端口号
server.port=8081

直接在resources目录下新建banner.txt文件。

                                                              
                                .::::.                        
                              .::::::::.                      
                              :::::::::::                     
                              ':::::::::::..                  
                               :::::::::::::::'               
                                ':::::::::::.                 
                                  .::::::::::::::'            
                                .:::::::::::...               
                               ::::::::::::::''               
                   .:::.       '::::::::''::::                
                 .::::::::.      ':::::'  '::::               
                .::::':::::::.    :::::    '::::.             
              .:::::' ':::::::::. :::::      ':::.            
            .:::::'     ':::::::::.:::::       '::.           
          .::::''         '::::::::::::::       '::.          
         .::''              '::::::::::::         :::...      
      ..::::                  ':::::::::'        .:' ''''     
   ..''''':'                    ':::::.'                      
                                                              

重启后即可生效。

四、基本原理探究

4.1、pom.xml

4.1.1、依赖管理

其中它主要是依赖一个父项目spring-boot-starter-parent,主要是管理项目的资源过滤及插件。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点进去,发现还有一个父依赖spring-boot-dependencies

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

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本管理中心。我们在引入一些SpringBoot依赖的时候,不需要指定版本,就是因为在这里指定了默认版本。

4.1.2、启动器

pom.xml文件中还会有很多启动器,如:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-xxxSpringBoot的场景启动器。如spring-boot-starter-web,会帮我们自动导入web模块正常运行所依赖的组件。

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter(启动器)。需要使用什么功能,只要找到和导入对应的启动器即可。未来我们也可以自定义starter

这些启动器可以在SpringBoot的官方文档中找到 https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

4.2、主启动类

package org.wzq.springboot01helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Springboot01HelloworldApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}

4.2.1、自动装配

注解的总体层次结构:

@SpringBootApplication
	- @SpringBootConfiguration
		-- @Configuration
			--- @Component
	- @EnableAutoConfiguration
		-- @AutoConfigurationPackage    // 自动配置包
			--- @Import({AutoConfigurationPackages.Registrar.class})    // 自动注册包
		-- @Import({AutoConfigurationImportSelector.class})   // 自动导入包的核心
        	--- AutoConfigurationImportSelector   // 自动配置导入选择器
        		# getCandidateConfigurations()   // 获取候选的配置
	- @ComponentScan    // 扫描当前主启动类同级的包
4.2.1.1、@SpringBootApplication

作用:标注在某个类上,说明这个类是SpringBoot的主启动类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    ....
}
4.2.1.2、@ComponentScan

这个注解在Spring中很重要,它对应XML配置中的元素。

作用:自动扫描并加载符合条件的组件或者bean, 将这个bean定义加载到IOC容器中。

4.2.1.3、@SpringBootConfiguration

作用:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类。

@Configuration   // 说明这是一个配置类,即对应 Spring 的 xml 配置文件
public @interface SpringBootConfiguration {
    ....
}

// 点击进 Configuration 注解看到
@Component   // 说明启动类本身也是 Spring 中的一个组件
public @interface Configuration {
	....
}
4.2.1.4、@EnableAutoConfiguration

作用:开启自动配置功能。以前我们需要自己配置的东西,现在SpringBoot可以自动帮我们配置。@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效。

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    ....
}

AutoConfigurationImportSelector:自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击这个类看源码:可以看到它有一个getCandidateConfigurations()方法,获取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置文件。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
    Assert.notEmpty(configurations, "No auto configuration classes found 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;
}

找到该文件,打开可以看到很多自动配置的文件,这就是自动配置根源所在。

20240528165636_103741.png

20240528165636_271637.png

任意打开其中的一个配置类,可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean

20240528165636_286862.png
// 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@Configuration
// 启动指定类的 ConfigurationProperties 功能
// 进入这个 HttpProperties 查看,将配置文件中对应的值和 HttpProperties 绑定起来
// 并把 HttpProperties 加入到 ioc 容器中
@EnableConfigurationProperties({HttpProperties.class})
// Spring 底层 @Conditional 注解
// 根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效
// 这里的意思就是判断当前应用是否是 web 应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
// 判断当前项目有没有这个类 CharacterEncodingFilter - SpringMVC 中进行乱码解决的过滤器
@ConditionalOnClass({CharacterEncodingFilter.class})
// 判断配置文件中是否存在某个配置:spring.http.encoding.enabled
// 如果不存在(即使配置文件中不配置 pring.http.encoding.enabled=true),也是默认生效的
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
    // 已经和 SpringBoot 的配置文件映射了
    private final Encoding properties;
    // 只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    // 给容器中添加一个组件,这个组件的某些值需要从 properties 中获取
    @Bean
    @ConditionalOnMissingBean  // 判断容器没有这个组件
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new
            OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframew
                                                                   ork.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframe
                                                                    work.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    
    ......
}

// 进入 HttpProperties 类中可以看到其指定的前缀(用于配置文件中使用)
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
	// .....
}

// 总结: 根据当前不同的条件判断,决定这个配置类是否生效!
// 一但这个配置类生效,这个配置类就会给容器中添加各种组件
// 这些组件的属性是从对应的 Properties 类中获取的,这些类里面的每一个属性又是和配置文件绑定的
// 所有在配置文件中能配置的属性都在 xxxxProperties 类中封装着

// xxxxAutoConfigurartion: 自动配置类,给容器中添加组件
// xxxxProperties: 封装绑定配置文件中相关属性

在配置文件中使用这个前缀测试。

20240528165636_302275.png

【小结】所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的org.springframework.boot.autoconfigure包下的配置项,通过反射实例化为对应标注了@ConfigurationJavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

4.2.1.5、@Conditional

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才生效。

20240528165636_119294.png

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

可以通过启用debug=true属性,来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。

# 开启 springboot 的调试类
debug=true
4.2.1.6、@AutoConfigurationPackage

自动配置包。

// Spring 底层注解 @import,给容器中导入一个组件
@Import({AutoConfigurationPackages.Registrar.class})  // 将主启动类的所在包及所有子包里的所有组件扫描到 Spring 容器
public @interface AutoConfigurationPackage {
    ....
}
4.2.1.7、小结

SpringBoot所有自动配置都是在启动的时候扫描并加载:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置文件,所有的自动配置类都在这里面,但是不一定生效。要判断条件是否成立(通过@ConditionalOnClass注解),只要导入了对应的start启动器,对应的自动装配就会生效。

  • 1、SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值。
  • 2、将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作。
  • 3、整个J2EE的整体解决方案和自动配置都在springboot-autoconfigurejar包中。
  • 4、它会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件。
  • 5、有了自动配置类,免去了我们手动编写配置注入功能组件等工作。

4.2.2、启动应用

4.2.2.1、SpringApplication

这个类主要做了以下四件事情:

  • 1、推断应用的类型是普通的项目还是Web项目。
  • 2、查找并加载所有可用初始化器,设置到initializers属性中。
  • 3、找出所有的应用程序监听器,设置到listeners属性中。
  • 4、推断并设置main方法的定义类,找到运行的主类。
4.2.2.2、run方法

静态方法,用于启动应用。

五、配置文件

SpringBoot使用一个全局的配置文件,配置文件名称是固定的。

  • application.properties 语法结构:key=value

  • application.yaml 语法结构:key: value

配置文件的作用:修改SpringBoot自动配置的默认值。比如我们可以在配置文件中修改Tomcat默认启动的端口号:

server.port=8081

propertiesyaml配置文件之间的对比:(松散绑定意思是,比如yaml配置文件中写的last-namelastName是一样的)

20240528165636_317626.png

【注意】properties配置文件在写中文的时候会有乱码 , 需要在IDEA中设置编码格式为UTF-8。(settings -> Editor -> FileEncodings中)

20240528165636_134879.png

5.1、注入配置文件

yaml文件更强大的地方在于,它可以给我们的实体类直接注入匹配值。

例如,编写一个实体类DogPerson

@Component // 注册 bean 到容器中
public class Dog {
    private String name;
    private Integer age;
    // 有参无参构造、getter、setter 方法、toString() 方法
}

@Component // 注册 bean 到容器中
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    //有参无参构造、get、set方法、toString()方法
}

5.1.1、原有方式

按照原先的方式,我们可以通过@Value注解给bean注入属性值。

@Component
public class Dog {
    @Value("旺财")
    private String name;
    @Value("5")
    private Integer age;
}

编写测试类测试。

@SpringBootTest
class DemoApplicationTests {
    @Autowired //将狗狗自动注入进来
    Dog dog;
    @Test
    public void contextLoads() {
        System.out.println(dog); //打印看下狗狗对象
    }
}

5.1.2、yaml配置方式

现在我们来使用yaml配置的方式进行注入。

导入依赖。

<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

编写yaml配置文件。

person:
	name: wzq
	age: 3
	happy: false
	birth: 2000/01/01
	maps: {k1: v1, k2: v2}
	lists:
		- code
		- girl
		- music
	dog:
		name: 旺财
		age: 3

修改实体类。

/*
@ConfigurationProperties 作用:
告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person”: 将配置文件中的 person 下面的所有属性一一对应
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

在测试类中进行测试。

@SpringBootTest
class DemoApplicationTests {
    @Autowired
    Person person;  // 将 person 自动注入进来
    @Test
    public void contextLoads() {
        System.out.println(person);  // 打印 person 信息
    }
}

5.2、加载指定配置文件

前述@ConfigurationProperties注解默认从全局配置文件中获取值,如果想加载指定的配置文件,可以使用@PropertySource注解。

@PropertySource(value="classpath:person.properties")
@Component
public class Person{
    
    @Value("${name}")
    private String name;
    
    ....
}

5.3、yaml配置文件占位符

person:
	name: wzq${random.uuid} # 随机 uuid
	age: ${random.int} # 随机 int
	happy: false
	birth: 2000/01/01
	maps: {k1: v1, k2: v2}
	lists:
		- code
		- girl
		- music
	dog:
		# 引用 person.hello 的值,如果不存在就用 :后面的值,即 other,然后拼接上_旺财
		name: ${person.hello:other}_旺财
		age: 1

5.4、JSR303数据校验

可以在字段上增加一层过滤器验证,保证数据的合法性。

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。

举例:这里来写个注解让name必须是Email格式。

@Component
@ConfigurationProperties(prefix="person")
@Validated // 数据校验
public class Person {
	@Email(message="邮箱格式错误") // name 必须是邮箱格式
	private String name;
}

5.5、多环境切换

profileSpring对不同环境提供不同配置功能的支持,可以实现快速切换环境。

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml,用来指定多个环境版本。

例如:application-test.properties 代表测试环境配置; application-dev.properties 代表开发环境配置。

但是 SpringBoot 并不会直接启动这些配置文件,它默认使用 application.properties 主配置文件。我们需要通过一个配置来选择需要激活的环境:

spring.profiles.active=dev

六、自定义一个starter

分析完毕了源码以及自动装配的过程,可以尝试自定义一个启动器来玩玩!

七、静态资源

项目中有许多静态资源,比如css,js等前端资源文件,那么在SpringBoot中如何处理静态资源呢。

静态资源映射规则SpringBoot中,SpringMVCweb配置都在 WebMvcAutoConfiguration 这个配置类里面。

找到其中的内部类 WebMvcAutoConfigurationAdapter,其中有很多配置方法。

找到addResourceHandlers()方法,该方法用于添加资源处理。

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");  // 已禁用默认资源处理
    } else {
        // webjars 配置
        this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
        // 静态资源配置
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}

// 点进 getWebjarsPathPattern() 方法,可以找到 webjars 默认路由
// private String webjarsPathPattern = "/webjars/**";

// 点进 getStaticPathPattern() 方法,可以找到 静态资源 默认路由
// private String staticPathPattern = "/**";

// 点进 getStaticLocations() 方法,可以找到默认静态资源位置
// private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

7.1、Webjars方式

Webjars本质就是以jar包的方式引入静态资源,官方网站 https://www.webjars.org/

例如,需要使用jQuery,我们只要引入jQuery对应版本的pom依赖即可。

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

导入成功后可以看到其目录结构。

20240528165636_150124.png

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源。可以访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

7.2、默认静态资源路径

根据上述源码分析,有以下4个目录存放的静态资源可以被默认识别。

classpath:/META-INF/resources/ 
classpath:/resources/
classpath:/static/
classpath:/public/

可以在resources根目录下创建对应的文件夹,存放静态资源。可以访问:http://localhost:8080/test.js

7.3、自定义静态资源路径

我们也可以自己通过配置文件来指定静态资源存放的路径。在application.properties中配置:

spring.resources.static-locations=classpath:/coding/,classpath:/photo/

一旦自定义了静态资源文件夹的路径,原来的自动配置就都会失效了。

八、首页配置

WebMvcAutoConfigurationEnableWebMvcConfiguration 内部类中,可以找到如下方法。

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider,
                                           WelcomePageHandlerMapping::new);
}

@Bean
public WelcomePageNotAcceptableHandlerMapping welcomePageNotAcceptableHandlerMapping(
    ApplicationContext applicationContext, FormattingConversionService mvcConversionService,
    ResourceUrlProvider mvcResourceUrlProvider) {
    return createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider,
                                           WelcomePageNotAcceptableHandlerMapping::new);
}

private <T extends AbstractUrlHandlerMapping> T createWelcomePageHandlerMapping(
    ApplicationContext applicationContext, FormattingConversionService mvcConversionService,
    ResourceUrlProvider mvcResourceUrlProvider, WelcomePageHandlerMappingFactory<T> factory) {
    TemplateAvailabilityProviders templateAvailabilityProviders = new TemplateAvailabilityProviders(
        applicationContext);
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    T handlerMapping = factory.create(templateAvailabilityProviders, applicationContext, getIndexHtmlResource(),
                                      staticPathPattern);  // 获取欢迎页
    handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    handlerMapping.setCorsConfigurations(getCorsConfigurations());
    return handlerMapping;
}

private Resource getIndexHtmlResource(String location) {
    return getIndexHtmlResource(this.resourceLoader.getResource(location));
}

private Resource getIndexHtmlResource(Resource location) {
    try {
        Resource resource = location.createRelative("index.html");  // 获取到 index.html
        if (resource.exists() && (resource.getURL() != null)) {
            return resource;
        }
    }
    catch (Exception ex) {
        // Ignore
    }
    return null;
}

可以得出结论,在静态资源目录下创建index.html,即可作为首页访问。

20240528165636_165710.png

如果想要更换favicon图标,只需要在静态资源目录下添加favicon.ico图片文件即可。

九、模板引擎

我们以前使用jsp编写前端页面,jsp支持在页面中编写Java代码。但是 SpringBoot 是以 jar 的方式,不是 war。同时我们用的是嵌入式的Tomcat,默认不支持jsp

SpringBoot推荐使用模板引擎:其实jsp就是一个模板引擎,常用的还有ThymeleafSpringBoot官方推荐),freemarker等。

模板引擎的思想都是一致的,即通过表达式在前端页面模板中动态加载数据。

20240528165636_332997.png

9.1、Thymeleaf

Thymeleaf 官网】https://www.thymeleaf.org/

Thymeleaf | Githubhttps://github.com/thymeleaf/thymeleaf

导入thymeleaf依赖。

<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

此时可以找到ThymeleafProperties配置类。

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
	public static final String DEFAULT_PREFIX = "classpath:/templates/";   // 默认前缀
	public static final String DEFAULT_SUFFIX = ".html";                   // 默认后缀

	/**
	 * Whether to check that the template exists before rendering it.
	 */
	private boolean checkTemplate = true;

	/**
	 * Whether to check that the templates location exists.
	 */
	private boolean checkTemplateLocation = true;

	/**
	 * Prefix that gets prepended to view names when building a URL.
	 */
	private String prefix = DEFAULT_PREFIX;

	/**
	 * Suffix that gets appended to view names when building a URL.
	 */
	private String suffix = DEFAULT_SUFFIX;

	/**
	 * Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
	 */
	private String mode = "HTML";

	/**
	 * Template files encoding.
	 */
	private Charset encoding = DEFAULT_ENCODING;
    
    ....
}

由此可知,我们只需要把html页面放在类路径下的templates文件夹下,thymeleaf就可以帮我们自动渲染。

测试:编写一个IndexController

package org.wzq.springboot01helloworld.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Arrays;

@Controller
public class IndexController {

    @RequestMapping("/index")
    public String index(Model model){
        model.addAttribute("msg", "Hello, Thymeleaf!");  // 添加数据
        model.addAttribute("users", Arrays.asList("wzq", "little"));
        return "index";   // classpath:/templates/index.html
    }
}

编写一个测试页面 index.html 放在 templates 目录下。

首先,需要通过xmlns:th="http://www.thymeleaf.org"导入thymeleaf命名空间约束。然后,就可以使用thymeleaf语法进行取值渲染。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>

<body>
    <h1>这是一个首页</h1>
    <div th:text="${msg}"></div>
    <div th:each="user:${users}" th:text="${user}"></div>
</body>
</html>

20240528165636_180938.png

十、SpringMVC自动配置

在进行项目编写前,我们还需要知道SpringBootSpringMVC做了哪些配置,以及如何扩展和定制。

https://docs.spring.io/spring-boot/reference/web/servlet.html#web.servlet.spring-mvc.auto-configuration

20240528165636_195657.png

Spring BootSpring MVC 提供了自动配置功能,满足大多数应用程序的需要。它取代了对 @EnableWebMvc 的需要,并且两者不能同时使用。除了 Spring MVC 的默认设置外,自动配置还提供了以下功能:

  • 包含 ContentNegotiatingViewResolverBeanNameViewResolverbeans
  • 支持静态资源服务,包括对 WebJars 的支持。
  • 自动注册 ConverterGenericConverterFormatterbeans
  • 支持 HttpMessageConverters
  • 自动注册 MessageCodesResolver
  • 支持静态 index.html
  • 自动使用 ConfigurableWebBindingInitializerbean

如果想保留这些 Spring Boot MVC 定义并进行更多 MVC 的自定义(拦截器、格式化器、视图控制器和其他功能),可以添加自己的 @Configuration 类,该类的类型为 WebMvcConfigurer,并不添加 @EnableWebMvc

如果您想提供 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 的自定义实例,并仍然保留 Spring Boot MVC 的定义功能,您可以声明一个 WebMvcRegistrations 类型的 bean,并用它来提供这些组件的自定义实例。自定义实例将由 Spring MVC 进一步初始化和配置。要参与后续处理,或在需要时覆盖后续处理,应使用 WebMvcConfigurer

如果不想使用自动配置,而是想完全控制 Spring MVC,可添加自己的 @Configuration 并注释为 @EnableWebMvc。或者,按照 @EnableWebMvcJavadoc 中的描述,添加自己的 @Configuration 注解的 DelegatingWebMvcConfiguration

SpringBoot 在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@bean),如果有就用用户配置的,如果没有就用自动配置的。有些组件可以存在多个,比如视图解析器,就将用户配置的和默认的组合起来。

在源码目录下新建一个config包,编写一个MyMvcConfig类。

package org.wzq.springboot01helloworld.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

// 扩展 SpringMVC (官方推荐方式)
// 如果想自定义一些功能,只需要写这个组件,然后将它交给 SpringBoot,SpringBoot 就会帮我们自动装配
@Configuration   // 说明这是一个配置类
public class MyMvcConfig implements WebMvcConfigurer {
    // 视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/wzq").setViewName("index");   // 可以通过 localhost:8080/wzq 访问 index.html 页面
    }

    // 注册自定义视图解析器为 bean
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    // 自定义视图解析器(实现了 ViewResolver 接口的类,就可以看做视图解析器)
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }
}

十一、数据库连接

对于数据访问层,无论是 SQL(关系型数据库)还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Data 也是 Spring 中与 Spring BootSpring Cloud 等齐名的知名项目。

Sping Data 官网】https://spring.io/projects/spring-data

11.1、依赖导入

导入数据库连接相关依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

11.2、配置数据源

application.yml总体配置文件中,添加如下配置。

spring:
  datasource:
    username: root
    password: xxxxxx
    url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

11.3、测试连接

此时即可在测试类中,测试数据库的连接。

package com.wzq;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot05DataApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        // 查看默认的数据源
        System.out.println(dataSource.getClass());

        // 获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        connection.close();
    }
}

有了连接,就可以使用原生的 JDBC 语句来操作数据库。

不过 Spring 本身对原生的 JDBC 做了轻量级的封装,即 JdbcTemplate,包括了数据库操作的各种 CRUD 方法。Spring Boot 默认已经配置好了 JdbcTemplate 放在了容器中,只需注入即可使用。

11.4、数据库操作

可以通过自动装载jdbcTemplate,来进行数据库操作。

JdbcTemplate 主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句。
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate 方法用于执行批处理相关语句。
  • query方法及queryForXXX方法:用于执行查询相关语句。
  • call方法:用于执行存储过程、函数相关语句。
package com.wzq.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class JDBCController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    // 查询 user 表中所有数据
	// List 中的 1 个 Map 对应数据库的 1 条记录
	// Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("/userList")
    public List<Map<String, Object>> userList(){
        String sql = "select * from mybatis.user";
        return jdbcTemplate.queryForList(sql);
    }
	
    @GetMapping("/addUser")
    public String addUser(){
        String sql = "insert into mybatis.user (id, name, pwd) values (33, '小米', '123456');";
        jdbcTemplate.update(sql);
        return "add-ok";
    }

    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
        String sql = "update mybatis.user set name = ?, pwd = ? where id=" + id;

        Object[] objects = new Object[2];
        objects[0] = "Little";
        objects[1] = "987654";

        jdbcTemplate.update(sql, objects);
        return "update-ok";
    }

    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id") int id){
        String sql = "delete from mybatis.user where id=?";
        jdbcTemplate.update(sql, id);
        return "delete-ok";
    }
}

11.5、整合Mybatis

11.5.1、导入依赖和测试连接

导入依赖。(可以看到,该artifactIdspring-boot-starter放在了后面,说明其不是springboot官方的)

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

同上11.211.3进行数据库的配置和测试连接。

11.5.2、编写实体类

和数据库表对应,编写实体类。

package com.wzq.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User{
    private int id;
    private String name;
    private String pwd;
}

11.5.3、编写Mapper接口

编写mapper接口。

package com.wzq.mapper;

import com.wzq.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper       // 表示这是一个 mybatis 的 mapper 类
@Repository   // 被 spring 整合
public interface UserMapper {
    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
}

11.5.4、编写mapper文件

编写mapper文件UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace -> bound a DAO/Mapper Interface -->
<mapper namespace="com.wzq.mapper.UserMapper">

    <select id="queryUserList" resultType="User">
        select * from mybatis.user;
    </select>

    <select id="queryUserById" resultType="User">
        select * from mybatis.user where id = #{id};
    </select>

    <insert id="addUser" parameterType="User">
        insert into mybatis.user (id, name, pwd) VALUES (#{id}, #{name}, #{pwd});
    </insert>

    <update id="updateUser" parameterType="User">
        update mybatis.user set name=#{name}, pwd=#{pwd} where id=#{id};
    </update>

    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id};
    </delete>

</mapper>

11.5.5、添加mybatis配置

application.yml总体配置文件中,添加mybatis的配置。

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml   # 指定 mybatis 的核心配置文件与 Mapper 映射文件
  type-aliases-package: com.wzq.pojo                 # 实体类的路径

11.5.6、编写controller

编写controller

package com.wzq.controller;

import com.wzq.mapper.UserMapper;
import com.wzq.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/queryUserList")
    public List<User> queryUserList(){
        List<User> userList = userMapper.queryUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        return userList;
    }

    @GetMapping("/addUser")
    public String addUser(){
        userMapper.addUser(new User(15, "哈哈", "1321"));
        return "ok";
    }

    @GetMapping("/updateUser")
    public String updateUser(){
        userMapper.updateUser(new User(15, "哈哈", "12222321"));
        return "ok";
    }

    @GetMapping("/deleteUser")
    public String deleteUser(){
        userMapper.deleteUser(15);
        return "ok";
    }
}
1

评论区