Spring Boot 学习之路 -- 基础认知

前言

  1. 最近因为业务需要,被拉去研究后端的项目,代码框架基于 Spring Boot,对我来说完全小白,需要重新学习研究...
  2. 出于个人习惯,会以 Blog 文章的方式做一些记录,文章内容基本来源于「 Spring Boot 从入门到精通(明日科技) 」一书,做了一些整理,更易于个人理解和回顾查找,所以大家如果希望更系统性的学习,可以阅读此书(比较适合我这种新手)。

一、概述

Spring Boot 是在 Spring 的基础上发展而来的全新的开源框架。它是由 Pivotal 团队开发的。开发 Spring Boot 的主要动机是简化部署 Spring 应用程序的配置过程。也就是说,使用 Spring Boot 能够以更简单的、更灵活的方式开发 Spring 应用程序。

Spring Boot的主要特点如下:

▪️ Spring Boot 的代码非常少。Spring 的注解驱动编程避免了大量的配置工作,并且 Spring Boot 可以自动创建各种工厂类,程序开发人员直接通过依赖注入就可以获取各类对象。

▪️ Spring Boot 的配置非常简单。程序开发人员只需在 application.properties 或 application.yml 文件中编写一些配置项就可以影响整个项目。即使不编写任何配置,项目也可以采用一套默认配置正常启动。Spring Boot 支持使用 @Configuration 注解管理、维护配置类,让配置工作变得更灵活。

▪️ Spring Boot 可以自动部署。Spring Boot 自带 Tomcat 服务器,在项目启动的过程中可以自动完成所有资源的部署操作。

▪️ Spring Boot 易于单元测试。Spring Boot 自带 JUnit 单元测试框架,可以直接测试各个组件中的方法。

▪️ Spring Boot 集成了各种流行的第三方框架或软件。Spring Boot 提供了许多集成其他技术的依赖包,程序开发人员可以直接通过这些依赖包调用第三方框架或软件,例如 Redis、MyBatis、ActiveMQ 等。

▪️ Spring Boot 项目启动的速度很快。即使项目有庞大的依赖库,仍能在几秒之内完成部署和启动。


二、基础

因为注解在 Spring Boot 中发挥着至关重要的作用,所以为了能够快速地掌握 Spring Boot,须明确什么是注解和 Spring Boot 常用注解的功能及其标注位置。在 Spring Boot 容器中,存放着很多的 Bean。为此,须明确 Bean 是什么,并深入理解与其相关的两个过程:注入 Bean 和注册 Bean。在开发 Spring Boot 项目中,离不开"依赖",我们须掌握如何为项目添加依赖。与此同时,还须掌握如何为 Spring Boot 项目中的程序元素命名。

2.1 注解

在程序开发的过程中,注解是无处不在的。但是,注解又不是必需的;换言之,在不使用注解的情况下,也能够开发程序。只不过,掌握注解能够帮助程序开发人员深入理解框架,进而提高程序开发的效率。那么,什么是注解呢?注解的作用又有哪些呢?

2.1.1 概念及其应用

在给出注解的概念之前,须明确什么是元数据。所谓元数据,指的是用于描述数据的数据。下面结合某个配置文件里的一行信息,举例说明什么是元数据。

xml 复制代码
<string name="app_name">AnnotionProject</string>​​

上述信息中的数据 "app_name" 是用于描述数据 AnnotionProject 的。也就是说,数据 "app_name" 就是元数据。那么,什么是注解呢?注解又被称作标注,是一种被加入源码的具有特殊语法的元数据。

需要特别说明的是:

  • 注解仅仅是元数据,和业务逻辑无关。
  • 虽然注解不是程序本身,但是可以对程序做出解释。
  • 应用程序中的类、方法、变量、参数、包等程序元素都可以被注解。

