Spring面试题hot2-源码分析

Spring面试题hot2-源码分析
mengnankkzhou基础
1.什么是 IOC?
IOC就是控制反转,反转之前是啥,,,反转之后是啥。。。。实现核心就是反射,设计模式是工厂模式
然后IOC和核心就是DI,几种实现方式,循环依赖的解决方式。
@bean的生命周期,@PostConstruct @PreDestroy,bean的作用域,单例,原型,websocket,session,request,application
bean的单例不一定是线程安全的,这个跟我们的业务逻辑有关,如果bean没有一个可变的成员变量,那么他就是无状态的,也就是线程安全的
我们为了保证线程安全可以改变作作用域为prototype,或者使用ThreadLocal,或者是用锁
IOC容器:
BeanFactory
和 ApplicationContext
都是Spring的IoC容器,但后者是前者的超集,提供了更强大的功能。
ApplicationContext
继承了BeanFactory
,所以它具备BeanFactory
的所有能力,同时还提供了更多面向企业应用的功能。它的预加载机制能帮助你在应用启动时就发现配置错误,而不是等到运行时才报错。
特性 | BeanFactory | ApplicationContext (推荐使用) |
---|---|---|
核心定位 | Spring IoC容器的基础接口,是“底层基础设施”。 | IoC容器的高级接口,是BeanFactory 的子接口。 |
Bean加载 | 懒加载 (Lazy-loading):只有当getBean() 被调用时,才会去实例化Bean。 |
预加载 (Eager-loading):容器启动时,会一次性实例化所有singleton 作用域的Bean。 |
功能丰富度 | 功能较少,主要提供Bean的注册和获取。 | 功能非常丰富,除了BeanFactory 的所有功能外,还提供了: |
- 国际化支持 (i18n) | ||
- 事件发布与传播机制 (ApplicationEvent) | ||
- AOP集成(自动识别并配置BeanPostProcessor 等) |
||
- Web环境支持(如WebApplicationContext ) |
||
- 资源访问(如classpath: 、file: 前缀的资源加载) |
||
使用场景 | 对内存消耗要求极高的场景(如移动设备)。在现代企业应用开发中几乎不直接使用。 | 绝大多数Spring应用和所有Spring Boot应用的默认选择。 |
2.什么是spring Aop
AOP是啥,作用
实现的原理,动态代理,jdk,cgilb
几个概念,切面,连接点,通知,切点,织入
AOP通过织入(Weaving),将切面(Aspect)中的通知(Advice),应用到由切点(Pointcut)匹配到的连接点(Join Point)所对应的目标对象(Target)上。
通知类型,用了代理这个设计模式
代理对象(Proxy)接管了对目标对象(Target)的访问。所有外部调用都必须先经过代理对象。
代理对象在将调用请求转发给真正的目标对象之前或之后,有机会执行额外的操作。这些额外的操作,就是我们之前说的通知(Advice),也就是切面逻辑。
pring AOP的核心是基于动态代理实现的。它利用代理模式,在运行时为我们的目标Bean动态地创建一个代理对象。这个代理对象拦截了对原始Bean方法的所有调用,并在调用前后织入了我们定义的切面逻辑(比如日志、事务等),从而在不修改源代码的情况下,实现了功能的增强。
这个只能作用在public方法上,所以非public的方法会失效
通知注解,@Around包裹在目标方法的整个执行过程周围。它是功能最强大、最灵活的通知。
环绕通知的第一个参数必须是ProceedingJoinPoint
,它有一个proceed()
方法。
- 你可以在调用
proceed()
方法之前执行逻辑(等同于@Before
)。 - 你可以决定是否调用
proceed()
方法,从而决定是否执行目标方法。 - 你可以在调用
proceed()
方法之后执行逻辑(等同于@AfterReturning
)。 - 你可以修改目标方法的参数或返回值。
- 你可以捕获并处理目标方法抛出的异常(等同于
@AfterThrowing
)。
3.spring的常用注解
组件:
@Component
,标记为一个组件,生成一个bean
@Service
,业务层的组件注解
@Repository
,DTO层的组件注解,但是能让Spring自动转换特定于平台的数据库异常为统一的DataAccessException
。
@Controller
,controller层的组件注解
@Scope
,定义Bean的作用域。最常用的两个是:
singleton
:(默认值)在整个应用中只有一个实例。prototype
:每次请求(获取)时都会创建一个新的实例。request,websocket,context,session
@PostConstruct
和 @PreDestroy
,初始化合销毁
DI
@Autowired
,按类型(byType)自动注入依赖。如果找到多个相同类型的Bean,会尝试按名称(byName)匹配。如果还找不到就会报错。
@Qualifier("beanName")
,指定bean的名称注入,多个bean相同的
@Resource(name = "beanName")
,默认按名称注入,找不到就按类型注入
web
@RestController
,专门写API的
@RequestMapping("/path")
,指定URL
@RequestParam
,从请求的URL的?中获取参数
@PathVariable
,从URL路径里面获取参数
@RequestBody
,把发过来的JSON请求体变成一个Java对象
配置
@SpringBootApplication
=
@SpringBootConfiguration
: (就是@Configuration
)@EnableAutoConfiguration
: 启用Spring Boot的自动配置机制。内部使用@Import注解是自动装配的核心,每发现一个自动配置类,就Selector使用条件判断来确实是不是满足导入条件,自动创建所选bean- @AutoConfigurationPackage,将项目src中main包下的所有组件注册到容器中
@ComponentScan
: 自动扫描启动类所在包及其子包下的所有组件。
@Value
,配置文件读取值
@Bean
,作用方法上
@Configuration
,表明配置类
4.@Bean
创建->实例化->填充属性->初始化->可用和销毁
实例化:当客户端请求一个Bean,或者在容器启动时,Spring容器会查找Bean的定义。利用java的反射机制或者工厂方法创建一个原始的bean
填充属性,IOC容器识别bean的依赖关系,比如@Autowired@Resource。从容器中查找对应的bean,通过反射将依赖注入到bean的属性中
初始化,最重要的点,扩展点最多的。
执行Aware接口的方法,实现了特定的Aware
接口,比如BeanNameAware,
BeanFactoryAware,
ApplicationContextAware
执行BeanPostProcessor
的前置处理,调用所有BeanPostProcessor
的postProcessBeforeInitialization
方法。
执行@PostConstruct
注解的方法,调用使用了@postConstruct的方法
执行InitializingBean
接口的方法,实现了InitializingBean
接口,Spring会调用它的afterPropertiesSet()
方法。
执行自定义的init-method
,如果在XML配置或@Bean
注解中指定了init-method
,Spring会调用这个自定义的初始化方法。
执行BeanPostProcessor
的后置处理,调用所有BeanPostProcessor
的postProcessAfterInitialization
方法。
Spring的AOP(动态代理)就是在这个阶段通过包装原始Bean实例,并返回一个代理对象来实现的。我们平时拿到的Bean,很多时候其实是这一步处理后的代理对象。
可用和销毁,当Spring容器关闭时(或者对于非singleton
作用域的Bean,当其作用域结束时)。按照顺序执行
- 执行
@PreDestroy
注解的方法:如果Bean的方法上使用了@PreDestroy
注解,Spring会调用这个方法。这也是推荐的销毁回调方式。 - 执行
DisposableBean
接口的方法:如果Bean实现了DisposableBean
接口,Spring会调用它的destroy()
方法。 - 执行自定义的
destroy-method
:如果在XML配置或@Bean
注解中指定了destroy-method
,Spring会调用这个方法。
扩展点
BeanFactoryPostProcessor
- 作用时机:在Spring容器加载了所有Bean的定义信息(BeanDefinition),但尚未创建任何Bean实例之前。
- 能力:允许你读取并修改Bean的定义元数据。例如,你可以动态地修改某个Bean的属性值,甚至更改它的作用域。
通俗理解:给你一个机会在“蓝图”阶段修改设计图纸,而不是等房子建好了再去敲墙。
比如是在
application.properties
中使用占位符${...}
的功能(PropertySourcesPlaceholderConfigurer
)。MyBatis的MapperScannerConfigurer
,它会扫描接口并将其注册为Bean定义。
BeanPostProcessor
作用时机:在Bean实例化和属性填充之后,初始化方法(
init-method
,@PostConstruct
)的前后。能力
:包含两个方法:
postProcessBeforeInitialization
: 在初始化之前干预。postProcessAfterInitialization
: 在初始化之后干预。Spring的AOP就是通过它实现的,在此处返回Bean的代理对象。
通俗理解:在房子建好(实例化)并装修完(填充属性)之后,在主人入住(可用)前后,给你机会对房子进行“精装修”或“改造”(如加装监控系统-AOP)。
Aware
系列接口:
- 作用时机:在Bean的初始化阶段,
BeanPostProcessor
之前。 - 能力:让Bean能“感知”并获取到Spring容器自身的资源,如
ApplicationContext
、BeanFactory
、BeanName
等。 - 通俗理解:让Bean知道自己“身在何处”以及“叫什么名字”。
InitializingBean
和DisposableBean
:
- 作用时机:初始化和销毁阶段的特定回调。
- 能力:提供
afterPropertiesSet()
和destroy()
方法,用于自定义初始化和销毁逻辑。 - 通俗理解:Bean的“出生仪式”和“临终遗言”的固定写法。(现在更推荐使用
@PostConstruct
和@PreDestroy
注解,因为无代码侵入)
5.spring事务
事务分为声明型事务和编程型事务
声明型事务,通过注解@Transactional
来声明,他的底层是基于AOP实现的
- Spring容器在启动时,会为标记了
@Transactional
的Bean创建一个代理对象 (Proxy)。 - 当外部调用这个代理对象的方法时,代理逻辑会先被触发。
- 事务开始:代理逻辑会负责开启事务(例如,禁用数据库连接的自动提交
connection.setAutoCommit(false)
)。 - 执行业务:然后,代理对象再调用你编写的原始业务方法。
- 事务提交/回滚:
- 如果业务方法正常执行完毕,代理逻辑会提交事务。
- 如果业务方法抛出运行时异常 (RuntimeException) 或 Error,代理逻辑会回滚事务。
- Spring容器在启动时,会为标记了
- 优点:对业务代码无侵入,将事务管理代码与业务逻辑彻底解耦,使得代码非常清晰。
编程型事务,在业务代码中,通过手动调用TransactionTemplate
或PlatformTransactionManager
的API来精确地控制事务的边界。
然后调用方法,自己进行手动的提交和手动的回滚
提供了极高的灵活性,可以实现非常精细的事务控制,比如在一个方法内实现多次提交或回滚。但是高耦合
事务的隔离级别跟mysql的事务的隔离级别相同。多了一个DEFAULT
:这是@Transactional
注解的默认值。它表示使用数据库本身设置的默认隔离级别。一般数据库的事务的隔离级别就是rr
或者rc?
但是事务会失效的,事务失效的场景如下:
AOP代理有关的
- 方法不是
public
的- 原因:Spring AOP的代理机制在为类创建代理时,只会代理其
public
方法。如果你将@Transactional
注解用在protected
、private
或package-private
方法上,事务不会生效,也不会有任何报错。 - 一句话总结:代理对象无法覆盖非公有方法。
- 原因:Spring AOP的代理机制在为类创建代理时,只会代理其
- 方法被
final
修饰- 原因:被
final
修饰的方法无法被子类重写(Override)。Spring的CGLIB动态代理是通过创建目标类的子类来实现的,因此无法代理final
方法。 - 一句话总结:final方法无法被代理。
- 原因:被
- 同一个类中的方法调用(
this
调用)- 原因:这是最常见也最隐蔽的失效场景。当你在一个Bean的
methodA
中调用同一个类中的methodB
(methodB
有@Transactional
注解)时,这个调用是通过this
指针直接发生的,绕过了代理对象。事务增强逻辑是存在于代理对象中的,所以事务会失效。
- 原因:这是最常见也最隐蔽的失效场景。当你在一个Bean的
跟运行时异常有关的
- 异常被
try-catch
捕获了- 原因:Spring声明式事务默认只在遇到RuntimeException或Error时才会回滚。如果你在事务方法内部用
try-catch
捕获了异常,并且没有在catch
块中重新抛出,那么Spring的事务代理就无法感知到异常的发生,从而会正常提交事务。 - 一句话总结:异常没有传播到代理层,代理以为一切正常。
- 原因:Spring声明式事务默认只在遇到RuntimeException或Error时才会回滚。如果你在事务方法内部用
- 指定了不回滚的异常类型
- 原因:在
@Transactional(noRollbackFor = ...)
中指定了某个异常类,那么当这个异常发生时,事务将不会回滚。 - 示例:
@Transactional(noRollbackFor = NullPointerException.class)
- 原因:在
事务有关的
- 数据库引擎不支持事务
- 原因:例如,MySQL的MyISAM引擎就不支持事务。如果表使用了不支持事务的引擎,所有事务相关的操作都会被静默忽略。
- 一句话总结:底层基础不支持,上层框架无能为力。
- 事务的传播类型设置的不对,比如不支持事务never
事务的传播机制:
REQUIRED
(需要)- 描述:这是默认的传播特性。如果当前存在一个事务,那么新方法就加入到这个事务中。如果当前没有事务,就新建一个事务。
- 场景:绝大多数情况下的选择。
SUPPORTS
(支持)- 描述:如果当前存在一个事务,就加入。如果当前没有事务,就以非事务的方式执行。
- 场景:用于那些“可有可无”的事务方法,比如一些只读查询操作。
MANDATORY
(强制)- 描述:强制要求当前必须存在一个事务。如果当前没有事务,就会抛出异常。它不会自己创建事务。
- 场景:用于那些必须在事务环境下执行的核心操作,起到一种校验作用。
REQUIRES_NEW
(需要新的)- 描述:总是创建一个全新的、独立的事务。如果当前已经存在一个事务,会把当前事务挂起,然后执行新事务。新事务执行完毕后,再恢复被挂起的事务。
- 场景:希望某些操作的事务结果独立于外部事务,不受其影响。比如,在一个大的下单流程中,记录操作日志,无论下单成功与否,日志都必须成功入库。
NOT_SUPPORTED
(不支持)- 描述:以非事务的方式执行。如果当前存在一个事务,会把当前事务挂起。
- 场景:用于那些明确不希望在事务中运行的方法。
NEVER
(从不)- 描述:以非事务的方式执行。如果当前存在一个事务,就会抛出异常。
- 场景:用于和
MANDATORY
相对的校验场景。
NESTED
(嵌套)- 描述:如果当前存在一个事务,就在这个事务中创建一个嵌套事务(保存点 Savepoint)。嵌套事务独立于外部事务进行提交或回滚。如果外部事务回滚,嵌套事务也会回滚。但嵌套事务的回滚不影响外部事务。如果当前没有事务,行为等同于
REQUIRED
。 - 注意:这是一个部分数据库才支持的特性(如Oracle),需要底层JDBC驱动和数据库的支持。
- 描述:如果当前存在一个事务,就在这个事务中创建一个嵌套事务(保存点 Savepoint)。嵌套事务独立于外部事务进行提交或回滚。如果外部事务回滚,嵌套事务也会回滚。但嵌套事务的回滚不影响外部事务。如果当前没有事务,行为等同于
6.spring MVC
MVC 设计模式:
model->view->controller
作用:
- 实现MVC模式的解耦:它提供了一套清晰的架构,将处理请求的控制器、业务逻辑的模型和展示用的视图分离开来,极大地提高了代码的可维护性、可扩展性和可测试性。
- 简化Web开发:它基于Servlet API构建,但极大地简化了底层的Servlet、Request、Response等对象的直接操作。开发者可以用简单的注解(如
@GetMapping
,@PostMapping
)来处理复杂的HTTP请求。 - 与Spring生态无缝集成:它可以非常方便地使用Spring核心的IoC和AOP功能,轻松整合Service层、DAO层以及事务管理等。
- 提供强大的功能:内置了强大的参数绑定、数据校验、RESTful风格支持、拦截器、国际化、文件上传等一系列Web开发所需的核心功能。
springmvc核心组件&&执行过程c
常用注解,那几个mappring 参数绑定的那几个
深入知识:
1.统一异常处理 (@ControllerAdvice
+ @ExceptionHandler
)
- 作用:通过创建一个带有
@ControllerAdvice
注解的类,可以在其中定义多个@ExceptionHandler
方法,来集中处理整个应用中由Controller抛出的特定异常。这避免了在每个Controller中都写try-catch
,实现了优雅的全局异常处理。 - 示例:捕获所有
NullPointerException
,并返回一个自定义的错误JSON。
2.拦截器 (HandlerInterceptor
)
作用:提供了在请求处理的生命周期中(Controller方法执行前后)织入自定义逻辑的能力。它比Servlet Filter更精细,因为它能访问到即将执行的
Handler
信息。三大方法
:
preHandle
: 在Controller方法执行之前调用。可以进行权限验证、日志记录等。返回false
则中断后续流程。postHandle
: 在Controller方法执行之后,视图渲染之前调用。可以修改ModelAndView
中的数据。afterCompletion
: 在整个请求处理完成(包括视图渲染)之后调用。主要用于资源清理。
与Filter的区别:Filter是Servlet规范的一部分,作用范围更广,能处理所有HTTP请求;Interceptor是Spring MVC的一部分,只能处理经过
DispatcherServlet
的请求,但能获取到Spring MVC的上下文信息。
3.数据绑定与类型转换 (DataBinder
, Converter
)
Spring MVC能自动将请求参数(都是字符串)转换为Controller方法参数所需的类型(如Integer
, Date
)。这个过程就是数据绑定。我们可以通过实现Converter
接口,并将其注册到Spring中,来定义自定义的类型转换逻辑(例如,将”2023-01-01”字符串转换为LocalDate
对象)。
4.跨域请求处理 (@CrossOrigin
)
- 作用:简单方便地解决Web开发中常见的跨域资源共享(CORS)问题。可以直接在
Controller
类或方法上使用@CrossOrigin
注解,来允许来自特定域的跨域请求。
7.springboot
springboot是啥,四大特性(自动配置,starter,内嵌web服务器,无需xml配置)
常用的starter,Starter本质上是一个Maven依赖描述符 (pom)。它的作用是将实现某一特定功能所需的所有依赖项打包在一起,并触发与该功能相关的自动配置。
启动springboot,
spring-boot-devtools热部署工作原理:
当devtools
检测到classpath下的文件发生变化时,它会触发应用快速重启(不是完全重启,速度很快)。它通过维护两个类加载器(一个加载不变的第三方库,一个加载你自己的代码)来实现这一点,只重新加载你自己写的代码,从而大大加快了速度。
Spring Boot JAR 与 普通JAR的区别
特性 | 普通JAR (Thin JAR) | Spring Boot JAR (Fat JAR / Executable JAR) |
---|---|---|
内容 | 只包含你项目自己编译的.class 文件和资源文件。 |
包含所有内容:你的代码、资源文件,以及项目所需的所有第三方依赖库的JAR包。 |
大小 | 非常小。 | 非常大,因此也叫“胖JAR”。 |
运行方式 | 不能直接java -jar 运行(除非配置了Main-Class 且无外部依赖)。通常是作为其他项目的库被引用。 |
可以通过java -jar 命令直接运行,因为它内置了所有依赖和启动逻辑。 |
结构 | 标准JAR结构。 | 特殊的结构。解压后会看到一个BOOT-INF 目录,里面包含了classes (你的代码)和lib (所有依赖的JAR包)。还有一个org/springframework/boot/loader 目录,这是Spring Boot的启动加载器。 |
自动装配原理,Spring Boot的自动装配核心在于@SpringBootApplication
注解,而这个注解又是一个组合注解,其中最关键的是@EnableAutoConfiguration
。
@EnableAutoConfiguration
:这个注解是自动配置的开关。@Import(AutoConfigurationImportSelector.class)
:@EnableAutoConfiguration
内部通过@Import
注解导入了AutoConfigurationImportSelector
这个类。AutoConfigurationImportSelector
:这个类的核心作用是去加载和筛选需要被激活的自动配置类。- 扫描
META-INF/spring.factories
:它会扫描项目中所有JAR包的META-INF/spring.factories
文件。这个文件中定义了所有可能的自动配置类(key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
)。 - 按需加载:
Selector
会根据条件注解 (@ConditionalOnClass
,@ConditionalOnBean
等) 来判断这些自动配置类是否满足生效条件(比如DataSourceAutoConfiguration
只有在classpath下存在DataSource.class
时才会生效)。 - 注入Bean:最终,满足条件的自动配置类被加载到Spring容器中,它们内部定义的各种Bean(如
DataSource
,RestTemplate
)就被创建并注入了。
@Import注解:
@Import
是Spring框架提供的基础注解,它比@Bean
更强大,通常用于批量导入Bean或者导入配置类。它有三种主要使用方式,而Spring Boot的自动配置正是利用了第三种:
- 导入普通的类:
@Import(MyService.class)
,Spring会将MyService
注册为一个Bean。 - 导入配置类:
@Import(MyConfig.class)
,Spring会加载MyConfig
这个配置类以及它内部定义的所有@Bean
。 - 导入
ImportSelector
实现类:这是最关键的用法。@Import(MyImportSelector.class)
,Spring会实例化MyImportSelector
,并调用它的selectImports()
方法,该方法返回一个字符串数组,数组里的每一个类名都会被Spring注册为Bean。Spring Boot的自动配置就是通过这种方式,动态地、可选择地加载了大量的配置类。
spring源码分析
bean
凡是可以存放数据的具体数据结构实现,都可以称之为容器,在 Spring Bean 容器的场景下,我们需要一种可以用于存放和名称索引式的数据结构,所以选择 HashMap 是最合适不过的。
HashMap 是一种基于扰动函数、负载因子、红黑树转换等技术内容,形成的拉链寻址的数据结构,它能让数据更加散列的分布在哈希桶以及碰撞时形成的链表和红黑树上。它的数据结构会尽可能最大限度的让整个数据读取的复杂度在 O(1) ~ O(Logn) ~O(n)之间,当然在极端情况下也会有 O(n) 链表查找数据较多的情况。不过我们经过10万数据的扰动函数再寻址验证测试,数据会均匀的散列在各个哈希桶索引上,所以 HashMap 非常适合用在 Spring Bean 的容器实现上。但是我们实际上应用的是concurrenthashmap,因为他是线程安全的。我们的bean不可能只是单线程进行操作的。他是一个弱一致性迭代器,避免了并发的修改异常。
一个简单的 Spring Bean 容器实现,还需 Bean 的定义、注册、获取三个基本步骤
- 定义:BeanDefinition,可能这是你在查阅 Spring 源码时经常看到的一个类,例如它会包括 singleton、prototype、BeanClassName 等。
- 注册:这个过程就相当于我们把数据存放到 HashMap 中,只不过现在 HashMap 。在我们注册阶段Map里面存储的是beanDefintion
- 获取:最后就是获取对象,Bean 的名字就是key,Spring 容器初始化好 Bean 以后,就可以直接获取了。
- 实例化完成之后里面存储的才是bean实例
我们解决循环依赖的三级缓存就是这么设计的,key是bean的名字,value是bean的实例。
Spring Bean 容器的整个实现内容非常简单,也仅仅是包括了一个简单的 BeanFactory 和 BeanDefinition
BeanDefinition,用于定义 Bean 实例化信息,现在的实现是以一个 Object 存放对象,可以继续添加属性,比如:SCOPE_SINGLETON、SCOPE_PROTOTYPE、ROLE_APPLICATION、ROLE_SUPPORT、ROLE_INFRASTRUCTURE 以及 Bean Class 信息。
BeanFactory,代表了 Bean 对象的工厂,可以存放 Bean 定义到 Map 中以及获取。
在 Bean 工厂的实现中,包括了 Bean 的注册,这里注册的是 Bean 的定义信息。同时在这个类中还包括了获取 Bean 的操作。
然后我们使用的时候,是先初始化beanfactory容器,然后通过beanDefinition来创建一个bean。
然后去通过beanfactory去获取我们注册的bean,然后去使用bean里面封装的方法。这个时候才会实例化
然后实际我们使用的spring容器比如说是DefaultListableBeanFactory,他是继承了AbstractAutowireCapableBeanFactory,然后实现了ConfigurableListableBeanFactory, BeanDefinitionRegistry
里面包括存储BeanDefinition的容器,存储bean定义名词的列表,一级缓存,已经完成初始化的单例bean,二级缓存,早期bean的引用。三级缓存,单例工厂
我们注册bean的时候bean并没有实例化,直到获取bean的时候才会实例化,这就是懒加载
获取的时候先从一级缓存获取,缓存没有才创建bean
设计模式
职责分清,一个接口只关注一个核心的职责,通过组合来实现多种能力。这样的话,方便我们的后续扩展和维护
比如我们的BeanFactory只负责获取bean,SingletonBeanRegistry只负责单例bean的管理,BeanDefinitionRegistry 只负责定义注册
模板方法模式,所有子类都遵循相同的Bean获取流程,子类只需实现特定的抽象方法,通用逻辑在父类中实现
比如一个抽象的基类AbstractBeanFactory定义了获取bean的标准流程,先去看一级缓存中有没有,没有的话,获取一个beandefinition,然后创建实例化这个bean
然后剩下的方法只是定义完,具体的逻辑留给子类实现
分层架构,每一层都该干属于自己的事情,比如接口层定义契约和规范,然后抽象类层,实现通用的逻辑和模板方法,实现类层,实现具体的业务逻辑实现
然后在我们的项目中,比如说实现一个支付的实现的话,我们可以先创建一个支付接口,定义一个执行方法。
然后创建一个所有的支付类共同的抽象类,实现支付的基本逻辑,先检验参数,然后执行,获取执行结果,返回执行结果
然后子类具体的去实现这个执行的逻辑,比如支付宝的具体的实现逻辑
比如说我们在项目中可以根据我们的类型来选择我们要实例化bean的模式,一般就是普通的jdk代理或者是cglib代理,可以在bean属性中class里面设定,然后我们根据属性的设定来选择我们具体是选择哪一种策略。
实例化
jdk实例化,首先通过 beanDefinition 获取 Class 信息,这个 Class 信息是在 Bean 定义的时候传递进去的。然后看我们获取的class信息是不是空的,空的就是无构造函数实例化,不是空的就是有构造函数实例化。这里我们重点关注有构造函数的实例化,实例化方式为 clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
,把入参信息传递给 newInstance 进行实例化。比较简单,适用于简单的pojo对象。
cglib实例化,先是构建enhancer,设置目标为父类。设置回溯,使用noop就是说明不需要额外的处理。可以设置MethodInterceptor
等其他回调来实现AOP功能
如果class信息为空,就enhacers默认的创建,不为空的话,就创建我们指定的构造器的类型。
支持动态代理,适用于需要进行增强的对象。运行的时候动态的生成新的class文件,比如需要事务代理,需要缓存代理的
特性 | JDK反射 | Cglib动态代理 |
---|---|---|
实现原理 | Java原生反射API | 字节码动态生成 |
性能表现 | 反射调用开销较小 | 首次创建开销大,后续调用快 |
功能扩展 | 仅支持实例化 | 支持方法拦截、AOP增强 |
依赖要求 | 无额外依赖 | 需要cglib和asm库 |
代理限制 | 无法代理final类/方法 | 无法代理final类/方法 |
注入
属性填充的时机,实在对象创建后立刻进行。. PropertyValue - 属性值载体**,PropertyValues - 属性集合管理 BeanReference - 对象依赖标识**
- 延迟解析:不是直接存储Bean对象,而是存储Bean名称
- 递归创建:在属性填充时才真正创建依赖的Bean
- 循环依赖预留:为后续处理循环依赖留下接口
属性填充方法,内部使用递归,检测到BeanReference
类型,调用getBean(beanReference.getBeanName())
获取他的依赖的名字
使用递归获取,被依赖的Bean创建完成后返回,用创建好的Bean对象填充当前Bean的属性
框架自动处理依赖关系,开发者无需关心创建顺序,只有真正需要这个bean的时候才会创建,提高了性能。然后我们创建后的Bean会被缓存,避免重复创建。这是缓存的思想
处理循环依赖:使用三级缓存架构来解决,一级缓存,完整的bean。二级缓存,实例化,没有进行属性的填充。三级缓存,bean的工厂对象,用于解决aop代理的问题
缓存级别 | 存储内容 | 作用 | 时机 |
---|---|---|---|
一级缓存 | 完整的Bean对象 | 存放完全初始化好的Bean | Bean创建完成后 |
二级缓存 | 早期Bean对象 | 存放实例化但未填充属性的Bean | 解决循环依赖时 |
三级缓存 | ObjectFactory | 用于创建代理对象 | Bean实例化后立即放入 |
扩展支持private boolean allowCircularReference = true;
DefaultSingletonBeanRegistry:
一级一级的调用,先去找一次缓存没有,标记为正在创建,然后再找二级缓存,允许早期引用。没有就从三级缓存中获取,还没有的话,通过ObjectFactory创建Bean
然后放入二级缓存,从三级缓存中删除。
实体类方法:
实例化后立刻放入三级缓存,然后进行填充属性。然后初始化bean,成功之后放入一级缓存,标记为完成
流程:
创建UserService
- doGetBean(“userService”)
- getSingleton(“userService”) → null(一级缓存为空)
- beforeSingletonCreation(“userService”) → 标记正在创建
- createBeanInstance() → 实例化UserService对象
- addSingletonFactory(“userService”, ObjectFactory) → 添加到三级缓存
- applyPropertyValues() → 开始填充orderService属性
- getBean(“orderService”) → 触发OrderService创建
创建orderService
- doGetBean(“orderService”)
- getSingleton(“orderService”) → null(一级缓存为空)
- beforeSingletonCreation(“orderService”) → 标记正在创建
- createBeanInstance() → 实例化OrderService对象
- addSingletonFactory(“orderService”, ObjectFactory) → 添加到三级缓存
- applyPropertyValues() → 开始填充userService属性
- getBean(“userService”) → 再次请求UserService
循环依赖解析
- doGetBean(“userService”)
- getSingleton(“userService”, true) → 执行三级缓存查找
- 一级缓存:null
- isSingletonCurrentlyInCreation(“userService”) → true
- 二级缓存:null
- 三级缓存:找到ObjectFactory
- factory.getObject() → 返回早期UserService对象
- 放入二级缓存,移除三级缓存
- 返回早期UserService对象给OrderService
- OrderService属性填充完成
- OrderService初始化完成,添加到一级缓存
- 返回OrderService给UserService
- UserService属性填充完成
- UserService初始化完成,添加到一级缓存
这就是我们所说的提前暴露的问题
bean管理
使用配置文件来管理我们的bean,添加一个资源解释器,也就是能读取classpath、本地文件和云文件的配置内容
里面会包括 Bean 对象的描述和属性信息。在读取配置文件信息后,接下来就是对配置文件中的 Bean 描述信息解析后进行注册操作,把 Bean 对象注册到 Spring 容器中。
从配置文件层->资源加载层->解析注册层->bean容器层
资源加载:定义 Resource 接口,提供获取 InputStream 流的方法
然后策略实现类,多种的实现策略。
ClassPath资源加载:
通过 ClassLoader
读取ClassPath
下的文件信息,具体的读取过程主要是:classLoader.getResourceAsStream(path)
- 打包后的配置文件:JAR包内的spring.xml
- 测试资源:src/test/resources下的配置文件
- 类路径资源:与class文件同目录的配置文件
文件系统资源加载:
通过指定文件路径的方式读取文件信息
- 外部配置文件:/etc/app/config.xml
- 用户自定义配置:~/app/custom.properties
- 绝对路径资源:D:/config/spring.xml
URL资源加载:
通过 HTTP 的方式读取云服务的文件,我们也可以把配置文件放到 GitHub 或者 Gitee 上,使用URLConnection
- 远程配置中心:http://config-server/app.xml
- 云端配置文件:https://github.com/user/repo/config.xml
- 动态配置:从配置中心实时拉取
智能资源定位器,DefaultResourceLoader
按照资源加载的不同方式,资源加载器可以把这些方式集中到统一的类服务下进行处理,外部用户只需要传递资源地址即可
使用的顺序是先去从classpath进行获取,然后再去获取url资源。都没有的话采取获取默认的文件系统资源
Bean定义读取器,BeanDefinitionReader
通过这个抽象类的具体实现就可以把解析后的 XML 文件中的 Bean 信息,注册到 Spring 容器去了。以前我们是通过单元测试使用,调用 BeanDefinitionRegistry 完成Bean的注册,现在可以放到 XMl 中操作了
解析xml处理bean注册,XmlBeanDefinitionReader 基础自抽象类
将xml文件映射为我们需要的代码,然后进行在spring容器中的注册
bean组件的扩展
在实际工作中,当我们开发基于Spring的技术组件(如中间件、SpringBoot Starter等)时,经常需要:
- 修改Bean的信息
- 添加日志打印、监控
- 处理数据库路由和数据源切换
- 给RPC服务连接注册中心
- 实现AOP切面功能
这些都需要在Bean的生命周期中插入自定义逻辑,这就是Spring扩展机制的核心价值。
BeanFactoryPostProcessor - Bean定义后置处理器
在beandefinition加载完之后,bean实例化之前,可以去修改beandefinition的属性信息。相当于我们去建筑的蓝图。用于配置属性修改、Bean定义动态调整
比如
1 |
|
BeanPostProcessor - Bean实例后置处理器
Bean实例化和属性注入后,初始化方法调用前执行,他有前置和后置的类型,我们一般在后置进行增加的更多,比如AOP代理,Bean增强,属性修改。依赖检查,权限校验和监控
ApplicationContext应用上下文架构
为了避免繁琐的操作,他是我们最常用的一个IOC容器
1 | // 简洁的上下文操作 |
接口体系:
1 | // 接口层次结构 |
在applicationcontext里面最重要的方法就是refresh方法
创建beanfactory然后加载beandefinnition
然后我们去获取beanfactory,执行BeanFactoryPostprocessor修改beandefinition
然后注册beanpostprocessor,为后续的处理进行准备
然后提前实例化单例的bean。
完整的生命周期
1 | 1. BeanDefinition注册 |
BeanFactoryPostProcessor应用场景:
- 配置中心集成:动态读取远程配置修改Bean属性
- 环境相关配置:根据环境(dev/test/prod)调整Bean配置
- 属性占位符解析:解析${property}占位符
- 条件化Bean注册:根据条件动态注册Bean
BeanPostProcessor应用场景:
- AOP实现:Spring AOP就是通过此接口创建代理对象
- 中间件集成:如MyBatis的MapperScannerConfigurer
- 监控和日志:自动添加监控、日志功能
- 依赖注入增强:如@Autowired注解的实现
- 数据源路由:动态数据源切换
Spring中哪些功能使用了这些扩展机制
BeanFactoryPostProcessor的应用:
PropertyPlaceholderConfigurer
:属性占位符解析PropertySourcesPlaceholderConfigurer
:Spring 3.1+的属性解析ConfigurationClassPostProcessor
:@Configuration类处理
BeanPostProcessor的应用:
AutowiredAnnotationBeanPostProcessor
:@Autowired注解处理CommonAnnotationBeanPostProcessor
:@Resource等注解处理AnnotationAwareAspectJAutoProxyCreator
:AOP代理创建
bean的初始化和销毁
完整的生命周期:
Bean定义注册
↓
BeanFactoryPostProcessor执行
↓
Bean实例化(Constructor)
↓
属性注入(Setter/Field)
↓
BeanPostProcessor.postProcessBeforeInitialization
↓
InitializingBean.afterPropertiesSet() ← 接口方式初始化
↓
init-method执行 ← XML配置方式初始化
↓
BeanPostProcessor.postProcessAfterInitialization
↓
Bean就绪状态
↓
容器关闭触发
↓
DisposableBean.destroy() ← 接口方式销毁
↓
destroy-method执行 ← XML配置方式销毁
在这个里面,init-method和destory-method他们也是要xml配置来进行的。所以也要通过 XmlBeanDefinitionReader 加载 spring.xml 配置信息到 BeanDefinition 中。
- InitializingBean、DisposableBean,两个接口方法还是比较常用的,在一些需要结合 Spring 实现的组件中,经常会使用这两个方法来做一些参数的初始化和销毁操作。比如接口暴漏、数据库数据读取、配置文件加载等等。
- 在方法 invokeInitMethods 中,主要分为两块来执行实现了 InitializingBean 接口的操作,处理 afterPropertiesSet 方法。另外一个是判断配置信息 init-method 是否存在,执行反射调用 initMethod.invoke(bean)。这两种方式都可以在 Bean 对象初始化过程中进行处理加载 Bean 对象中的初始化操作,让使用者可以额外新增加自己想要的动作。先执行接口方式,再执行配置方式,通过反射避免重复执行同名方法
- 方法destory跟上面的invokeinitMethods差不多,都是先实现接口,然后配置信息 destroy-method {判断是为了避免二次执行销毁}
虚拟机关闭钩子:
- 首先我们需要在 ConfigurableApplicationContext 接口中定义注册虚拟机钩子的方法
registerShutdownHook
和手动执行关闭的方法close
。 - 在抽象实现类里面进行实现,注册JVM关闭构造的时候,是Runtime.getRuntime().addShutdownHook
1.初始化方法和构造函数的区别?
主要的区别在于执行的实际和功能定位,构造函数执行的时候,依赖注入还没完成,无法进行初始化
初始化方法执行的时候,所有属性已经完成。可以使用依赖对象进行初始化。比如建立数据库连接池,缓存预热,注册到注册中心
然后构造函数主要用于对象创建、基本属性设置,初始化方法主要使用依赖就绪后的业务初始化
2.为什么需要销毁方法?不能依赖GC吗
GC只能回收内存,不能处理资源的释放
比如,socket,http连接,文件流,数据库连接等。比如线程池,定时器。还有注册中心,Mbean
3.Spring中哪些组件使用了初始化/销毁机制?
几乎所有核心组件都有应用,比如数据源组件,初始化连接池,销毁连接池
缓存组件,初始化缓存预热,关闭redis连接
消息队列,启动监听消息队列,停止监听关闭消息队列
4.如何保证初始化方法的执行顺序?
使用@DependsOn注解,就是一个前置条件,确保configService和cacheService先初始化
@Order注解配合ApplicationListener,数字越小,约先执行。
实现Ordered接口,然后确定高优先级的初始化
5.如何处理初始化方法的异常?
- 快速失败:关键资源初始化失败时立即抛异常
- 优雅降级:非关键失败时使用备用方案
- 延迟重试:网络等临时性失败可以重试
Aware
Aware是Spring提供的一种容器感知机制,让Bean能够获取Spring容器中的各种资源和服务。它是一个标记接口,通过实现不同的Aware子接口,Bean可以感知到:
- BeanFactory
- ApplicationContext
- ClassLoader
- Bean名称
- 以及其他容器资源
继承 Aware 的接口包括:BeanFactoryAware、BeanClassLoaderAware、BeanNameAware和ApplicationContextAware
在具体的接口实现过程中你可以看到,一部分(BeanFactoryAware、BeanClassLoaderAware、BeanNameAware)在 factory 的 support 文件夹下,另外 ApplicationContextAware 是在 context 的 support 中,这是因为不同的内容获取需要在不同的包下提供。所以,在 AbstractApplicationContext 的具体实现中会用到向 beanFactory 添加 BeanPostProcessor 内容的 ApplicationContextAwareProcessor
操作,最后由 AbstractAutowireCapableBeanFactory 创建 createBean 时处理相应的调用操作。
Aware接口
在 Spring 中有特别多类似这样的标记接口的设计方式,它们的存在就像是一种标签一样,可以方便统一摘取出属于此类接口的实现类,通常会有 instanceof 一起判断使用。
标记模式:类似于
Serializable
接口,用于标识具有某种特性的类- instanceof判断:通过
bean instanceof Aware
统一识别和处理 - 统一管理:将所有感知接口归类到一个体系下
四大核心感知接口:
1 | // 1. 感知BeanFactory |
调用感知:
- 首先在 initializeBean 中,通过判断
bean instanceof Aware
,调用了三个接口方法,BeanFactoryAware.setBeanFactory(this)
、BeanClassLoaderAware.setBeanClassLoader(getBeanClassLoader())
、BeanNameAware.setBeanName(beanName)
,这样就能通知到已经实现了此接口的类。 另外我们还向 BeanPostProcessor 中添加了
ApplicationContextAwareProcessor
,此时在这个方法中也会被调用到具体的类实现,得到一个 ApplicationContex 属性。确保Bean获得感知能力后再进行业务初始化
ApplicationContext比较特殊他要单独去执行在refresh方法中进行,ApplicationContext在AbstractAutowireCapableBeanFactory
中不可直接获取,需要在容器启动时注册专门的处理器,体现了分层架构的设计思想。ApplicationContextAwareProcessor在ApplicationContext,其他的他们在BeanFactory层 。
实现:
- 在ApplicationContext层创建
ApplicationContextAwareProcessor
- 将processor注册到BeanFactory中
- 利用BeanPostProcessor机制在Bean初始化时注入ApplicationContext
题目:
1.Aware接口的作用是什么?
Aware接口提供了Bean获取Spring容器资源的标准机制:
- 容器感知:让Bean能够感知到Spring容器的存在
- 资源获取:提供获取容器内部资源的标准方式
- 扩展能力:为开发中间件和框架提供扩展点
- 解耦设计:通过接口回调而非静态方法获取资源
比如setApplicationContext方法,可以获取容器中的其他的bean,发布应用事件,可以获取环境配置
setBeanFactory方法,可以动态获取bean,检查bean是不是存在
2.不同Aware接口的执行顺序是什么?
\1. BeanNameAware.setBeanName()
\2. BeanClassLoaderAware.setBeanClassLoader()
\3. BeanFactoryAware.setBeanFactory()
\4. EnvironmentAware.setEnvironment() (如果实现)
\5. EmbeddedValueResolverAware.setEmbeddedValueResolver() (如果实现)
\6. ResourceLoaderAware.setResourceLoader() (如果在ApplicationContext中)
\7. ApplicationEventPublisherAware.setApplicationEventPublisher() (如果在ApplicationContext中)
\8. MessageSourceAware.setMessageSource() (如果在ApplicationContext中)
\9. ApplicationContextAware.setApplicationContext() (如果在ApplicationContext中)
- Bean自身信息先设置(Name、ClassLoader、Factory)
- 容器环境信息后设置(Context、Environment等)
- 应用层面信息最后设置(Event、Message等)
- 最后是我们的context
3.Aware机制与@Autowired的区别?
对比维度 | Aware接口 | @Autowired |
---|---|---|
注入对象 | Spring容器内部资源 | 业务Bean对象 |
执行时机 | Bean初始化阶段 | 属性注入阶段 |
耦合度 | 与Spring框架耦合 | 相对解耦 |
使用场景 | 框架扩展、中间件开发 | 业务依赖注入 |
灵活性 | 可以获取容器的动态能力 | 静态依赖关系 |
FactoryBean&Bean的作用域
FactoryBean是Spring提供的一种创建复杂Bean对象的工厂接口,它允许我们自定义Bean的创建逻辑,特别适用于:
- 代理对象创建(如MyBatis的Mapper代理)
- 复杂对象初始化(需要多步骤构建的对象)
- 第三方框架集成(将外部框架的对象纳入Spring管理)
Bean作用域(Scope)机制
Spring支持多种Bean作用域:
- singleton:单例模式(默认),容器中只有一个实例
- prototype:原型模式,每次获取都创建新实例
- request/session/application:Web环境中的作用域
单例模式和原型模式的区别就在于是否存放到内存中,如果是原型模式那么就不会存放到内存中,每次获取都重新创建对象
这就是我们常说的五大作用域
FactoryBean设计:获取对象、对象类型,以及是否是单例对象
比如我们的MyBatis Mapper代理创建就是我们自定义的一个FactoryBean, 使用 MapperFactoryBean
将接口注册为代理对象,启动时会把接口方法解析成 MappedStatement
存入 Configuration 中,运行时由 MapperProxy
通过反射动态执行 SQL。
还有数据源代理等等。
作用域就是我们定义在BeanDefinition中的一个常量,然后我们通过指定scpore字段来定义
然后xml解析回去xml里获取作用域,然后设置beandefinition的信息
1.FactoryBean和BeanFactory的区别?
对比维度 | BeanFactory | FactoryBean |
---|---|---|
性质 | Spring容器的根接口 | 用户可实现的工厂接口 |
作用 | 管理Bean的生命周期 | 创建复杂的Bean对象 |
使用者 | Spring框架内部使用 | 开发者实现和使用 |
获取方式 | 通过ApplicationContext | 通过getBean()获取其产品 |
2.如何获取FactoryBean本身而不是它创建的对象
使用&的前缀符表示我们需要的是FactoryBean
1 | MyFactoryBean factoryBean = (MyFactoryBean) applicationContext.getBean("&myFactoryBean"); |
然后内部处理
1 | public Object getBean(String name) { |
3.FactoryBean在什么场景下使用?
代理对象的创建,复杂对象初始化, 第三方框架集成(比如redis中设置redis的运行的参数和序列化方式)
4.FactoryBean的生命周期是怎样的?
FactoryBean有双重生命周期,一个是他本身的生命周期,一个是他产品对象的生命周期
事件
Event机制
在复杂的业务系统中,直接调用会导致紧耦合问题:
- 用户注册 → 直接调用发送邮件、赠送积分、风控检查等服务
- 订单支付 → 直接调用库存扣减、物流发货、积分计算等服务
- 文章发布 → 直接调用消息推送、索引更新、缓存刷新等服务
这种方式会导致:
- 代码耦合度高:核心业务逻辑与辅助功能混杂
- 扩展性差:新增功能需要修改核心代码
- 维护困难:一个环节出错影响整个流程
- 性能问题:同步执行所有操作,响应慢
我们可以通过观察者模式进行解耦
用户注册成功 → 发布UserRegisteredEvent → 多个监听器异步处理
├── EmailListener: 发送欢迎邮件
├── CouponListener: 赠送新人礼包
├── RiskListener: 风控分析
└── StatisticsListener: 数据统计
spring event
定义出事件类、事件监听、事件发布 事件广播器
applicationevent->applicationlistener->applicationeventpublisher->applicaitoneventmulticaster
我们事件类是构建一个基本的抽象类,然后其他的具体事件继承这个抽象类
事件广播器定义了添加监听和删除监听的方法,和添加广播的方法。multicastEvent
最终推送时间消息也会经过这个接口方法来处理谁该接收事件。
那我们怎么处理事件广播的并发处理呢?
- 监听器隔离:一个监听器异常不影响其他监听器
- 异步执行:可配置同步或异步处理
- 顺序控制:支持监听器执行顺序
然后我们怎么去确定某个事件如何被监听器处理?我们使用泛型参数匹配来实现
我们先去获取监听的class,然后如果存在GCLIB代理的话,我们处理CGLIB代理。spring中Bean可能被代理,需要获取真实类型,就是他的父类。
然后获取泛型接口,提取泛型参数,获取事件的类型。
然后判断事件是否匹配,就是判断ParameterizedType 和eventClassName是不是子类和父类的关系
事件发布器,初始化事件发布者(initApplicationEventMulticaster),
主要用于实例化一个 SimpleApplicationEventMulticaster,这是一个事件广播器。
注册事件监听器(registerListeners),通过 getBeansOfType 方法获取到所有从 spring.xml 中加载到的事件配置 Bean 对象。
- 发布容器刷新完成事件(finishRefresh),发布了第一个服务器启动完成后的事件,这个事件通过 publishEvent 发布出去,其实也就是调用了 applicationEventMulticaster.multicastEvent(event); 方法。
问题
1.spring event事件执行的流程:
- 事件定义:继承
ApplicationEvent
创建事件类 - 监听器注册:实现
ApplicationListener
并注册到容器 - 事件发布:通过
ApplicationEventPublisher.publishEvent()
发布 - 事件广播:
ApplicationEventMulticaster
接收事件 - 监听器匹配:根据泛型参数匹配感兴趣的监听器
- 事件处理:调用匹配监听器的
onApplicationEvent()
方法
2.如何保证Event处理的事务一致性?
使用事件同步机制,事务提交后执行事务监听器,发送邮件通知等,然后事务回滚的时候发送清理操作
通过@TransactionalEventListener的状态来确定
然后失败之后通过补偿机制。
动态代理
动态代理是我们spring的AOP的
AOP主要就是解耦,他将跟核心业务没关系的业务比如权限,日志等业务的抽离出来,一般都是以注释的形式。然后方法只执行业务核心方法
动态代理分为JDK动态代理和CGLIB动态代理
JDK动态代理基于接口,代理实现了接口的类,在运行时动态生成代理类的字节码。速度较快,不需要生成新的字节码
JdkDynamicAopProxy实现了AopProxy, InvocationHandler
获取代理方式,获取当前线程的context类加载器,然后通知通知添加目标接口。然后执行InvocationHandler实现
然后使用Invoke方法,执行代理。检查方法是不是匹配切点表达式,匹配成功后执行方法拦截器,拦截,然后换取我们自己实现的bean。如果不匹配的话,我们就执行原来的方法
CGLIB动态是基于继承目标类生成子类代理,使用了使用ASM字节码技术。除了final都可以代理,速度较慢,需要生成字节码。但是性能较好
get方法是通过设置我们的需要代理的类,然后设置接口,设置回调处理器