Java八股文——Spring「SpringBoot 篇」
为什么使用 Spring Boot
面试官您好,对我来说,Spring Boot并不仅仅是Spring框架的一个升级,它是一次颠覆性的革命。它的核心目标,就是为了将开发者从繁重、易错的Spring配置和依赖管理中彻底解放出来,让我们能够以极快的速度搭建和开发生产级的、可独立运行的应用程序。
要理解它的价值,我们可以先回忆一下传统Spring开发的痛点:
- 繁琐的XML配置:我们需要编写大量的XML文件来定义Bean、配置AOP、事务、MVC等。
- 复杂的依赖管理:我们需要手动地在
pom.xml
中添加大量的依赖,并且要小心翼翼地处理不同依赖之间的版本兼容性问题,这非常容易出错,被称为“依赖地狱”。
Spring Boot正是为了解决这些痛痛点而生的。它的核心优势,正如您所说,主要体现在以下几个方面,而这些优势背后都有其精巧的实现原理。
1. 简化开发 —— 核心是“起步依赖” (Starters)
- 痛点解决:彻底解决了“依赖地狱”问题。
- 实现原理:Spring Boot提供了一系列名为
spring-boot-starter-*
的“起步依赖”。比如,我们想开发一个Web应用,只需要在Maven或Gradle中引入spring-boot-starter-web
这一个依赖就够了。 - 它做了什么? 这个
starter
就像一个“全家桶”,它会通过Maven的依赖传递机制,自动地把开发一个Web应用所需要的所有常用库(如spring-webmvc
,spring-web
,tomcat-embed
,jackson-databind
等)都一起引入进来,并且管理好了它们之间的版本兼容性。我们完全不需要再关心应该引入哪些jar包,以及它们的版本应该是什么。
2. 自动化配置 (Auto-Configuration) —— 最具魔力的特性
- 痛点解决:彻底解决了“繁琐的XML配置”问题。
- 实现原理:这是Spring Boot的“魔法”核心,它遵循 “约定大于配置”(Convention over Configuration) 的理念。
- 条件化配置 (
@ConditionalOn...
):Spring Boot内部有大量的配置类(@Configuration
),但这些配置类并不是无条件生效的。它们大多被@ConditionalOn...
系列的注解所标记,比如@ConditionalOnClass
(当classpath中存在某个类时生效)、@ConditionalOnBean
(当容器中存在某个Bean时生效)、@ConditionalOnProperty
(当配置文件中有某个属性时生效)等等。 spring.factories
机制:在每个starter
的jar包中,都有一个META-INF/spring.factories
文件(新版中是新的机制),里面列出了这个starter
需要触发的自动配置类。- 工作流程:当Spring Boot应用启动时,它会扫描所有
starter
中的spring.factories
文件,加载这些自动配置类,然后根据@Conditional
注解的条件,智能地判断哪些配置需要生效。比如,它发现classpath中有tomcat-embed.jar
,就会自动为我们配置好内嵌的Tomcat服务器;发现有DataSource.class
,就会自动配置好数据源。
- 条件化配置 (
- 结果:我们无需编写任何一行XML,就能得到一个预先配置好的、最佳实践的Spring环境。
3. 快速启动与独立运行 —— 内嵌Web服务器
- 痛点解决:解决了传统Web应用需要打包成WAR包,并部署到外部Tomcat服务器的繁琐流程。
- 实现原理:
spring-boot-starter-web
中默认就包含了内嵌的Tomcat服务器(也可以换成Jetty或Undertow)。 - 结果:我们的Spring Boot应用可以直接打包成一个可执行的fat JAR。这个JAR包里不仅包含了我们自己的代码,还包含了所有依赖以及内嵌的Web服务器。我们可以通过一句简单的
java -jar myapp.jar
命令,在任何有Java环境的地方,一键启动我们的Web应用,极大地简化了开发、测试和部署流程。
总结与生态
Spring Boot通过起步依赖、自动化配置、内嵌服务器这三大法宝,从根本上重塑了Spring应用的开发体验。
它不仅自身极其强大,更重要的是,它成为了整个Spring Cloud微服务生态的基石。所有Spring Cloud的组件(如Nacos, Sentinel, Gateway等)都是以Spring Boot starter
的形式提供的,这使得构建一个复杂的分布式系统,也变得像搭积木一样简单。可以说,Spring Boot是现代Java后端开发的事实标准和必备技能。
Spring Boot比Spring好在哪里
面试官您好,我认为“Spring Boot比Spring好在哪里”这个问题,更准确的说法应该是 “Spring Boot为我们解决了传统Spring框架的哪些核心痛点” 。
Spring Boot并不是用来替代Spring的,它是在强大的Spring框架之上,构建的一套旨在简化开发、提升效率的“脚手架”和“最佳实践集”。它让Spring从一个需要精细配置的“专家级”框架,变成了一个“开箱即用”的、对开发者极其友好的平台。
它的“好”,主要体现在解决了以下三大“痛点”:
1. 解决了“配置地狱”的痛点 —— 通过“自动化配置”
- 传统Spring的痛点:
- 在过去,我们要搭建一个Spring MVC项目,需要编写大量的XML配置文件:
web.xml
来配置DispatcherServlet
,spring-mvc.xml
来配置MVC三组件和AOP,applicationContext.xml
来配置数据源、事务等等。这个过程非常繁琐、易错,且充满了样板代码。
- 在过去,我们要搭建一个Spring MVC项目,需要编写大量的XML配置文件:
- Spring Boot的解决方案:自动化配置 (Auto-Configuration)
- Spring Boot遵循 “约定大于配置” 的原则。它内部集成了海量的“自动配置类”。
- 它会根据我们项目classpath中的jar包,来智能地判断我们需要什么,并自动为我们配置好。
- 比如:它发现classpath里有
spring-webmvc.jar
,就自动为我们配好DispatcherServlet
和MVC的各种默认设置;发现有HikariCP.jar
和JDBC驱动,就自动为我们配好DataSource
和JdbcTemplate
。 - 我们几乎可以做到零XML配置,就能得到一个功能完备的、按最佳实践配置好的Spring环境。
2. 解决了“依赖地狱”的痛点 —— 通过“起步依赖”
- 传统Spring的痛点:
- 我们要自己手动管理
pom.xml
中所有的依赖。比如,要用Spring+MyBatis,我们需要自己去添加spring-core
,spring-beans
,spring-context
,spring-jdbc
,mybatis
,mybatis-spring
等一大堆依赖。 - 最头疼的是,我们还要自己去解决这些库之间的版本兼容性问题,稍有不慎就会导致各种
ClassNotFoundException
或NoSuchMethodError
。
- 我们要自己手动管理
- Spring Boot的解决方案:起步依赖 (Starters)
- Spring Boot提供了一系列
spring-boot-starter-*
的依赖包。 - 我们只需要在
pom.xml
中引入一个,比如spring-boot-starter-web
或spring-boot-starter-data-jpa
。 - 这个
starter
就像一个“依赖管理全家桶”,它会通过Maven的依赖传递,把所有相关的、且版本兼容的库都自动引入进来。 - 我们再也无需关心应该引入哪些依赖,以及它们的版本号是什么,彻底告别了“依赖地狱”。
- Spring Boot提供了一系列
3. 解决了“部署繁琐”的痛点 —— 通过“内嵌服务器”
- 传统Spring的痛点:
- Web项目必须被打成一个WAR包。
- 然后,我们需要在服务器上预先安装和配置一个外部的Web容器,比如Tomcat或Jetty。
- 最后,再把WAR包部署到这个容器中去运行。整个过程步骤多,且环境依赖重。
- Spring Boot的解决方案:内嵌Web服务器
spring-boot-starter-web
中默认就包含了内嵌的Tomcat(或其他可选服务器)。- 这使得我们的Spring Boot应用可以被打包成一个可执行的、独立的fat JAR。
- 这个JAR包里包含了所有东西:我们的代码、所有依赖、以及内嵌的服务器。
- 我们可以通过一句简单的
java -jar myapp.jar
命令,在任何有Java环境的地方,一键启动我们的应用,极大地简化了开发、测试和部署流程,也为Docker化和云原生部署铺平了道路。
总结一下,Spring Boot通过这三大法宝,让Spring框架的易用性、开发效率和部署便捷性都得到了质的飞跃。它让我们开发者可以真正地“约定优于配置”,将精力完全集中在业务逻辑的实现上。
怎么理解Spring Boot中的约定大于配置
面试官您好,“约定大于配置”(Convention over Configuration)是Spring Boot最核心、最具颠覆性的设计哲学。
我的理解是:Spring Boot为我们预设了一整套合理的、基于业界最佳实践的“约定”。只要我们遵循这些约定,就可以享受到框架带来的巨大便利,几乎无需任何手动配置就能快速地开发和运行一个应用程序。
这个理念,贯穿于Spring Boot设计的方方面面:
1. 依赖管理的约定 —— 通过“起步依赖” (Starters)
- 约定:Spring Boot约定,“如果你想开发一个Web应用,你就应该需要Spring MVC、Tomcat、Jackson这些东西。”
- 实现:它将这个“约定”打包成了一个名为
spring-boot-starter-web
的起步依赖。我们只需要在pom.xml
中引入这一个依赖。 - 效果:所有相关的、版本兼容的jar包都会被自动引入。我们无需再手动配置繁琐的依赖列表,Spring Boot已经为我们做好了最佳的“约定”。
2. Bean配置的约定 —— 通过“自动化配置” (Auto-Configuration)
这是“约定大于配置”最魔力的体现。
- 约定:Spring Boot约定,“如果你在classpath中加入了
spring-boot-starter-web
,那么你很可能就需要一个配置好的DispatcherServlet
、Tomcat
服务器、JSON消息转换器等等。” - 实现:
@EnableAutoConfiguration
:我们主启动类上的@SpringBootApplication
注解中,就包含了这个核心的自动配置开关。@ConditionalOn...
:Spring Boot内部有海量的自动配置类,但它们都带有条件注解。比如,WebMvcAutoConfiguration
(负责配置Spring MVC)上,就有@ConditionalOnClass(Servlet.class)
等条件。- 工作流程:应用启动时,Spring Boot会根据我们引入的
starter
(即classpath中的内容),来智能地判断哪些“约定”应该生效,并自动为我们配置好相应的Bean。
- 效果:我们无需再手动编写大量的XML或Java Config来配置这些基础组件。
3. 项目结构的约定
- 约定:正如您所说,Spring Boot推荐一种标准的项目结构。
- 主启动类(带有
@SpringBootApplication
的类)放在一个根包下(比如com.example.myapp
)。 - 其他所有的业务代码(如Controller, Service, Repository)都放在这个根包的子包下(如
com.example.myapp.controller
)。
- 主启动类(带有
- 为什么有这个约定? 因为
@SpringBootApplication
注解中包含的@ComponentScan
,默认会扫描其所在包及其所有子包。遵循这个结构约定,我们就可以无需手动配置@ComponentScan
的扫描路径。
4. 配置文件名的约定
- 约定:Spring Boot约定,应用程序的配置文件应该命名为
application.properties
或application.yml
,并放在src/main/resources
目录下。 - 效果:只要我们把配置文件放在这个约定的位置,Spring Boot启动时就会自动加载它,我们无需任何额外配置来指定配置文件的位置。
当“约定”不满足需求时 —— 配置大于约定
“约定大于配置”的后半句,其实是 “配置大于约定” 。Spring Boot在提供了巨大便利的同时,也保留了极高的灵活性。
- 如何覆盖? 当预设的“约定”不满足我们的需求时,我们可以通过在
application.properties
或application.yml
中明确地提供我们自己的配置,来覆盖掉自动配置的默认值。 - 例子:Spring Boot自动配置的Tomcat默认端口是8080。如果我们想换成8081,只需要在配置文件里加上一行
server.port=8081
即可。我们自己的配置,其优先级高于自动配置。
总结一下,“约定大于配置”是Spring Boot实现“开箱即用”和“快速开发”的核心思想。它通过一系列精心设计的“约定”,将开发者从繁重的配置工作中解放出来,让我们能更专注于业务逻辑本身。同时,它也提供了简单的方式来覆盖这些约定,保证了框架的灵活性。
Spring Boot的项目结构是怎么样的?
面试官您好,Spring Boot推崇 “约定大于配置” 的设计理念,这也体现在它推荐的项目结构上。遵循这套标准的项目结构,不仅能让项目看起来非常清晰、专业,更能让我们充分利用Spring Boot的自动化配置特性,减少很多不必要的配置。
一个典型的、基于Maven的Spring Boot项目,其结构通常如下:
my-project/
├── .mvn/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── myapp/
│ │ │ ├── MyApplication.java <-- (1) 主启动类
│ │ │ ├── controller/ <-- (2) 控制层 (Web层)
│ │ │ │ └── UserController.java
│ │ │ ├── service/ <-- (3) 业务逻辑层
│ │ │ │ └── UserService.java
│ │ │ ├── repository/ (或 dao/) <-- (4) 数据访问层
│ │ │ │ └── UserRepository.java
│ │ │ ├── domain/ (或 model/, entity/) <-- (5) 实体/领域模型
│ │ │ │ └── User.java
│ │ │ ├── config/ <-- (6) 配置类
│ │ │ │ └── AppConfig.java
│ │ │ └── common/ (或 util/) <-- (7) 公共工具类
│ │ │ └── DateUtils.java
│ │ ├── resources/
│ │ │ ├── static/ <-- (8) 静态资源
│ │ │ │ ├── css/
│ │ │ │ └── js/
│ │ │ ├── templates/ <-- (9) 视图模板
│ │ │ │ └── index.html
│ │ │ ├── application.properties (或 .yml) <-- (10) 核心配置文件
│ │ │ └── mybatis/ <-- (可选) MyBatis Mapper XML
│ │ │ └── UserMapper.xml
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── myapp/
│ └── MyApplicationTests.java <-- (11) 单元测试/集成测试
├── target/
└── pom.xml <-- (12) Maven配置文件
对这个结构的关键点,我的理解如下:
- 主启动类 (
MyApplication.java
):- 这是整个应用的入口,包含
main
方法,并使用@SpringBootApplication
注解标记。 - 一个最重要的约定:它必须放在一个根包下(如
com.example.myapp
)。因为@SpringBootApplication
中包含了@ComponentScan
,它默认会扫描其所在包及其所有子包。遵循这个约定,我们就不需要手动指定要扫描的包路径了。
- 这是整个应用的入口,包含
- 分层结构 (controller, service, repository):
- 这是经典的三层架构。将代码按职责划分到不同的包中,使得项目结构非常清晰。
controller
:负责处理HTTP请求,与前端交互。service
:负责封装核心的业务逻辑。repository
或dao
:负责与数据库进行交互,进行数据持久化操作。
- 实体/领域模型 (
domain
或entity
):- 存放POJO(Plain Old Java Object),通常是与数据库表结构对应的实体类。
- 配置类 (
config
):- 存放所有使用
@Configuration
注解的Java配置类。比如数据源配置、MyBatis配置、线程池配置等。
- 存放所有使用
src/main/resources
目录 :- 这是存放所有非Java代码资源的地方。
- (8)
static/
:Spring Boot约定,这个目录下的所有文件(如CSS, JavaScript, 图片)都会被当作静态资源直接对外提供访问。 - (9)
templates/
:这是存放动态视图模板的约定目录,比如Thymeleaf或FreeMarker的模板文件。 - (10)
application.properties
或application.yml
: 这是Spring Boot的核心配置文件,所有应用的配置项都写在这里。Spring Boot启动时会自动加载它。
- 测试目录 (
src/test
):- 遵循与主代码完全相同的包结构,用于存放单元测试和集成测试代码。
pom.xml
:- Maven项目的核心配置文件。我们在这里引入Spring Boot的起步依赖(Starters),并管理项目的基本信息和插件。
遵循这套标准的项目结构,不仅能让团队协作更顺畅,更能最大限度地利用Spring Boot“约定大于配置”的优势,让我们的开发工作事半功倍。
Spring Boot自动装配原理是什么?
面试官您好,Spring Boot的自动装配(Auto-Configuration)是它最核心、最具“魔力”的特性。它的根本目标,就是根据我们项目classpath中存在的jar包,来智能地、自动地为我们配置好各种所需的Bean,从而让我们摆脱繁琐的手动配置。
这个“魔法”的实现,主要依赖于以下几个核心组件和机制的协同工作:
1. 起点:@SpringBootApplication
注解
我们通常在主启动类上标注@SpringBootApplication
。这其实是一个组合注解,其中最重要的一个元注解就是:
@EnableAutoConfiguration
: 这就是开启自动装配功能的总开关。没有它,一切都不会发生。
2. “候选配置”的加载:spring.factories
机制
@EnableAutoConfiguration
本身并不包含具体的配置逻辑,它通过另一个注解@Import(AutoConfigurationImportSelector.class)
,来加载所有需要被自动配置的“候选者”。
AutoConfigurationImportSelector
:这个类的作用,就是去扫描我们项目所有依赖的jar包中,一个约定好的文件——META-INF/spring.factories
。spring.factories
文件:- 这是一个标准的Properties文件。在Spring Boot的各个
starter
(如spring-boot-autoconfigure.jar
)中,都包含了这个文件。 - 文件里,有一个关键的key:
org.springframework.boot.autoconfigure.EnableAutoConfiguration
。 - 这个key下面,用逗号分隔,列出了所有可能需要被自动配置的
@Configuration
类的全限定名。 - 例如:
- 这是一个标准的Properties文件。在Spring Boot的各个
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
- 工作流程:Spring Boot启动时,
AutoConfigurationImportSelector
会读取所有jar包里的spring.factories
文件,将这个key下的所有配置类名都加载到内存中,形成一个巨大的“自动配置候选列表”。
(注意:在Spring Boot 2.7及以后,spring.factories
机制被一个新的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件所取代,原理类似,但性能更好。面试时可以提一下,更能体现知识的与时俱进。)
3. “智能筛选”:@Conditional
系列注解
现在,我们有了一个包含上百个自动配置类的“候选列表”,但显然我们不需要所有的。Spring Boot如何智能地决定哪些该生效,哪些不该生效呢?
- 答案就是
@Conditional
条件注解。 - 几乎每一个自动配置类(比如
DataSourceAutoConfiguration
)或者其内部的@Bean
方法,都被一个或多个@ConditionalOn...
注解所修饰。 - 这些条件注解就像一个个“智能开关”:
@ConditionalOnClass
: “如果classpath中存在某个类,我这个配置才生效。”- 例子:
DataSourceAutoConfiguration
上就有@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
。这意味着,只有当你引入了数据库驱动(classpath中有了DataSource
类),Spring Boot才会尝试为你自动配置数据源。
- 例子:
@ConditionalOnMissingBean
: “如果容器中不存在某个类型的Bean,我才创建这个默认的Bean。”- 例子:在
DataSourceAutoConfiguration
内部,创建DataSource
的@Bean
方法上,有@ConditionalOnMissingBean(DataSource.class)
。这给了我们覆盖的权利:如果你自己定义了一个DataSource
的Bean,那么Spring Boot的自动配置就不会生效,会优先使用你自己的。
- 例子:在
- 其他常用条件:
@ConditionalOnProperty
(当配置文件中有某个属性时生效)、@ConditionalOnWebApplication
(当这是一个Web应用时生效)等等。
总结整个流程
- 启动:
@SpringBootApplication
中的@EnableAutoConfiguration
开启了自动装配。 - 加载:
AutoConfigurationImportSelector
通过扫描spring.factories
,加载了所有starter
中定义的“候选配置类”。 - 筛选:Spring在处理这个“候选列表”时,会逐一检查每个配置类上的
@Conditional
条件。 - 生效:只有满足所有条件的自动配置类,才会被容器加载,其内部定义的
@Bean
才会最终被创建和注册到IoC容器中。
通过这套 “约定(spring.factories
) + 条件判断(@Conditional
)” 的精巧机制,Spring Boot就实现了在几乎零配置的情况下,为我们的应用程序提供“按需服务”、“开箱即用”的强大能力。
说几个启动器(starter)?
面试官您好,Spring Boot的starter
(起步依赖)是其 “约定大于配置”理念的核心体现。它的本质就是一个“依赖管理全家桶” ,通过引入一个starter
,我们就能自动地把某个技术场景下所需的所有常用依赖(并且是版本兼容的)都引入进来,极大地简化了我们的Maven或Gradle配置。
在我的项目中,我用过非常多的starter
,可以按照它们解决的技术领域,举几个最典型的例子:
1. Web开发领域
spring-boot-starter-web
: 这是最常用、最基础的starter
。- 它提供了:构建一个标准Web应用所需的一切,包括Spring MVC框架、RESTful支持(通过Jackson进行JSON转换),以及一个内嵌的Tomcat服务器。
- 效果:只要引入它,我们就能立刻开始编写
@RestController
,并且可以直接将应用打包成可执行的JAR包来运行,无需部署到外部Web容器。
2. 数据访问领域
这个领域有非常丰富的starter
来适配不同的数据存储技术。
spring-boot-starter-data-jpa
:- 它提供了:用于实现基于JPA (Java Persistence API)规范的数据持久化。它默认会引入Hibernate作为JPA的实现,并包含
spring-data-jpa
、数据库连接池(如HikariCP)等。 - 效果:让我们能够通过定义
Repository
接口,以一种非常优雅、几乎不用写SQL的方式来操作数据库。
- 它提供了:用于实现基于JPA (Java Persistence API)规范的数据持久化。它默认会引入Hibernate作为JPA的实现,并包含
mybatis-spring-boot-starter
:- 它提供了:这是由MyBatis社区官方提供的,用于无缝集成MyBatis框架。
- 效果:它会自动配置好
SqlSessionFactory
和SqlSessionTemplate
,并能自动扫描我们的Mapper
接口并将其注册为Spring Bean,我们只需要在配置文件中提供数据源信息即可。
spring-boot-starter-data-redis
:- 它提供了:与Redis数据库集成的所有支持。它包含了Spring Data Redis模块和底层的Java客户端(如Lettuce或Jedis)。
- 效果:我们可以非常方便地通过
RedisTemplate
或者注解驱动的缓存(@Cacheable
)来操作Redis,实现高速缓存、分布式锁等功能。
3. 系统安全领域
spring-boot-starter-security
:- 它提供了:Spring Security框架的支持。
- 效果:一旦引入,我们的应用会立即获得基础的安全防护,比如默认的登录页面、HTTP Basic认证等。然后我们可以通过编写
SecurityConfigurerAdapter
配置类,来非常灵活地定制我们的认证和授权规则。
4. 测试领域
spring-boot-starter-test
:- 它提供了:进行单元测试和集成测试所需的全套工具。包括JUnit 5, Spring Test & Spring Boot Test, AssertJ(一个流式的断言库), Mockito(一个Mocking框架)等。
- 效果:让我们能够非常方便地编写测试用例,无论是对单个Bean的单元测试,还是启动一个完整的Spring容器进行集成测试。
总结
Spring Boot的starter
生态非常庞大,几乎覆盖了我们日常开发中可能用到的所有主流技术和框架。它的核心价值在于,将“技术集成”这个复杂、易错的过程,标准化、自动化了。我们开发者只需要在众多“全家桶套餐”中选择自己需要的,然后就可以直接开始烹饪我们的“主菜”(业务逻辑),而无需关心“厨房”的搭建和“食材”的采购问题。
写过Spring Boot Starter吗?
面试官您好,是的,我在之前的项目中有过编写自定义Spring Boot Starter的经验。当时我们是为了解决一个跨多个微服务团队的通用功能复用问题。
这个通用功能是一个定制化的操作日志记录模块。我们希望所有微服务都能以一种统一的、零配置的方式,轻松地集成这个日志功能。将它封装成一个Starter,是实现这个目标的最佳方式。
下面我来介绍一下我当时设计和实现这个Starter的完整思路和步骤。
第一步:明确Starter的核心目标与功能
我们的目标是,任何一个微服务,只需要在它的pom.xml
中引入我们这个log-spring-boot-starter
依赖,然后:
- 自动地在Spring容器中注册一个核心的
LogService
Bean。 - 自动开启一个AOP切面,通过一个自定义的
@LogRecord
注解,来拦截需要记录操作日志的方法。 - 提供一些可配置的属性,比如日志的输出格式、是否启用等,允许使用者在
application.yml
中进行定制。
第二步:创建项目结构
一个标准的Starter通常包含两个模块:
log-spring-boot-autoconfigure
(自动配置模块) :- 这是Starter的核心,包含了所有的自动配置逻辑(
@Configuration
类)、属性配置类(@ConfigurationProperties
)以及spring.factories
文件。 - 这个模块不包含任何具体的业务实现,它只负责“配置”。
- 这是Starter的核心,包含了所有的自动配置逻辑(
log-spring-boot-starter
(起步依赖模块):- 这是一个 “空壳” 的Maven项目,它里面几乎没有代码。
- 它的唯一作用,就是通过
pom.xml
的<dependencies>
,将log-spring-boot-autoconfigure
模块以及这个日志功能所需要的其他第三方依赖 (比如AOP相关的spring-boot-starter-aop
)聚合在一起。 - 使用者只需要引入这一个
starter
依赖,就能通过Maven的依赖传递,获得所有需要的东西。
第三步:实现自动配置模块 (autoconfigure
)
这是最关键的一步。
- 编写属性配置类 (
LogProperties.java
)
@ConfigurationProperties(prefix = "myapp.log")
public class LogProperties {private boolean enabled = true; // 提供一个总开关private String format = "default"; // 提供日志格式配置// ... getters and setters
}
这个类用于映射application.yml
中myapp.log.*
开头的配置。
- 编写核心业务Bean (
LogService.java
等)- 这里面是真正的日志记录逻辑的实现。
- 编写自动配置类 (
LogAutoConfiguration.java
)
@Configuration
// 只有当用户配置了 myapp.log.enabled=true (或者没配,默认为true)时,此配置才生效
@ConditionalOnProperty(prefix = "myapp.log", name = "enabled", havingValue = "true", matchIfMissing = true)
// 让我们的LogProperties生效
@EnableConfigurationProperties(LogProperties.class)
// 确保在AOP自动配置之后再配置我们的
@AutoConfigureAfter(AopAutoConfiguration.class)
public class LogAutoConfiguration {@Bean// 只有当容器中不存在LogService这个Bean时,才创建我们这个默认的@ConditionalOnMissingBeanpublic LogService logService(LogProperties properties) {// 根据配置属性来创建Servicereturn new LogServiceImpl(properties.getFormat());}@Bean@ConditionalOnMissingBeanpublic LogAspect logAspect(LogService logService) {// 创建AOP切面Beanreturn new LogAspect(logService);}
}
- 设计的关键:大量使用
@ConditionalOn...
注解,保证我们的自动配置足够“智能”,不与用户的自定义配置冲突。
- 创建
META-INF/spring.factories
文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.log.autoconfigure.LogAutoConfiguration
(我会补充一句:在Spring Boot 2.7之后,更推荐使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,原理类似但性能更好。)
- 在这个文件中,注册我们的自动配置类,告诉Spring Boot启动时需要加载它。
第四步:打包、发布和使用
- 将这两个模块打包并发布到公司的私有Maven仓库。
- 其他微服务团队,只需要在他们的
pom.xml
中添加一段简单的依赖:
<dependency><groupId>com.example</groupId><artifactId>log-spring-boot-starter</artifactId><version>1.0.0</version>
</dependency>
- 然后,他们就可以直接在自己的业务方法上使用
@LogRecord
注解,并且在application.yml
中通过myapp.log.enabled
来控制功能的开关,真正实现了“开箱即用”和“约定大于配置”。
通过这个过程,我深刻地理解了Spring Boot Starter不仅仅是一种技术,更是一种封装和分享最佳实践的强大思想。
Spring Boot里面有哪些重要的注解?还有一个配置相关的注解是哪个?
面试官您好,Spring Boot通过一套强大的注解体系,极大地简化了开发。在我的实践中,我习惯于把这些重要的注解分为几个核心类别:
1. 启动与自动配置注解
@SpringBootApplication
: 这是Spring Boot应用的“心脏”,通常标注在主启动类上。它本身是一个组合注解,至少包含了三个核心注解:@EnableAutoConfiguration
: 开启自动配置的总开关。@ComponentScan
: 启用组件扫描,默认扫描当前包及其子包。@SpringBootConfiguration
: 继承自@Configuration
,表明这个类也是一个Spring配置类。
2. Bean定义与依赖注入注解 (IoC/DI)
这些是Spring框架的基础,Spring Boot让它们用起来更方便。
@Component
及其衍生注解@Service
,@Repository
,@Controller
,@RestController
: 用于将类声明为Spring容器管理的Bean。@Autowired
: 用于自动注入依赖。
3. Web开发注解 (Spring MVC)
@RequestMapping
及其衍生注解@GetMapping
,@PostMapping
等:用于将HTTP请求映射到Controller方法。@PathVariable
,@RequestParam
,@RequestBody
: 用于从请求中提取数据。
4. 配置相关注解 (Configuration)
配置相关的注解,在Spring Boot中至关重要,因为它们是连接代码和 外部化配置(application.yml
) 的桥梁。
@Configuration
:- 这是定义Java配置类的核心注解。它告诉Spring,这个类是一个配置源,里面可以通过
@Bean
方法来定义Bean。这是替代传统XML配置的现代方式。
- 这是定义Java配置类的核心注解。它告诉Spring,这个类是一个配置源,里面可以通过
@Value("${...}")
:- 这是一个简单、直接的属性注入方式。它可以将配置文件中的单个属性值注入到Bean的字段中。
- 示例:
@Value("${server.port}")
可以获取服务器端口。 - 缺点:当需要注入的配置项很多时,代码会显得非常零散和重复。
@ConfigurationProperties(prefix = "...")
:- 这是我强烈推荐的、用于处理复杂配置的最佳实践。
- 作用:它提供了类型安全的、结构化的属性绑定。我们可以创建一个POJO类,用
@ConfigurationProperties
标记它,并指定一个配置前缀。 - 示例:
application.yml
中:
myapp:thread-pool:core-size: 10max-size: 200
Java配置类:
@Component // 或在@Configuration类中用@EnableConfigurationProperties
@ConfigurationProperties(prefix = "myapp.thread-pool")
public class ThreadPoolProperties {private int coreSize;private int maxSize;// getters and setters...
}
- 优势:
- 类型安全:Spring Boot会自动进行类型转换。
- 结构化:将相关的配置项聚合到一个对象中,代码更清晰、更易于维护。
- 强大的IDE支持:IDE可以对配置文件中的这些属性提供自动补全和语法高亮。
总结一下,虽然@Value
很方便,但在处理一组相关的配置时,使用 @Configuration
配合@Bean
和@ConfigurationProperties
的组合,是构建可维护、类型安全的配置的最佳方式。它完美地体现了Spring Boot在简化配置方面的设计哲学。
Spring Boot怎么开启事务?
面试官您好,在Spring Boot中开启和使用事务非常简单和直接,这得益于Spring Boot强大的自动化配置和约定大于配置的理念。
我们只需要做两件核心的事情:添加依赖和使用注解。
第一步:添加必要的“起步依赖” (Starter)
要使用Spring的声明式事务管理,我们首先需要确保项目中包含了数据访问和AOP相关的依赖。
- 添加JDBC Starter:
- 通常,我们会在
pom.xml
中引入spring-boot-starter-jdbc
或者spring-boot-starter-data-jpa
。
- 通常,我们会在
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- 为什么是这个? 因为
spring-boot-starter-jdbc
不仅包含了JDBC操作所需的一切,它还会传递性地引入spring-tx
模块,这正是Spring事务管理的核心。同时,它也包含了spring-boot-starter-aop
,为事务的AOP实现提供了基础。
- 添加数据库驱动:
- 当然,我们还需要添加具体的数据库驱动依赖,比如MySQL的:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
第二步:在主启动类上开启事务管理
- 使用
@EnableTransactionManagement
注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;@SpringBootApplication
@EnableTransactionManagement // 开启事务管理
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
- 我们需要在主启动类上,明确地加上
@EnableTransactionManagement
这个注解,来告诉Spring Boot:“请为我开启声明式事务管理的功能。”
- Spring Boot 2.x及以后的简化:
- 值得一提的是,在现代的Spring Boot版本中,只要我们引入了像
spring-boot-starter-jdbc
或spring-boot-starter-data-jpa
这样的starter
,Spring Boot的自动化配置 (DataSourceTransactionManagerAutoConfiguration
) 会自动地为我们开启事务管理。 - 所以,在很多情况下,即使我们不写
@EnableTransactionManagement
,事务也能正常工作。但从代码的可读性和明确性角度出发,我个人还是习惯于显式地加上这个注解。
- 值得一提的是,在现代的Spring Boot版本中,只要我们引入了像
第三步:在需要事务的方法上使用@Transactional
注解
- 这是最关键的一步。我们只需要在需要进行事务管理的方法(或者整个类)上,加上
@Transactional
注解即可。 - 最佳实践:通常,我们会将
@Transactional
注解加在Service层的public
方法上。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Autowiredprivate AccountDao accountDao;@Override@Transactional // 声明此方法需要事务管理public void registerUser(User user, Account account) {// 1. 插入用户记录userDao.insert(user);// 模拟一个异常if (account.getBalance() < 0) {throw new RuntimeException("账户余额不能为负!");}// 2. 插入账户记录accountDao.insert(account);}
}
- 工作原理:
- 当外部调用
registerUser
方法时,Spring AOP会拦截到这个调用。 - 它会发现这个方法被
@Transactional
标记了,于是会在方法执行前,开启一个数据库事务。 - 然后,它会执行方法体内的业务逻辑。
- 如果方法正常执行完毕,AOP会在方法返回后,提交事务。
- 如果方法执行过程中,抛出了
RuntimeException
或Error
(默认情况下),AOP会捕获到这个异常,并回滚事务 。在上面的例子中,userDao.insert(user)
的操作就会被撤销。
- 当外部调用
总结一下,在Spring Boot中开启事务,流程非常简单:
- 引入正确的
starter
(如spring-boot-starter-jdbc
)。 - (可选但推荐)在主启动类上加上
@EnableTransactionManagement
。 - 在需要事务的Service层
public
方法上,标注@Transactional
注解。
剩下的所有复杂的事务开启、提交、回滚逻辑,都由Spring Boot通过自动化配置和AOP为我们透明地完成了。
Spring Boot怎么做到导入就可以直接使用的?
面试官您好,Spring Boot之所以能做到“导入一个依赖就可以直接使用”,这背后是其两大核心机制——“起步依赖(Starters)”和“自动化配置(Auto-Configuration)”——在协同工作。
下面我来详细解释一下这两个机制是如何配合的:
第一步:“起步依赖”——打包好所有需要的“零件”
- 它解决了什么问题? 解决了传统开发的 “依赖地狱” 问题。在过去,我们要用一个技术(比如MyBatis),需要自己手动去
pom.xml
里添加mybatis.jar
,mybatis-spring.jar
,spring-jdbc.jar
等一大堆依赖,并且还要小心翼翼地处理它们之间的版本兼容性。 - Starter如何工作?
- Spring Boot提供了一系列名为
spring-boot-starter-*
的“起步依赖”。比如,我们想用MyBatis,只需要引入一个mybatis-spring-boot-starter
。 - 这个
starter
本身几乎没有代码,它最重要的作用,就是通过Maven的依赖传递机制,将使用MyBatis所需要的所有相关jar包(包括MyBatis自身、Spring的集成支持、AOP、JDBC等)都自动地、以一个兼容的版本引入到我们的项目中。
- Spring Boot提供了一系列名为
第二步:“自动化配置”——智能地“组装和配置”
- 它解决了什么问题? 解决了传统开发的 “配置地狱” 问题。过去,零件到齐了,我们还需要编写大量的XML或Java代码,去手动地将这些零件组装起来(比如配置
SqlSessionFactoryBean
,MapperScannerConfigurer
等)。 - Auto-Configuration如何工作?
@EnableAutoConfiguration
开关:我们主启动类上的@SpringBootApplication
注解,已经为我们打开了这个“自动安装”的总开关。spring.factories
清单:在我们引入的starter
的jar包中(比如mybatis-spring-boot-autoconfigure.jar
),有一个META-INF/spring.factories
文件。这个文件就像一张“安装说明书” ,里面写着:“如果你想自动配置MyBatis,请加载我这个MybatisAutoConfiguration
配置类。”@Conditional
智能判断:Spring Boot启动时,会加载这张“安装说明书”里列出的所有自动配置类。但它并不会无脑地全部生效。每个自动配置类(如MybatisAutoConfiguration
)都带有一系列@ConditionalOn...
的条件注解。- 比如,
@ConditionalOnClass(SqlSessionFactory.class)
会检查:“你的项目里(classpath)是不是真的有MyBatis的核心类?” - 比如,
@ConditionalOnMissingBean(SqlSessionFactory.class)
会检查:“用户是不是已经自己配置了一个SqlSessionFactory
?如果配了,那我就不重复配置了,优先用用户的。”
- 比如,
- 自动创建Bean:只有当所有条件都满足时,这个自动配置类才会生效。它内部会使用
@Bean
注解,自动地为我们创建和配置好所有需要的Bean(比如SqlSessionFactory
),并注册到Spring容器中。
总结:为什么导入就能用?
整个流程可以总结为:
- 你引入了一个
starter
(买了一个大礼包)。 - 这个
starter
自动地把所有需要的依赖jar包(零件) 带进了你的项目。 - Spring Boot启动时,看到了这些新来的jar包(零件),于是触发了对应的自动化配置(自动安装程序)。
- 这个“自动安装程序”检查了一下你家里的环境(
@Conditional
判断),发现条件都满足。 - 于是,它自动地把这些零件组装好(创建并配置好Bean),并放到了Spring容器中。
最终,我们作为开发者,什么都不用做,只需要通过@Autowired
就可以直接注入并使用这个已经配置好的SqlSessionFactory
或Mapper
接口了。这就是Spring Boot“约定大于配置”理念的强大之处。
Spring Boot 过滤器和拦截器说一下?
面试官您好,过滤器(Filter)和拦截器(Interceptor)都是Web开发中用于实现横切关注点(Cross-Cutting Concerns) 的重要工具,比如日志记录、权限校验、数据编码等。
但它们是两个完全不同层面的东西,我通常会从它们的归属、功能范围、执行时机和使用方式这几个维度来区分它们。
一个核心的比喻:大楼安检与楼层门禁
我们可以把一次Web请求的处理过程,想象成进入一栋大楼:
- 过滤器 (Filter):就像是整栋大楼的入口安检系统。
- 拦截器 (Interceptor):就像是进入具体某个公司或楼层的内部刷卡门禁。
1. 归属与依赖关系 (出身不同)
- 过滤器 (Filter):
- 归属:它是Java Servlet规范的一部分(
javax.servlet.Filter
),不依赖于任何特定的框架。只要是支持Servlet的Web容器(如Tomcat),都可以使用它。 - 定位:是一个更底层的、与框架无关的组件。
- 归属:它是Java Servlet规范的一部分(
- 拦截器 (Interceptor):
- 归属:它是Spring MVC框架提供的一个组件(
org.springframework.web.servlet.HandlerInterceptor
)。 - 定位:它强依赖于Spring的IoC容器和MVC框架,是一个更高层的、与Spring紧密集成的组件。
- 归属:它是Spring MVC框架提供的一个组件(
2. 功能范围与能访问到的信息 (权限不同)
- 过滤器 (Filter):
- 能做什么:因为工作在最外层,它可以对任何进入容器的请求进行过滤,甚至可以修改请求(Request)和响应(Response)的内容。
- 能访问什么:它只能访问到最原始的
HttpServletRequest
和HttpServletResponse
对象。它不知道这个请求最终会被哪个Controller的哪个方法处理,也无法直接访问到Spring容器中的任何Bean。
- 拦截器 (Interceptor):
- 能做什么:它只拦截被Spring MVC的
DispatcherServlet
所分派的请求。 - 能访问什么:它的权限要大得多。因为它本身就是Spring容器管理的一个Bean,所以它可以轻松地通过
@Autowired
注入任何其他的Service或Component。在postHandle
方法中,它还能访问到即将返回给视图的ModelAndView
对象 ,可以修改模型数据。
- 能做什么:它只拦截被Spring MVC的
3. 执行时机与生命周期 (工作流程不同)
这是一个非常关键的区别,也印证了“大楼安检与楼层门禁”的比喻。
- 执行流程:一个请求过来,其执行顺序是:
请求 -> Filter链 -> DispatcherServlet -> Interceptor链前置处理(preHandle
) -> Controller -> Interceptor链后置处理(postHandle
) -> 视图渲染 -> Interceptor链完成处理(afterCompletion
) -> Filter链 -> 响应 - 过滤器 (Filter):
- 它的执行时机非常靠前,在请求进入
DispatcherServlet
之前。 - 它的
doFilter
方法将整个请求处理过程包裹起来。
- 它的执行时机非常靠前,在请求进入
- 拦截器 (Interceptor):
- 它的执行被细分为了三个阶段,嵌入在Controller的执行前后:
preHandle
: 在Controller方法执行之前调用。如果它返回false
,则整个请求处理流程会被中断。非常适合做权限校验。postHandle
: 在Controller方法执行之后,视图渲染之前调用。我们可以在这里修改ModelAndView
。afterCompletion
: 在整个请求处理完成(包括视图渲染)之后调用。非常适合做一些资源清理工作。
- 它的执行被细分为了三个阶段,嵌入在Controller的执行前后:
总结与选型建议
对比维度 | 过滤器 (Filter) | 拦截器 (Interceptor) |
---|---|---|
归属 | Servlet规范 | Spring MVC框架 |
依赖 | 无,Web容器即可 | 依赖Spring IoC容器 |
拦截范围 | 所有请求 | 仅Spring MVC处理的请求 |
可访问信息 | 原始的Request/Response | Request/Response, Spring Beans, ModelAndView |
执行时机 | 请求进入Servlet之前 | Controller方法执行前后 |
功能粒度 | 较粗,包裹整个请求 | 更细,分为前、后、完成三个阶段 |
我的选型策略:
- 当需要实现一些与框架无关的、通用的、底层的请求处理时,我会选择过滤器 (Filter)。
- 典型场景:字符编码统一处理、跨域(CORS)配置、XSS攻击防护等。
- 当需要实现与业务逻辑紧密相关、并且需要使用Spring容器中的服务(如用户认证、权限校验)时,我一定会选择拦截器 (Interceptor)。
- 典型场景:
- 登录校验:在
preHandle
中检查Session或Token,如果未登录,则直接中断请求。 - 权限验证:在
preHandle
中获取当前用户信息,并检查其是否有权限访问即将被调用的Controller方法。 - 在向视图传递数据前,添加一些公共的模型属性:在
postHandle
中实现。
- 登录校验:在
- 典型场景:
简单来说,过滤器管“入口”,做通用粗粒度处理;拦截器管“业务”,做精细化、与Spring集成的处理。
参考小林coding和JavaGuide