理解了"什么是注解"后,再来了解一下在应用程序中注解的应用体现在哪些方面:

  • 在编译时进行格式检查。例如,如果被 @Override 标记的方法不是父类的某个方法,编译器就会报错。
  • 减少配置。依据代码的依赖性,使用注解替代配置文件。
  • 减少重复工作。在程序开发的过程中,通过注解减少对某个方法的调用次数。

2.1.2 常用注解及其标注位置

Spring Boot 是一个支持海量注解的框架:

注解 标注位置 功能
@autowired 成员变量 自动注入依赖
@Bean 方法 @Bean 用于注册 Bean,当 @Bean 标记方法时,等价于在 XML 中配置 Bean
@Component 用于注册组件。当不清楚注册类属于哪个模块时就用这个注释
@ComponentScan 开启组件扫描器
@Configuration 声明配置类
@ConfigurationProperties 用于加载额外的 properties 配置文件
@Controller 声明控制器类
@ControllerAdvice 可用于声明全局异常处理类和全局数据处理类
@EnableAutoConfiguration 开启项目的自动配置功能
@ExceptionHandler 方法 用于声明处理全局异常的方法
@Import 用于导入一个或者多个 @Configuration 注解标注的类
@ImportResource 用于加载 XML 配置文件
@PathVariable 方法参数 让方法参数从 URL 中的占位符中取值
@Qualifier 成员变量 与 @Autowired 配合使用,当 Spring 容器中有多个类型相同的 Bean 时,可以用 @Qualifier("name") 来指定注入哪个名称的 Bean
@RequestMapping 方法 指定方法可以处理哪些 URL 请求
@RequestParam 方法参数 让方法参数从 URL 参数中取值
@Resource 成员变量 与 @Autowired 功能类似,但是有 name 和 type 两个参数,可根据 Spring 配置的 Bean 的名称进行注入
@ResponseBody 方法 表示方法的返回结果直接写入 HTTP response body 中。如果返回值是字符串,则直接在网页上显示该字符串
@RestController 相当于 @Controller 和 @ResponseBody 的合集,表示在这个控制器下的所有方法都被 @ResponseBody 标注
@Service 服务的实现类 用于声明服务的实现类
@SpringBootApplication 主类 用于声明项目主类
@Value 成员变量 动态注入,支持 " #{ } " 与 " ${ } " 表达式

这些注解的编码位置是非常灵活的。当注解用于标注类、成员变量和方法时,注解的编码位置既可以在成员变量的上边,例如:

kotlin 复制代码
@Autowired
​​​​private String name;​

又可以在成员变量的左边,例如:

kotlin 复制代码
@Autowired private String name;​​

在 Spring Boot 的常用注解中,需特别说明的是,使用 @RequestParam 能够标注方法中的参数。例如:

kotlin 复制代码
​​​​@RequestMapping("/user")
​​​​@ResponseBody
​​​​public String getUser(@RequestParam Integer id) {
​​​​    return "success";
​​​​}​

2.1.3 使用@SpringBootApplication标注启动类

使用注解能够启动一个 Spring Boot 项目,这是因为在每一个 Spring Boot 项目中都有一个启动类(主类),并且启动类必须被 @SpringBootApplication 注解标注,进而能够调用用于启动一个 Spring Boot 项目的 SpringApplication.run() 方法。

如下:

kotlin 复制代码
package com.spark.springbootlearning;

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

@SpringBootApplication
public class SpringBootLearningApplication {

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

}

@SpringBootApplication 注解虽然重要,但使用起来非常简单,因为这个注解是由多个功能强大的注解整合而成的。

打开 @SpringBootApplication 注解的源码可以看到它被很多其他注解标注,其中最核心的 3 个注解分别是:

  • SpringBootConfiguration 注解:让项目采用基于 Java 注解的配置方式,而不是传统的 XML 文件配置。当然,如果程序开发人员写了传统的 XML 配置文件,Spring Boot 也是能够读取这些 XML 文件并识别里面的内容的。
  • @EnableAutoConfiguration 注解:开启自动配置。这样 Spring Boot 在启动时就可以自动加载所有配置文件和配置类了。
  • @ComponentScan 注解:启用组件扫描器。这样项目才能自动发现并创建各个组件的 Bean,包括 Web 控制器(@Controller)、服务(@Service)、配置类(@Configuration)和其他组件(@Component)。

