Spring 获取单例 Bean 的过程(创建期间)
什么是循环依赖
循环依赖就是两个或者两个以上的 Bean 相互依赖
什么是 Spring IOC?
Spring IOC 是一种控制反转的思想,将创建对象的权利交给 Spring,由 Spring 进行管理对象的生命周期,当要用到这个对象的时候,Spring 会把这个对象交给你
Spring IOC 有什么好处
ioc 的思想最核心的地方在于,资源不由使用资源者管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。
Spring 中的 DI 是什么?
DI 是 IOC 的一种实现方式
DI ,Dependency Injection,依赖注入
在容器运行的时候,可以找到被依赖的对象,然后将其注入,通过这样的方式,使得各对象之间的关系可由运行期决定,而不用在编码的时候明确
什么是 Spring Beans?
由 Spring 创建并管理的对象就是 Spring Bean
Spring 中的 BeanFactory 是什么?
BeanFactory 用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理
Spring 中的 FactoryBean 是什么?
FactoryBean 是一个接口,可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean,简单来说就是 可以封装 bean,它提供了更加灵活的初始化定制功能
Spring 中的 ObjectFactory 是什么?
ObjectFactory 是一个工厂,主要功能之一是支持懒加载,Bean 实例仅在调用 getObject() 方法时才会创建或获取
Spring Bean 一共有几种作用域?
[图片上传失败...(image-a1110c-1729518417405)]
Spring 一共有几种注入方式?
例子
// 构造注入
@Component
public class MyService {
private final MyDependency myDependency;
public MyService(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
// set 注入
@Component
public class MyService {
private MyDependency myDependency;
@Autowired
public void setMyDependency(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
// 字段注入
@Component
public class MyService {
@Autowired
private MyDependency myDependency;
}
// 方法注入
public class MyService {
public void performAction(@Autowired MyDependency myDependency) {
myDependency.doSomething();
}
}
[图片上传失败...(image-202fce-1729518417405)]
什么是 AOP?
AOP 就是面向切面编程,将一些通用的逻辑集中实现,然后通过 AOP 进行逻辑的切入,减少了零散的碎片化代码,提高了系统的可维护性。
可以理解为:通过代理的方式,在调用想要的对象方法时候,进行拦截处理,执行切入的逻辑,然后再调用真正的方法实现
说下 Spring Bean 的生命周期
详细步骤
Spring Bean 生命周期详细步骤
1.1 实例化阶段:
Bean 的实例化是通过反射机制创建的。Spring 根据 @Component
、@Bean
或者 XML 中的 <bean>
元素配置,来确定要创建的 Bean。
1.2 属性赋值阶段:
在实例化完成后,Spring 会进行依赖注入。这包括将属性值注入到 Bean 的字段中,可能是通过构造函数注入、setter 方法注入,或者直接字段注入。
1.3 初始化前的扩展机制:
Bean 可以实现 BeanNameAware
、BeanFactoryAware
等 Aware
接口,从而在初始化之前获取 Bean 的名称、BeanFactory、ApplicationContext 等容器资源。例如,ApplicationContextAware
接口允许 Bean 获取 ApplicationContext
,以便进一步与 Spring 容器交互。
1.4 BeanPostProcessor 的作用:
BeanPostProcessor
接口允许开发者在 Bean 初始化前后添加自定义逻辑。例如,可以在 postProcessBeforeInitialization
方法中执行某些前置操作,如代理包装、AOP 切面等。在 postProcessAfterInitialization
中,可以进一步修改或替换 Bean 实例。
1.5 初始化的细节:
InitializingBean
接口提供了一个 afterPropertiesSet
方法,用于在 Bean 的所有属性设置完成后执行一些自定义初始化逻辑。开发者也可以通过 @PostConstruct
注解或者 XML/Java 配置中的 init-method
属性,来指定初始化方法。
1.6 Bean 的就绪状态:
Bean 完成初始化后,即进入就绪状态,可以供应用程序使用。在此状态下,Bean 已经完成了所有的属性设置和初始化步骤,处于可用状态。
1.7 销毁阶段的清理:
Bean 的销毁通常在容器关闭时进行。DisposableBean
接口提供了 destroy
方法,用于清理资源。开发者也可以通过 @PreDestroy
注解或配置中的 destroy-method
属性,指定清理逻辑。
Bean 的作用域(Scope)与生命周期的关系
Spring Bean 的生命周期还与其作用域密切相关:
3.1 Singleton(单例) :
- 默认作用域,Bean 的生命周期与 Spring 容器的生命周期一致。在容器启动时创建,在容器关闭时销毁。
3.2 Prototype(原型) :
- 每次请求时创建一个新的 Bean 实例,容器只负责创建,不管理其生命周期(不调用销毁方法)。
3.3 Request、Session、Application、WebSocket:
- 这些作用域用于 Web 应用中,Bean 的生命周期分别与 HTTP 请求、会话、应用或 WebSocket 的生命周期一致。
Spring AOP 默认用的是什么动态代理,两者有什么区别?
Spring AOP 默认使用 JDK 动态代理,但是 SpringBoot 2.X 版本都改成使用 CGLIB 动态代理
区别:
JDK 动态代理是基于接口的,所以要求代理类一定是有定义接口的
CGLIB 基于 ASM 字节码生成工具,它是通过继承的方式来实现代理类,所以要注意 final 方法
Spring Aop 和 AspectJ 有什么区别?
Spring Aop 是动态代理,运行时织入,Spring Aop 只支持方法级别的织入
AspectJ 是静态代理,编译时织入,而 AspectJ 支持字段、方法、构造函数等等,所以它更加强大,当然也更加复杂
你了解的 Spring 都用到哪些设计模式?
- 工厂模式,从名字就能看出来是 BeanFactory,整个 Spring IOC 就是一个工厂。
- 模板方法,例如 JdbcTemplate、RestTemplate,名字是 xxxTemplate 的都是模板。
- 代理模式,AOP 整个都是代理模式。
- 单例模式,默认情况下 Bean 都是单例的。
- 责任链模式,比如 Spring MVC 中的拦截器,多个拦截器串联起来就形成了责任链。
- 观察者模式,在 Spring 中的监听器实现。
- 适配器模式,在 Spring MVC 中提到的 handlerAdapter 其实就是适配器。
Spring 事务有几个隔离级别?
Spring 提供了五种事务隔离级别:
-
DEFAULT(默认):使用底层数据库的默认隔离级别。如果数据库没有特定的设置,通常默认为
READ_COMMITTED
。 - READ_UNCOMMITTED(读未提交):最低的隔离级别,允许事务读取尚未提交的数据,可能会导致脏读、不可重复读和幻读。
- READ_COMMITTED(读已提交):仅允许读取已经提交的数据,避免了脏读,但可能会出现不可重复读和幻读问题。
- REPEATABLE_READ(可重复读):确保在同一个事务内的多次读取结果一致,避免脏读和不可重复读,但可能会有幻读问题。
- SERIALIZABLE(可串行化):最高的隔离级别,通过强制事务按顺序执行,完全避免脏读、不可重复读和幻读,代价是性能显著下降。
Spring 有哪几种事务传播行为?
- PROPAGATION_REQUIRED(默认) 如果当前存在事务,则用当前事务,如果没有事务则新起一个事务
- PROPAGATION_SUPPORTS 支持当前事务,如果不存在,则以非事务方式执行
- PROPAGATION_MANDATORY 支持当前事务,如果不存在,则抛出异常
- PROPAGATION_REQUIRES_NEW 创建一个新事务,如果存在当前事务,则挂起当前事务
- PROPAGATION_NOT_SUPPORTED 不支持当前事务,始终以非事务方式执行
- PROPAGATION_NEVER 不支持当前事务,如果当前存在事务,则抛出异常
- PROPAGATION_NESTED 如果当前事务存在,则在嵌套事务中执行,内层事务依赖外层事务,如果外层失败,则会回滚内层,内层失败不影响外层。
Spring 事务传播行为有什么用?
控制事务的边界
能简单说说请求是如何找到对应的 controller 吗?
当用户发送一个请求过来,首先会经过前端调度器 DispatcherSevlet,通过 HandlerMapper(简单理解成 Map 存储映射),HandlerMapping 找到的其实是 HandlerExecutionChain ,它包含了拦截器和 handler,请求需要先进过拦截器的处理才会到最后的handler(也就是controller内的方法)
[图片上传失败...(image-22eaf3-1729518417405)]
Spring 的优点
主要有以下几点(基本上答出 ioc、aop、生态和社区就差不多了):
- 轻量级和非侵入性,不需要引入大量的依赖和配置。
- 面向切面编程(AOP),Spring 提供了强大的面向切面编程的支持,允许用户定义横切关注点,并将其与核心业务逻辑分离,提高了灵活性。
- 依赖注入(DI)和控制反转(IoC)容器,Spring 的核心是 IoC 容器,它实现了依赖注入模式,通过配置文件或注解来管理对象之间的依赖关系,降低了耦合度,提高了代码的可维护性和可测试性。
- 拥有大量的生态,几乎集成市面所有 Java 技术栈的一切框架和类库。
- Spring 拥有非常活跃的社区,官方文档丰富,网上大把教学资源,遇到问题比较容易找到对应的解决方案。
Spring Bean 注册到容器有哪些方式?
Spring Bean 注册到容器的方式主要包括以下几种:
1)基于 XML 的配置
使用 XML 文件配置 Bean,并定义 Bean 的依赖关系。
2)基于 @Component
注解及其衍生注解
使用注解如 @Component
、@Service
、@Controller
、@Repository
等进行配置。
3)基于 @Configuration
和 @Bean
注解
使用 @Configuration
注解声明配置类,并使用 @Bean
注解定义 Bean。
4)基于 @Import
注解
@Import
可以将普通类导入到 Spring 容器中,这些类会自动被注册为 Bean。
Spring 自动装配的方式有哪些?
自动装配指的是 Spring 可以根据一些特定的规则比如注解或者配置,自动在各个组件之间建立关联,完成依赖注入。
自动装配主要有以下四类:
- no(默认):不自动装配,需要显式地定义依赖
- byName:通过 Bean 名称进行自动装配
- byType:通过 Bean 类型进行自动装配
- constructor:通过构造函数进行自动装配
@Component、@Controller、@Repository和@Service 的区别?
它们都用于将类标记为 Spring 容器中的 Bean,并支持自动扫描和依赖注入,主要区别在于适用的场景和目的:
- @Component:是一个通用的 Spring 注解,表示一个 Spring 组件。可以用于任何类,标记该类为 Spring 管理的 Bean。
- @Controller、@Repository、@Service 都是 @Component 的一个派生注解
- @Controller 专门用于定义 MVC 控制器, @Repository 主要用于持久层(DAO)组件, @Service 专门用于业务逻辑层的 Bean
Spring 事务在什么情况下会失效?
- 方法是static修饰的静态方法。 静态方法不属于任何实例,而是类级别的方法。
- 方法或者类被 final修饰。动态代理本质是继承你这个类,你都final了人家咋继承?
- 方法不是被public修饰的。你一个private的方法人家咋走代理。
- 这个类没被spring管理或者是自己new出来的。
- 类里面方法自己调用。自己调用自己类的方法是不会走代理的。
- rollbackFor使用默认的,默认的只会回滚RuntimeException和Error的异常,那些什么IO异常都不会回滚。
- 你的异常被自己try catch捕获了。你不抛出来人家咋知道报异常了。
- 事务传播机制错了,比如你被调用的方法事务机制是REQUIRES_NEW,会新起一个事务,两个事务没啥关联,第一个报错了第二个不回滚。
- 表用不支持事务的引擎时,比如说MyISAM,这时候当然事务不生效。
Spring 的单例 Bean 是否有并发安全问题?
有并发安全问题的,在多线程环境下,如果 Bean 中包含全局可变状态(如实例变量或非线程安全资源),则可能会引发线程安全问题。
解决方案
- 避免在单例 Bean 中使用可变状态
-
使用
@Scope("prototype")
:对于有状态的 Bean,Spring 提供了原型作用域(prototype
),每次请求都会创建一个新的 Bean 实例,从而避免共享同一个实例带来的并发问题。 - 加锁
-
使用
ThreadLocal
保存变量
Spring中的@Value注解的作用是什么?
@Value 用于将配置值注入到 Spring Bean 里面
Spring 中的 @RequestBody 和 @ResponseBody 注解的作用是什么?
@RequestBody
:将 HTTP 请求体中的数据绑定到方法参数上
@ResponseBody
:将控制器方法的返回结果直接写入 HTTP 响应体中
Spring 中的 @PathVariable 注解的作用是什么?
@PathVariable
:它的主要作用是在处理 HTTP 请求时,从请求的 URL 路径中捕获变量,并将其绑定到控制器方法的参数上。
举个例子:网路请求地址如下,http://example.com/users/123
Spring 中的 @RequestHeader 和 @CookieValue 注解的作用是什么?
@RequestHeader:注解用于提取 HTTP 请求头中的值,并将其注入到控制器方法的参数中
@GetMapping("/header-info")
public String getHeaderInfo(@RequestHeader("User-Agent") String userAgent) {
// 使用 userAgent 进行业务处理
return "headerInfoView";
}
@CookieValue:注解用于从 HTTP 请求的 Cookie 中提取值,并将其注入到控制器方法的参数中。
@GetMapping("/cookie-info")
public String getCookieInfo(@CookieValue("sessionId") String sessionId) {
// 使用 sessionId 进行业务处理
return "cookieInfoView";
}