ps:最近正好在学习SpringBoot的底层原理,在看了一些书籍和一些讲解视频后,自己对其有了一些理解,由此记录一些,方便以后复习.
Bean生命周期
Spring Bean 的生命周期是 Spring 框架的核心机制之一,它定义了 Bean 从创建到销毁的全过程,并提供了多个扩展点供开发者定制逻辑。以下是 Bean 生命周期的完整流程及关键阶段解析:
Bean 生命周期总览
Spring Bean 的生命周期可分为 6 大阶段,涵盖容器管理、依赖注入、初始化与销毁等核心操作:
- 实例化
- 属性注入
- Aware 回调
- BeanPostProcessor 处理
- 初始化
- 销毁
实例化
首先在Sprin中有一个类叫BeanDefinition,在Spring容器创建后,首先会扫描@ComponentScan的包及其子包的的类路径,然后去查找到字节码文件下的.class文件,通过类加载器拿到这个类的类对象
通过类对象去判断当前类上是否有一个@Component注解,如果有这个注解则说明,这个类将交由Spring容器管理接着会去判断当前类是否为BeanPostProcessor的实现类
BeanPostProcessor 是 Spring 框架中 Bean 生命周期管理的核心扩展接口,允许开发者在 Bean 初始化的前后插入自定义逻辑,从而实现对 Bean 的深度定制。
通俗点来说,这个接口里有两个方法,postProcessBeforeInitialization()和postProcessAfterInitialization,这两个方法分别在Bean的初始化之前和Bean的初始化之后执行,他们会返回处理后的Bean对象,例如你通过这个方法将Bean对象转化为一个代理对象等.
所以,这里会判断是否需要执行BeanPostProcessor的前置处理,如果执行了BeanPostProcessor的前置处理方法,那么会将该Bean放到一个专门存放此类型的List容器中,如果没有前置处理则会获取@Component注解中指定的Bean的名字,如果没有指定则默认是首字母小写的类名.
然后Spring会创建一个BeanDefinition类,这个类主要存储了两个属性,一个是当前Bean的类型,另一个则是当前Bean的作用域,判断当前Bean对象是一个单例Bean还是一个多例Bean,主要通过@Scope注解来判断,默认是singleton单例Bean
接着Spring容器就会把当前这个BeanDefinition储存到一个Map集合(BeanPostProcessor容器)中,beanName为key,BeanDefinition为value.
接下来就会遍历整个BeanPostProcessor容器,如果这个BeanDefinition的type是单例的,Spring就会通过反射的方式,调用其构造器创建Bean对象,并添加到一个Map集合中(单例池
singletonObjects)中去.
所以一般我们在调用Bean对象的时候,如果是单例Bean的话,Spring会先去单例池中寻找使用get(beanName)的形式,如果单例池中找不到,取出的Bean对象是null,则会创建这样一个单例Bean,并添加到单例池中
而如果是多例Bean的话,则每次调用时,都会去复制一个Bean出来.
依赖注入(DI)
依赖注入,也可以称为属性填充,最常见的做法呢就是通过@Autowired注解
在Spring中是怎么实现属性注入的呢?就拿@Autowired为例:
@Component
public class UserService implements UserInterface{
@Autowired
private OrderService orderService;
@Override
public void test(){
System.out.println(orderService);
}
}
在这个例子中,我将UserService交给了Spring容器管理,其中orderService属性上添加了一个@Autowired注解,那么在UserService的Bean加载的时候,Spring会通过反射来获取UserService这个类的所有属性,如果发现一个有@Autowired注解的属性(例如代码中的orderService),且OrderService也是有Spring容器管理的Bean,那么就会创建OrderService的Bean对象并赋值给当前类下的orderService.
@PostConstruct
@PostConstruct用于标记 Bean 初始化后需要执行的回调方法。其核心作用是确保在依赖注入完成后执行特定初始化逻辑。
这个代码,假设我需要在依赖注入之后连接数据库,那么我就可以通过这个注解来完成
@Component
public class DatabaseConfig {
@Autowired
private DataSource dataSource;
@PostConstruct
public void init() {
try (
Connection conn = dataSource.getConnection()
) {
// 验证连接有效性
} catch (SQLException e) {
throw new RuntimeException("数据库连接失败", e);
}
}
}
@PostConstruct的执行时机呢,是在依赖注入之后,初始化方法之前.那么Spring是怎么判断这个方法的执行时机的呢?
同样的,在依赖注入完成之后,还是通过反射的方式,在找到这个注解所在的方法,然后执行该方法
初始化
Spring中有一个接口初始化Bean的接口InitializingBean,是Bean 生命周期管理的核心接口之一,用于在 Bean 属性注入完成后执行自定义初始化逻辑。
这个接口只有一个方法afterPropertiesSet(),它的触发时机是在依赖注入完毕并且在@PostConstruct之后,自定义init-method之前。
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
那么这个方法Spring是怎么执行的呢?Spring会判断你当前的这个类有没有实现一个InitializingBean接口,如果实现有这个接口,那就强制转化为InitializingBean,然后执行afterPropertiesSet()方法.
// 检查是否实现 InitializingBean
if (bean instanceof InitializingBean) {
((InitializingBean) bean).afterPropertiesSet();
}
推断构造方法
前面我们提到.Spring在创建Bean的时候是通过反射调用其构造器来实现的,那么它调用的到底是哪个构造方法呢?
首先Spring默认是调用无参构造器的,但如果你没有无参构造器,那么能不能完成Bean的实例化呢?
例如下面的代码,我只给了一个有参构造器,这时UserService能否的Bean能否实例化呢?
@Component
public class UserService{
private OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
这里其实是可以的.那么这里Spring是怎样去创建一个UserService的Bean的呢?首先它会在单例池中根据类型查找(byType),如果单例池中能找到一个类型为OrderService的Bean的话,就会传参然后创建UserService的Bean
如果容器中有几个类型一样的都是OrderService的Bean,则会在根据传入的名字查找,如果还找不到,说明容器中没有这么一个Bean对象,这时才会报错.
@Component
public class OrderService {
private UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
这里有一个特殊情况,如果OrderService的也是这样写的话,那么就会产生循环依赖,就是一种A创建需要B,B创建需要A的情况.而且特别是这种构造器注入的方式,Spring一般是无法解决的.
但我们都知道,依赖注入的方式一共有三种,字段注入,setter方法注入,构造器注入.其中,字段注入和setter注入Spring都设计有对应的解决机制如Spring的三级缓存设计或者@lazy注解等
AOP
Spring AOP(面向切面编程)的实现原理基于 动态代理 和 字节码增强 技术,其核心目标是将横切关注点(如日志、事务、安全等)从业务逻辑中解耦。
AOP也叫面向切面编程,这里主要理解一下,什么叫切面.我个人的理解就是在一个方法的执行前或者执行后,我去添加一些代码来增强这个代码的功能,像常见的一些AOP应用场景有:日志记录,事务管理,清理缓存,权限校验等.
那么AOP是怎么实现的呢?
1.动态代理
在SpringAOP中,主要通过动态代理来实现,它会通过生成一个代理对象来拦截目标方法的调用,并在方法执行前后插入切面逻辑。
在Spring中主要有两种代理类型,一个是JDK提供的基于接口生成的代理类,它要求目标类至少实现一个接口;
另一种则是CGLIB提供的,它是通过继承目标类生成子类代理,无需实现接口.在AOP执行开始的时候,Spring容器会会根据目标类自动选择代理类型
2.Weaving(织入)
Weaving是一种AspectJ中的一种技术,将切面逻辑插入目标对象的过程称为织入,Spring AOP 采用运行时织入:
- 编译时织入(AspectJ):在编译阶段修改字节码,性能最优但灵活性低。
- 类加载时织入(AspectJ):在类加载时修改字节码,需特定类加载器支持。
- 运行时织入(Spring):通过代理对象动态拦截方法调用,灵活性高但性能略低
那在Spring中AOP的执行流程是怎样的呢?
Spring会根据目标类生成代理对象(JDK/CGLIB),
接着在目标方法调用时拦截,
然后执行通知(Advice)也就是你切入的代码.
Spring事务
Spring 事务管理是保证数据一致性和完整性的核心机制,其设计基于 ACID 特性 和 声明式编程模型。
我们都知道,使用我们在使用JAVA操作数据库时,如果业务处理到一半突然抛出个异常,那么这时很可能会提交一条错误的数据到数据库中,这是因为jdbcTemplate和mybatis执行sql语句都是自动提交的
而在Spring中我们一般都会添加上一个事务注解@Transactional,就能将这个方法交给事务管理器来管理.
首先,Spring容器启动时,会为@Transactional类生成代理对象,调用代理方法时会触发TransactionInterceptor(事务拦截),开启事务管理器.
这时事务管理器会新建一个数据库连接,但是会把这个连接的autocommit(自动提交)设置为false,这时不管是jdbcTemplate执行sql还是mybatis执行sql都不会自动提交.
这时候代理对象会根据结果来判断最后要执行rollback()回滚日志,还是commint()提交.
但是在使用@Transactional时有一个前提,那就是我需在配置类上添加 @EnableTransactionManagement注解,否则 Spring 不会扫描@Transactional,而且一般我们都要搭配@Configuration来实现
因为@Transactional的实现依赖事务管理器PlatformTransactionManager,而该 Bean必须通过配置类显式定义。而且事务管理器通常需要与数据源(DataSource)绑定,而数据源的配置也需在 @Configuration类中完成。