注意:

一个项目可以有多个启动类,但这样的代码毫无意义。一个项目应该只使用一次@ SpringBootApplication 注解。

@SpringBootApplication 有一个使用要求:只能扫描底层包及其子包中的代码。底层包就是启动类所在的包。如果启动类在 com.spark.springbootlearning 包下,其他类应该写在 com.spark.springbootlearning 包或其子包中,否则无法被扫描器找到,就等同于无效代码。例如在图3.1和图3.2中,Controller类所在的位置可以被扫描到。而在图3.3和图3.4中,Controller类的位置就无法被扫描到了。

2.2 Bean 的注册和获取

Bean 指的是由 Spring Boot 容器管理的对象。Bean 是根据 Spring Boot 配置文件中的数据信息予以创建的。如果把 Spring Boot 容器看作是一个大工厂,那么 Bean 就相当于这个工厂生成的产品。

2.2.1 Bean 与依赖注入

在明确了什么是 Bean 后,还需要明确另外一个非常重要的概念:依赖注入

在面向对象程序设计中,对象和对象之间存在一种叫作"依赖"的关系。简单来说,依赖关系就是在一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象。

例如,在一个 B 类对象中,有一个 A 类的对象 a,那么就可以说 B 类的对象依赖于对象 a。而依赖注入就是基于这种"依赖关系"而产生的。

Spring Boot 在创建一个对象的过程中,会根据"依赖关系",把这个对象依赖的对象注入其中,这就是所谓的"依赖注入"。

掌握了 Bean 和依赖注入的概念后,再结合下图理解两个过程:

  1. 「 Bean 的注册 」:Spring Boot 能够自动寻找程序开发人员已经创建好的 Bean,并将其保存在 Spring Boot 容器中,这个过程被称作 Bean 的注册。
  2. 「 Bean 的注入 」:把 Spring Boot 容器中的 Bean 赋值给某个尚未被赋值的成员变量,这个过程被称作 Bean 的注入。

当 Spring Boot 项目被启动时,Spring Boot 首先会自动扫描所有的组件,然后注册所有的 Bean,最后把这些 Bean 注入各自的使用场景当中。下面通过一个实例,演示「 注册 Bean 」和「 注入 Bean 」的这两个过程。

  1. 创建用于注册 Bean 的组件类:BeanComponent

BeanComponent 类被 @Component 标注,表示这个类是一个组件类。类中只有一个 name() 方法,并且被 @Bean 标注,表示这个方法返回的对象被注册成了 Bean,并将其放到 Spring Boot 容器中。

  1. 创建 BeanTestController 控制器类

BeanTestController 类被 @RestController 标注,表示这个类是一个负责页面跳转的控制器,会直接返字符串结果。类中:

  • name 属性被 @Autowired 标注,表示这个属性的值由 Spring Boot 注入。
  • showName() 方法被 @RequestMapping("/bean") 标注,表示该方法映射"/bean"地址,并将 name 的值展示在页面中。
  1. 启动项目,打开浏览器,访问 http://127.0.0.1:8080/bean 地址,可以看到下图所示的结果:

页面中显示 "BeanComponent",但 BeanTestController 类中没有出现任何 "BeanComponent" 的字样,这个值是哪来的呢?"BeanComponent" 这个值出现在 BeanComponent 类的 name() 方法的返回值中。当 Spring Boot 项目被启动时,扫描器发现了 BeanComponent 类,并在该类下发现了被 @Bean 标注的方法,于是把该方法返回的对象注册成 Bean,再放到 Spring Boot 容器中。与此同时,扫描器也发现了 BeanTestController 类,发现这个类有一个 name 属性需要被注入值,Spring Boot 便在 Spring Boot 容器中查找有没有类型相同、名称匹配的 Bean,于是就找到了 name() 方法返回的字符串"BeanComponent",便将 "BeanComponent" 赋给了 name 属性。当前端发来请求时,showName() 方法便将 name 的值(也就是"BeanComponent")展示在了网页中。这就是一个「 注册 Bean 」和「 注入 Bean 」的例子。

