Spring入门
SpringBoot 常见面试题总结
简单介绍一下 Spring?有啥缺点?
- Spring 是一个重量级企业开发框架,是 Enterprise JavaBean(EJB)的替代品。Spring 为企业级 Java 开发提供了一种相对简单的方法,通过 依赖注入 和 面向切面编程(AOP),用简单的 Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。
- 缺点:虽然 Spring 的组件代码是轻量级的,但它的配置(特别是 XML 配置)可能会显得比较繁琐和复杂。
为什么要有 SpringBoot?
- Spring 旨在简化 J2EE 企业应用程序开发,而 Spring Boot 进一步简化了 Spring 开发,减少了配置文件,提供了开箱即用的开发体验。
使用 Spring Boot 的主要优点
- 开发基于 Spring 的应用程序更加简便。
- Spring Boot 项目的开发或工程时间显著减少,提高了整体生产力。
- 不需要编写大量样板代码、XML 配置和注解。
- Spring Boot 遵循“固执己见的默认配置”,减少开发工作量。
- Spring Boot 应用程序提供嵌入式 HTTP 服务器(如 Tomcat 和 Jetty),可以轻松开发和测试 Web 应用程序。
- 提供命令行接口(CLI)工具,用于开发和测试 Spring Boot 应用程序,如 Java 或 Groovy。
什么是 Spring Boot Starters?
- Spring Boot Starters 是一系列依赖关系的集合,简化了项目的依赖管理。
- 例如:开发 REST 服务或 Web 应用程序时,只需添加一个
spring-boot-starter-web
依赖,它会包含开发 REST 服务所需的所有依赖。
Spring Boot 支持哪些内嵌 Servlet 容器?
- Spring Boot 支持以下内嵌的 Servlet 容器:
- Tomcat
- Jetty
- Undertow
如何在 Spring Boot 应用程序中使用 Jetty 而不是 Tomcat?
- Spring Boot 默认使用 Tomcat 作为嵌入式 Servlet 容器。要使用 Jetty,只需在
pom.xml
(Maven)或build.gradle
(Gradle)中移除spring-boot-starter-tomcat
并添加spring-boot-starter-jetty
依赖。
介绍一下 @SpringBootApplication 注解
@SpringBootApplication
是一个组合注解,包含了以下三个注解:- @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类。
- @EnableAutoConfiguration:启用 Spring Boot 的自动配置机制。
- @ComponentScan:扫描被
@Component
(如@Service
,@Controller
)注解的 bean,默认会扫描该类所在的包下所有的类。
Spring Boot 的自动配置是如何实现的?
@EnableAutoConfiguration
是启动自动配置的关键。@EnableAutoConfiguration
注解通过 Spring 提供的@Import
注解导入了AutoConfigurationImportSelector
类。AutoConfigurationImportSelector
类中的getCandidateConfigurations
方法会返回所有自动配置类的信息,这些配置信息会被 Spring 容器作为 bean 管理。- 自动配置类通常使用
@Conditional
注解进行条件判断,以决定是否需要配置某些 bean。例如,@ConditionalOnClass
会检查指定的类是否存在于类路径中,@ConditionalOnBean
会检查容器中是否有指定的 bean。
开发 RESTful Web 服务常用的注解有哪些?
- @RestController:是
@Controller
和@ResponseBody
的组合注解,用于将函数的返回值直接填入 HTTP 响应体中,表示 REST 风格的控制器。 - @GetMapping:用于处理 GET 请求。
- @PostMapping:用于处理 POST 请求。
- @PutMapping:用于处理 PUT 请求。
- @DeleteMapping:用于处理 DELETE 请求。
- @RequestParam 和 @PathVariable:
@PathVariable
用于获取路径参数,@RequestParam
用于获取查询参数。 - @RequestBody:用于将请求体(可能是 POST, PUT, DELETE, GET 请求)中的 JSON 数据绑定到 Java 对象。
Spring Boot 常用的两种配置文件
- application.properties 和 application.yml 是 Spring Boot 应用程序的主要配置文件。
- YAML 配置文件不支持通过
@PropertySource
注解导入自定义的 YAML 配置。
Spring Boot 常用的读取配置文件的方法有哪些?
- @Value:用于读取简单的配置信息(不推荐)。
- @ConfigurationProperties:用于将配置与 bean 绑定。可以通过
@Component
注解或@EnableConfigurationProperties
注册配置 bean。 - @PropertySource:用于读取指定的 properties 文件。
Spring Boot 加载配置文件的优先级
- Spring Boot 加载配置文件的优先级从高到低如下:
- 外部配置(命令行参数,环境变量)
application.properties
或application.yml
application-{profile}.properties
或application-{profile}.yml
- 内部配置(类路径下的配置文件)
常用的 Bean 映射工具有哪些?
- 在代码中经常需要将一个数据结构封装成 DO、SDO、DTO、VO 等,常用的 Bean 映射工具有:
- Spring BeanUtils
- Apache BeanUtils
- MapStruct
- ModelMapper
- Dozer
- Orika
- Jmapper
- MapStruct 性能较好且使用灵活,是一个不错的选择。
Spring Boot 如何监控系统实际运行状况?
- 可以使用 Spring Boot Actuator 来监控 Spring Boot 项目的运行状态。
- 集成 Actuator 后,Spring Boot 应用程序自带了一些开箱即用的 API,可以获取应用程序运行时的内部状态信息。
Spring Boot 如何做请求参数校验?
Spring Boot 通过
spring-boot-starter-web
提供请求参数校验功能,包含以下校验注解:- @Null:被注释的元素必须为
null
。 - @NotNull:被注释的元素必须不为
null
。 - @AssertTrue:被注释的元素必须为
true
。 - @AssertFalse:被注释的元素必须为
false
。 - **@Min(value)**:被注释的元素必须是一个数字,其值必须大于等于指定的最小值。
- **@Max(value)**:被注释的元素必须是一个数字,其值必须小于等于指定的最大值。
- **@Size(max=, min=)**:被注释的元素的大小必须在指定的范围内。
- **@Digits(integer, fraction)**:被注释的元素必须是一个数字,其值必须在可接受的范围内。
- @Past:被注释的元素必须是一个过去的日期。
- @Future:被注释的元素必须是一个将来的日期。
- **@Pattern(regex=, flag=)**:被注释的元素必须符合指定的正则表达式。
- @Null:被注释的元素必须为
Hibernate Validator 提供的校验注解:
- @NotBlank:验证字符串非 null,且长度必须大于 0。
- @Email:被注释的元素必须是电子邮箱地址。
- **@Length(min=, max=)**:被注释的字符串的大小必须在指定的范围内。
- @NotEmpty:被注释的字符串必须非空。
- **@Range(min=, max=)**:被注释的元素必须在合适的范围内。
验证请求体(RequestBody):
- 在需要验证的参数上加上
@Valid
注解,如果验证失败,将抛出MethodArgumentNotValidException
,Spring 会将此异常转换为 HTTP Status 400(错误请求)。
- 在需要验证的参数上加上
验证请求参数(Path Variables 和 Request Parameters):
- 在类上加上
@Validated
注解,告诉 Spring 去校验方法参数。
- 在类上加上
如何使用 Spring Boot 实现全局异常处理?
- 可以使用
@ControllerAdvice
和@ExceptionHandler
注解处理全局异常。
Spring Boot 中如何实现定时任务?
- 可以使用
@Scheduled
注解创建定时任务。
Spring
IoC(控制反转)
控制反转(Inversion of Control, IoC) 是指将对象创建的控制权交给 Spring 框架来管理。
- 控制:对象创建(实例化、管理)的权力。
- 控制权交给外部环境(Spring 框架、IoC 容器)。
IoC 容器会实例化对象并将对象存储起来,在需要使用的时候,直接向 IoC 容器索要对象即可。
IoC 容器实际上是一个
Map(key, value)
,Map 中存放的是各种对象。
解决的问题
- 对象之间的耦合度高。
- 对象资源的管理困难。
设计思想
- 将原本在程序中手动创建对象的控制权交给第三方(如 IoC 容器)。
IoC 容器的实现类
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
ApplicationContext
BeanFactory
AOP(面向切面编程)
- AOP 是 OOP(面向对象编程)的一种延续,旨在将横切关注点(如日志记录、事务管理、权限控制、接口限流等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等方式,实现代码的复用和解耦,提高代码的可维护性和可扩展性。
AOP 关键术语
横切关注点:多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流等)。
切面:对横切关注点进行封装的类,一个切面是一个类,可以定义多个通知,用于实现具体的功能。
连接点(JointPoint):方法调用或者方法执行时的某个特定时刻。
通知(Advice):切面在某个连接点要执行的操作。通知有五种类型:
- 前置通知(Before)
- 后置通知(After)
- 返回通知(AfterReturning)
- 异常通知(AfterThrowing)
- 环绕通知(Around)
切点(Pointcut):一个表达式,用于匹配哪些连接点需要被切面所增强。切点通过注解、正则表达式、逻辑运算等方式来定义。
织入:将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。
- 编译期织入
- 运行期织入
AOP 的应用场景
- 日志记录
- 性能统计
- 事务管理(
@Transactional
) - 权限控制(
@PreAuthorize
) - 接口限流
- 缓存管理
AOP 的实现方式
动态代理:
- 如果被代理对象实现了某个接口,Spring AOP 会使用 JDK Proxy 创建代理对象;如果没有实现接口,则使用 Cglib 生成一个被代理对象的子类,在运行时增强,是代理模式的应用。
字节码操作:
- AspectJ 基于字节码操作,在编译时增强。如果切面较少,差异不大;如果切面较多,AspectJ 的性能更优。
AspectJ
- AspectJ 是一个面向切面编程的框架,拓展了 Java 语言,定义了 AOP 语法,有专门的编译器生成遵守 Java 字节码规范的 Class 文件。
切面的执行顺序
- 使用
@Order
注解。 - 实现
Ordered
接口并重写getOrder
方法。
Spring Bean 的生命周期
- Spring 中的 Bean 指的是那些被 IoC 容器管理的对象。
Autowired 和 Resource 的区别
@Autowired
默认是通过实例的类型注入的,如果 IoC 容器中存在多个相同类型的类,且不能通过变量名字匹配,将会报错。@Resource
默认是通过变量名字注入的,如果无法通过名称注入,会通过类型注入。可以通过注解的参数name
和type
来设定注入 bean 的名字和类型。- 可以使用
@Qualifier
注解配合@Autowired
来显式指定名称而不依赖变量名称。
Bean 的作用域
- singleton:默认都是单例模式。
- prototype:每次获取都是不同的。
- request:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP Request 中有效。
- session:每一次来自新 Session 的请求都会产生一个 bean,该 bean 仅在 HTTP session 内有效。
- application/global-session:Web 应用在启动的时候创建一个 Bean,该 bean 仅在当前应用启动时间内有效。
- websocket:每一次 WebSocket 会话产生一个新的 bean。
Bean 的生命周期
- 实例化:创建 bean 实例。
- 属性注入:注入 bean 的依赖属性。
- 初始化:执行
@PostConstruct
、InitializingBean.afterPropertiesSet()
、自定义的初始化方法。 - 销毁:执行
@PreDestroy
、DisposableBean.destroy()
、自定义的销毁方法。
Bean 是线程安全的吗?
- 在
prototype
作用域下,每次获取都会创建一个新的 bean,不存在资源竞争问题,不存在线程安全问题。 - 在
singleton
作用域下,IoC 容器只有唯一的 bean 实例,存在资源竞争问题。若这个 bean 有状态(包含可变的成员变量对象),存在线程安全问题。- 避免在 bean 中定义可变的成员变量。
- 可以在类中定义一个
ThreadLocal
变量,将需要的可变成员变量保存在ThreadLocal
中。
Bean 的生命周期细节
- 实例化:Spring 通过反射机制创建 bean 的实例。
- 属性注入:Spring 根据 bean 的定义注入属性,支持构造器注入、Setter 注入和自动注入。
- 初始化:
- 实现了
BeanFactoryPostProcessor
接口的 bean 会在其他 bean 加载前调用postProcessBeanFactory
方法。 - 实现了
InstantiationAwareBeanPostProcessor
接口的 bean 会在实例化之前调用postProcessBeforeInstantiation
方法。 - 调用
BeanNameAware
接口的setBeanName
方法设置 bean 的名称。 - 调用
BeanFactoryAware
接口的setBeanFactory
方法设置BeanFactory
。 - 调用
BeanPostProcessor
的postProcessBeforeInitialization
方法进行前置处理。 - 调用
InitializingBean
接口的afterPropertiesSet
方法。 - 调用自定义的初始化方法(通过
init-method
或@PostConstruct
注解定义)。 - 调用
BeanPostProcessor
的postProcessAfterInitialization
方法进行后置处理(包括 AOP 增强)。
- 实现了
- 销毁:
- 实现了
DisposableBean
接口的 bean,调用destroy()
方法。 - 配置了
destroy-method
的 bean 调用自定义的销毁方法,或使用@PreDestroy
注解定义的销毁方法。
- 实现了
Spring MVC 的核心组件
- DispatcherServlet:核心的前端控制器,负责接收请求、分发请求并响应客户端。
- HandlerMapping:处理器映射器,根据 URL 匹配查找能处理的 Handler,并将请求涉及的拦截器和 Handler 一起封装。
- HandlerAdapter:处理器适配器,适配并执行 Handler。
- ViewResolver:视图解析器,根据 Handler 返回的逻辑视图,解析并渲染真正的视图。
Spring MVC 的请求处理流程
- DispatcherServlet 拦截客户端请求。
- DispatcherServlet 根据请求信息调用 HandlerMapping,查找匹配的 Handler。
- HandlerAdapter 执行匹配到的 Handler。
- Handler 处理请求,返回一个 ModelAndView 对象,包含数据模型和视图信息。
- ViewResolver 根据逻辑视图查找实际视图。
- DispatcherServlet 渲染视图,将 Model 数据传递给视图进行渲染。
- 将渲染后的视图返回给客户端。
统一异常处理
- 使用以下注解:
- @ControllerAdvice:全局异常处理器,捕获 Controller 中的异常。
- @ExceptionHandler:用于定义具体的异常处理方法。
Spring 框架用到的设计模式
- 工厂设计模式:使用
BeanFactory
、ApplicationContext
创建 bean 对象。 - 代理设计模式:用于 Spring AOP 功能的实现。
- 单例设计模式:Spring 中的 bean 默认是单例的。
- 模板方法设计模式:如
JdbcTemplate
、RedisTemplate
等模板类。 - 包装器设计模式:用于连接多个数据库,根据不同客户在每次访问时访问不同的数据库。
- 观察者设计模式:Spring 事件驱动模型。
- 适配器模式:Spring AOP 的通知(Advice)使用了适配器,Spring MVC 也使用了适配器适配 Controller。
Spring 事务管理
事务
- 事务 是逻辑上的一组操作,要么都执行,要么都不执行。事务能否生效取决于数据库引擎是否支持事务(如 MySQL 的 InnoDB 支持事务,而 MyISAM 不支持)。
Spring 事务管理接口
- PlatformTransactionManager:Spring 事务策略的核心接口,定义了三个方法:
TransactionStatus getTransaction(TransactionDefinition definition)
:获取事务。void commit(TransactionStatus status)
:提交事务。void rollback(TransactionStatus status)
:回滚事务。
TransactionDefinition:事务属性
隔离级别:决定一个事务在处理数据时的隔离程度。
- ISOLATION_DEFAULT:使用数据库的默认隔离级别。
- ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据,可能导致脏读、幻读或不可重复读。
- ISOLATION_READ_COMMITTED:允许读取已提交的数据,可以防止脏读,但可能会导致不可重复读和幻读。
- ISOLATION_REPEATABLE_READ:保证多次读取结果的一致性,可以防止脏读和不可重复读,但可能导致幻读。
- ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别,所有事务依次执行,防止所有并发问题,但性能最差。
传播行为:解决业务层方法之间互相调用的事务问题。
- PROPAGATION_REQUIRED:默认传播行为,支持当前事务,如果没有就创建一个新事务。
- PROPAGATION_SUPPORTS:支持当前事务,如果没有就以非事务方式运行。
- PROPAGATION_MANDATORY:支持当前事务,如果没有则抛出异常。
- PROPAGATION_REQUIRES_NEW:创建一个新事务,如果当前存在事务则挂起当前事务。
- PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务则挂起当前事务。
- PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务则抛出异常。
- PROPAGATION_NESTED:嵌套事务,在当前事务中嵌套执行,如果当前没有事务则创建一个新事务。
回滚规则:定义哪些异常会导致事务回滚。默认情况下,遇到运行时异常(
RuntimeException
)和Error
时回滚,但遇到检查型异常时不会回滚。只读属性:对于只有读取数据的事务,可以指定为只读事务,数据库会进行一些优化。
事务超时:事务所允许执行的最长时间,超时后自动回滚事务。
TransactionStatus:事务状态
- **
isNewTransaction()
**:是否是新的事务。 - **
hasSavepoint()
**:是否有保存点。 - **
setRollbackOnly()
**:设置为只回滚。 - **
isRollbackOnly()
**:是否为只回滚。 - **
isCompleted()
**:事务是否已完成。
@Transactional 的使用注意事项
@Transactional
注解只作用在public
方法上,事务才生效,不推荐在接口上使用。- 避免同一个类中调用
@Transactional
注解的方法,这样会导致事务失效。 - 正确设置
@Transactional
的rollbackFor
和propagation
属性,否则事务可能回滚失败。 - 使用
@Transactional
的类必须由 Spring 管理,否则事务不生效。 - 使用的数据库必须支持事务机制。
@Autowired 和 @Resource 的区别
@Autowired
和 @Resource
是 Spring 框架中用于依赖注入的两个常用注解,它们之间有一些重要的区别:
- 来源不同
- **
@Autowired
**:属于 Spring 框架提供的注解,是 Spring 核心的一部分。 - **
@Resource
**:属于 Java EE(JSR-250)标准的注解,由javax.annotation
包提供。
- 注入方式不同
**
@Autowired
**:- 默认按类型(by type)注入。如果 Spring 容器中存在多个类型相同的 Bean,可以通过
@Qualifier
注解来指定注入的具体 Bean。 - 支持构造器、字段、方法的依赖注入。
- 默认情况下,
@Autowired
是必须找到一个匹配的 Bean 的。如果容器中没有找到对应的 Bean,Spring 会抛出NoSuchBeanDefinitionException
。可以通过设置required
属性为false
来允许非必须注入。
private MyBean myBean;
- **` `**:
- 默认按名称(by name)注入。它首先会根据名称查找对应的 Bean,如果找不到,再根据类型查找。
- 支持名称注入(`name` 属性)和类型注入(`type` 属性),但不能和 ` ` 一样在同一个注解中同时使用。
- 如果没有指定 `name` 或 `type`,则默认按照字段名称进行注入。
```java
private MyBean myBean;- 默认按类型(by type)注入。如果 Spring 容器中存在多个类型相同的 Bean,可以通过
- 默认行为
- **
@Autowired
**:默认是按类型注入。如果你想要按名称注入,可以结合@Qualifier
注解使用。 - **
@Resource
**:默认是按名称注入。如果找不到对应名称的 Bean,再按类型注入。
- 支持的注入类型
- **
@Autowired
**:支持构造函数注入、方法注入和字段注入。 - **
@Resource
**:通常用于字段和 setter 方法注入,不支持构造函数注入。
- 使用场景
- **
@Autowired
**:在需要使用 Spring 特有的功能,如@Qualifier
、@Primary
等时,使用@Autowired
更合适。 - **
@Resource
**:如果需要确保与 Java EE 标准的兼容性,或者项目中同时使用了 Spring 和 Java EE 框架,使用@Resource
更为合适。
- 优先级
- 在
@Resource
注解中,如果同时指定了name
和type
属性,容器会优先根据name
属性查找 Bean。
// 使用 @Autowired |
总结
@Autowired
更适合使用 Spring 框架特性并依赖 Spring 提供的自动装配功能。@Resource
更符合 Java EE 标准,通常在需要与标准兼容的场景下使用。
Spring Bean
Bean 的作用域有哪些?
- singleton:IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype:每次获取都会创建一个新的 bean 实例。即连续
getBean()
两次,得到的是不同的 Bean 实例。 - request(仅 Web 应用可用):每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP 请求中有效。
- session(仅 Web 应用可用):每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session(仅 Web 应用可用):Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket(仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
如何配置 Bean 的作用域?
XML 方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
Bean 的生命周期
- 实例化 Bean:通过反射机制创建 bean 实例。
- Bean 内部的属性赋值:注入 bean 的依赖属性。
- 初始化:执行
@PostConstruct
、InitializingBean.afterPropertiesSet()
、自定义的初始化方法。 - 销毁:执行
@PreDestroy
、DisposableBean.destroy()
、自定义的销毁方法。