2.2.2 注册 Bean

注册 Bean 需要用到 @Bean 注解,该注解用于标注方法,表示方法的返回值是一个即将进入 SpringBoot 容器中的 Bean。下面介绍 @Bean 注解的具体用法。

📄 1. 让 Spring Boot 发现 @Bean

如果想让 @Bean 注解生效,那么被标注的方法所在的类必须能够被 Spring Boot 的组件扫描器扫描到。

以下几个注解都可以让被标注的方法所在的类被扫描到:

注解 说明
@Configuration 声明配置类
@Controller 声明控制器类
@Service 声明服务接口或类
@Repository 声明数据仓库
@Component 如果不知道类属于什么模块,就用这个注解将类声明成组件。推荐使用此注解

如果不使用上面这 5 个注解,那么也可以用 @Import 注解将 @Bean 所在类的主动注册给 Spring Boot。

例如,我们修改刚才的代码,删除 BeanComponent 类上的 @Component 注解,代码如下:

在 Spring Boot 项目的启动类中,通过使用

kotlin 复制代码
@Import({com.mr.component.BeanComponent.class})

声明启动类,让项目启动时自动导入 BeanComponent 类,代码如下:

这样,当 Spring Boot 项目被启动时,BeanComponent 类中的 @Bean 也可以被注册到 Spring Boot 容器中(这里就不运行了,你可以自己测试)。

如果想要导入多个指定的类,@Import 的语法如下(注意圆括号和大括号的位置):

kotlin 复制代码
​​​​@Import({A.class, B.class, C.class})​​

📄 2. @Bean的使用方法

@Bean 注解有很多属性,其核心源码如下:

kotlin 复制代码
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

下面分别介绍这几个属性的用法:

  1. value 和 name 这两个属性的作用是一样的:就是给 Bean 起别名,让 Spring Boot 可以区分多个类型相同的 Bean。给 Bean起别名的语法如下:
kotlin 复制代码
​​​​@Bean("goudan")
​​​​@Bean(value = "goudan")
​​​​@Bean(name = "goudan")
​​​​@Bean(name = {"goudan", "GouDan", "Golden"})  // 同时给一个 Bean 起多个别名​​

如果没有给 Bean 起任何别名的话,那么 @Bean 注解会默认将方法名作为别名,例如:

kotlin 复制代码
​​​​@Bean
​​​​public String name() {
​​​​    return "Marco";
​​​​}​​

上面代码等同于:

kotlin 复制代码
​​​​@Bean(name = "name")
​​​​public String name() {
​​​​    return "Marco";
​​​​}​​
  1. autowireCandidate 属性:表示 Bean 是否采用默认的自动匹配机制,默认值为 true,如果将其赋值为 false,这个 Bean 就不会被默认的自动匹配机制匹配到,只能通过「使用别名的方式」匹配到。

比如,我们修改刚才的例子,代码如下:

BeanComponent 类中创建了两个方法,返回值类型均为 String。otherName() 方法定义了别名,并且 autowireCandidate 的值为 false,表示 Spring 在匹配 Bean 时会自动忽略 otherName() 方法的返回值。

下面再来看一下注入的代码。修改 BeanTestController 控制器类,代码如下:

BeanTestController 类定义了一个 name 属性,该属性使用 @Resource 标注,表示这个属性由 Spring 自动匹配并注入值。启动项目,打开浏览器访问 http://127.0.0.1:8080/bean 地址,看一下 name 被注入的值是什么:

name 的值为 "BeanComponent",即使 Spring 容器中有两个 String 类型的 Bean,但值为 "Marco" 的 Bean 拒绝自动匹配机制,所以 name 只能得到 "BeanComponent" 这个值。

如果想要得到 "Marco" 这个 Bean,就需要在注入时指定 Bean 的别名。例如使用 @Resource 注解读取别名为 "In" 的 Bean,关键代码如下:

这样,name 取的值就是别名为 "In" 的 Bean 的值。重启 Spring Boot 项目,并且重新访问地址 http://127.0.0.1:8080/bean 后,就可以看到网页上显示的值变成了 "Marco",效果如下:

  1. initMethod 属性:用于指定 Bean 初始化时会调用什么方法。
  2. destroyMethod 属性:用于指定 Bean 被 Spring 销毁时会调用什么方法,两个属性的值均为 Bean 对象的方法名。

2.2.3 获取 Bean

获取 Bean 就是在类中创建一个属性(可以是 private 属性),通过为属性添加注解,让 Spring Boot 为这个属性注入 Bean。

可以获取 Bean 的注解有3个:@Autowired、@Resouce 和 @Value。

这 3 个注解只能在可以被扫描到的类中使用。下面分别介绍这 3 个注解的用法。

📄 @Autowired

@Autowired 注解可以自动到 Spring 容器中寻找名称相同或类型相同的 Bean。例如,注册 Bean 的方法为:

kotlin 复制代码
​​​​@Bean
​​​​public String author() {
​​​​    return "Marco";
​​​​}​​

获取这个 Bean 的代码可以写成:

kotlin 复制代码
​​​​@Autowired
​​​​String author;

@Autowired 可以自动匹配与属性同名(即别名为 "author")的 Bean。如果匹配不到同名的 Bean,@Autowired 可以自动匹配类型相同的 Bean。例如,注册方法不变,获取 Bean 的代码为:

kotlin 复制代码
​​​​@Autowired
​​​​String name;​​

即使这么写,name 也可以获得 "Marco" 这个值,因为两者数据类型是相同的。

但要注意,当 Spring Boot 容器中仅有一个该类型的 Bean 时,@Autowired 才能匹配成功。如果存在多个该类型的 Bean,Spring 就不知道应该匹配哪个 Bean 了,项目就会抛出异常。例如,注册 Bean 的方法如下:

kotlin 复制代码
​​​​@Bean
​​​​public String author() {
​​​​    return "Marco";
​​​​}​​

​​​​@Bean
​​​​public String dev() {
​​​​    return "Superman";
}

现在容器中有两个 String 类型的 Bean,然后获取 Bean 的代码为:

kotlin 复制代码
​​​​@Autowired
​​​​String name;​​

启动项目后会抛出如下异常日志:

kotlin 复制代码
***************************
APPLICATION FAILED TO START
***************************

Description:

Field name in com.spark.springbootlearning.controller.BeanTestController required a single bean, but 2 were found:
	- author: defined by method 'author' in class path resource [com/spark/springbootlearning/component/BeanComponent.class]
	- dev: defined by method 'dev' in class path resource [com/spark/springbootlearning/component/BeanComponent.class]

This may be due to missing parameter name information

IDEA 代码也会有波浪线报错提示:

这个异常日志的意思是:程序需要自动匹配一个独立的 Bean,却找到了两个符合条件的 Bean。其中,一个 Bean 的别名叫"author",另一个 Bean 的别名叫"dev"。程序因为不知道哪个 Bean 是当前需要的,所以就停止了。

在这种因同时存在多个 Bean 而无法自动匹配的情况下,就需要指定 Bean 的别名以获取 Bean。

指定别名有两种方式:

  • 将类属性名改成 Bean 的别名。如果 Bean 的别名叫 "author",@Autowired 标注的属性名也叫 "author"。
  • 使用 @Qualifier 注解。@Qualifier 注解有一个 value 属性,用于指定要获取的 Bean 的别名。可以与 @Autowired 配套使用。例如,获取别名为 "dev" 的 Bean,代码如下:
kotlin 复制代码
​​​​@Autowired
​​​​@Qualifier("dev")
​​​​String name;​​

📄 @Resource

@Resource 注解的功能与 @Autowired 类似:@Resouce 注解自带 name 属性,可直接指定 Bean 的别名。其中,name 属性的默认值为空字符串,表示自动将被标注的属性名作为 Bean 的别名。

例如,获取别名为 "author" 的 Bean,可以有 3 种写法:

第一种写法:

kotlin 复制代码
​​​​@Resource(name="author")
​​​​String name;​

第二种写法:

kotlin 复制代码
@Resource
​​​​String author;  // 属性名就叫 author​

第三种写法:虽然可以执行,实际与第一种写法是一样的,不推荐这样写

kotlin 复制代码
​​​​@Resource(name="")
​​​​String author;​​

注意:

如果使用 @Autowired 注入 Object 类型的 Bean 时,抛出了 org.springframework.beans.factory. NoUniqueBeanDefinitionException:No qualifying bean of type 'java.lang.Object' available 异常,就将 @Autowired 换成 @Resource。

📄 @Value

@Value 注解可以动态地向属性注入值。@Value 有3种语法,分别是:

  1. 注入常量值。

下面的语法会让 name 的值等于 "author" 这个字符串,例如:

kotlin 复制代码
​​​​@Value("author")
​​​​String name;​
  1. 注入 Bean。

使用 "#{Bean别名}" 格式可以注入指定别名的 Bean,其效果类似于 @Resource(name="Bean别名"),例如:

kotlin 复制代码
@Value("#{author}")
​​​​String name;​​
  1. 注入配置文件中配置信息的值。

使用"${配置项}"格式可以注入 application.properties 文件中指定名称的配置信息的值,例如:

kotlin 复制代码
​​​​@Value("${author}")
​​​​String name;​​

注意:

如果配置文件中没有该项则会抛出 BeanCreationException 异常。

2.3 为Spring Boot项目添加依赖

在 Spring Boot 项目开发的过程中,有些依赖需要程序开发人员手动添加。本节将对如何手动为 Maven 项目添加依赖进行讲解。

2.3.1 在 pom.xml 文件中添加依赖

pom.xml 是Maven构建项目的核心配置文件,程序开发人员可以在此文件中为项目添加新的依赖,新的依赖将被添加到 <dependencies> 标签的子标签中,添加依赖的格式如下:

xml 复制代码
​​​​<dependency>
​​​​    <groupId>所属团队</groupId>
​​​​    <artifactId>项目ID</artifactId>
​​​​    <version>版本号</version>
​​​​    <scope>使用范围(可选)</scope>
​​​​</dependency>​

注意:

在 pom.xml 文件中,<dependency> 标签是 <dependencies> 标签的子标签。

例如,Spring Boot 项目自带的 Web 依赖和 JUnit 单元测试依赖在 pom.xml 中填写的位置如图所示。

程序开发人员只需要仿照这种格式在 标签内部添加其他依赖,然后保存 pom.xml 文件,Maven 就会自动下载依赖中的 jar 文件并自动引入项目中。

2.3.2 如何查找依赖的版本号

如果程序开发人员不知道依赖的 ID 和版本,可以到 MVNrepository 或阿里云云效 Maven 中去查找。

  1. MVNrepository 的官网地址为 https://mvnrepository.com/ ,查询结果如下图所示:
  1. 阿里云云效 Maven 虽然不会直接显示 XML 文本,但可以看到 groupId、artifactId 和 version 这3个值,效果如下图所示:
相关推荐
向前看-24 分钟前
验证码机制
前端·后端
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
超爱吃士力架2 小时前
邀请逻辑
java·linux·后端
AskHarries4 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion5 小时前
Springboot的创建方式
java·spring boot·后端
Yvemil75 小时前
《开启微服务之旅:Spring Boot Web开发举例》(一)
前端·spring boot·微服务
zjw_rp5 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder6 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚7 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
星河梦瑾7 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全