IoC容器的初始化参考:https://www.jianshu.com/p/93874bb2844d
Spring Bean 的生命周期可以分为以下几个阶段:
- 实例化 Bean:Spring 容器首先会使用构造器或者工厂方法创建 Bean 实例。
- 设置属性值:Spring 容器会通过反射机制,将 Bean 定义信息中的属性值注入到 Bean 实例中。
- 调用 BeanNameAware 的 setBeanName 方法:如果 Bean 实现了 BeanNameAware接口,Spring 容器会调用 setBeanName 方法,传入 Bean 的 ID。
- 调用 BeanFactoryAware 的 setBeanFactory 方法:如果 Bean 实现了 BeanFactoryAware 接口,Spring 容器会调用 setBeanFactory 方法,传入 BeanFactory。( 后续可以在 @PostConstruct public void init() {}中获取其他bean,做其他事)
- 调用 ApplicationContextAware 的 setApplicationContext 方法:如果 Bean 实现了
ApplicationContextAware 接口,Spring 容器会调用 setApplicationContext 方法,入
ApplicationContext。 - 调用 BeanPostProcessor 的 postProcessBeforeInitialization 方法:在 Bean
的初始化方法调用之前,Spring 容器会调用所有注册的 BeanPostProcessor 的
postProcessBeforeInitialization 方法。 - 调用 InitializingBean 的 afterPropertiesSet 方法:如果 Bean 实现了
InitializingBean 接口,Spring 容器会调用 afterPropertiesSet 方法。 - 调用自定义的初始化方法:如果在 Bean 定义中指定了 init-method 属性,Spring 容器会调用指定的方法作为初始化方法。
- 调用 BeanPostProcessor 的 postProcessAfterInitialization 方法:在 Bean
的初始化方法调用之后,Spring 容器会调用所有注册的 BeanPostProcessor 的
postProcessAfterInitialization 方法 - Bean 可以使用了:此时,Bean 已经准备好被应用程序使用。
- 调用 DisposableBean 的 destroy 方法:当容器关闭时,如果 Bean 实现了 DisposableBean接口,Spring 容器会调用 destroy 方法。
- 调用自定义的销毁方法:如果在 Bean 定义中指定了 destroy-method 属性,Spring
容器会调用指定的方法作为销毁方法。
整个生命周期中,Spring 容器通过反射机制调用 Bean 的方法,同时提供了多种扩展接口(如
BeanPostProcessor、InitializingBean、DisposableBean 等),允许在 Bean
的生命周期的不同阶段插入自定义逻辑。
初始化
在 Spring 框架中,Bean 的初始化阶段是指在 Bean 的属性被设置之后,Bean 可以被使用之前,执行的一系列操作。这个阶段主要涉及执行自定义的初始化逻辑,确保 Bean 在使用前处于正确的状态。初始化阶段可以包括以下几个具体的操作:
-
执行 @PostConstruct 注解的方法:这是 Java EE 5 引入的注解,Spring 支持在 Bean 的所有属性设置之后调用使用
@PostConstruct
注解的方法。这个方法通常用于执行初始化代码。@PostConstruct public void init() { // 初始化代码 }
-
调用 InitializingBean 接口的 afterPropertiesSet 方法:如果 Bean 实现了 Spring 的
InitializingBean
接口,Spring 容器将在所有 Bean 属性被设置之后调用afterPropertiesSet
方法。@Override public void afterPropertiesSet() throws Exception { // 初始化代码 }
-
执行自定义的初始化方法:在 Bean 的配置中,可以通过
init-method
属性指定一个方法作为 Bean 的初始化方法。Spring 容器将在 Bean 的所有属性被设置之后调用这个方法。<bean id="exampleBean" class="com.example.ExampleBean" init-method="customInit"/>
或者使用注解配置:
@Bean(initMethod = "customInit") public ExampleBean exampleBean() { return new ExampleBean(); }
其中
customInit
是 Bean 中的一个方法,用于执行初始化逻辑。
初始化几个方法的执行顺序
初始化阶段的主要目的是确保 Bean 在被使用之前,已经处于一个正确的状态。这可能包括开启资源、校验必要条件、预加载数据等。通过上述机制,Spring 提供了灵活的方式来定义和执行初始化逻辑,使得 Bean 的配置和使用更加灵活和安全。
在 Spring 框架中,如果一个 Bean 配置了多种自定义初始化方法,它们的执行顺序如下:
-
@PostConstruct 注解的方法:首先执行使用
@PostConstruct
注解标注的方法。这是 Java EE 5 引入的注解,用于指定在依赖注入完成后应该执行的方法,Spring 支持这一注解。 -
InitializingBean 接口的 afterPropertiesSet 方法:接着,如果 Bean 实现了
InitializingBean
接口,Spring 容器将调用afterPropertiesSet
方法。这是 Spring 提供的一种方式,允许 Bean 在所有属性设置之后,进行额外的初始化。 -
自定义初始化方法:最后,如果在 Bean 的定义中通过
init-method
属性指定了初始化方法,或者在使用@Bean
注解定义 Bean 时指定了initMethod
属性,该方法将被执行。这是最灵活的一种自定义初始化方法,因为它允许直接在 Bean 的定义中指定任意的初始化逻辑。
这个顺序确保了不同初始化机制可以协同工作,同时也提供了足够的灵活性,让开发者可以根据需要选择最适合的初始化方式。需要注意的是,尽管 Spring 允许配置多种初始化方法,但在实际开发中,通常建议只使用一种方式来避免潜在的混淆和错误。
LoggingBeanPostProcessor
与其他Bean的关联并不是通过直接的引用或者配置来实现的,而是通过Spring框架的工作机制来自动完成的。当LoggingBeanPostProcessor
被Spring容器识别并注册为一个BeanPostProcessor
后,Spring容器会在Bean的生命周期的特定点调用BeanPostProcessor
的方法。这个过程是由Spring框架自动管理的,开发者不需要手动编写代码来建立这种关联。
Spring容器如何处理BeanPostProcessor
-
自动检测:在Spring容器启动过程中,它会自动检测实现了
BeanPostProcessor
接口的所有Bean,并将这些Bean注册为BeanPostProcessor
。 -
调用时机:对于容器中的每个Bean,在它的初始化方法(如
@PostConstruct
注解方法、InitializingBean
接口的afterPropertiesSet
方法等)被调用之前,Spring容器会遍历所有注册的BeanPostProcessor
,调用它们的postProcessBeforeInitialization
方法。同样,在Bean的初始化方法调用之后,Spring容器会再次遍历所有BeanPostProcessor
,这次调用它们的postProcessAfterInitialization
方法。 -
全局作用:由于
BeanPostProcessor
是在容器级别注册的,它们对容器中所有Bean的初始化过程都有效。这意味着,一旦LoggingBeanPostProcessor
被注册,它就会自动应用于容器中所有Bean的初始化过程,无需额外的配置或引用。
示例
假设我们有一个简单的Bean:
import org.springframework.stereotype.Component;
@Component
public class MyBean {
public MyBean() {
System.out.println("MyBean constructor called.");
}
public void init() {
System.out.println("MyBean init method called.");
}
}
如果我们的Spring应用中包含了LoggingBeanPostProcessor
,那么在MyBean
的构造函数执行后、init
方法执行前后,Spring容器会自动调用LoggingBeanPostProcessor
的postProcessBeforeInitialization
和postProcessAfterInitialization
方法。这样,我们就可以在控制台上看到类似以下的日志输出:
MyBean constructor called.
Before Initialization: myBean
MyBean init method called.
After Initialization: myBean
这个过程完全是自动进行的,开发者不需要在MyBean
中添加任何特定的代码来与LoggingBeanPostProcessor
建立关联。这就是BeanPostProcessor
如何通过Spring框架的机制自动影响到容器中所有Bean的初始化过程的。
Bean 是线程安全的吗?
Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton ,重点关注 singleton 作用域即可。prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:在 Bean 中尽量避免定义可变的成员变量。在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
- spring aop运行时增强,aspect编译时增强,jdk proxy基于实现接口,cglib基于继承
- spring事务可以看下这篇:https://javaguide.cn/system-design/framework/spring/spring-transaction.html#spring-%E6%94%AF%E6%8C%81%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F%E7%9A%84%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86
三个核心类,事务管理器(PlatformTransactionManager),事务definition(TransactionDefinition隔离级别,传播行为,超时,是否只读,回滚规则),事务状态(TransactionStatus:是否是新事物,是否已完成,是否有恢复点,是否只回滚) - 事务传播行为
public void aMethod {
//do something
b.bMethod();
}
1.PROPAGATION_REQUIRED,如果外部有事务,加入该事物。没有新建一个事务,且事务间互不干扰
2.PROPAGATION_REQUIRES_NEW,外部有事务,会独立开启一个新事物,外部事务被挂起。外部没有事务,新建一个事务。a异常,b不会回滚。b抛异常,a会回滚(正常情况内外事务互相独立,b回滚,a也不会回滚,b抛未被捕获的异常时,a才会回滚)
3.PROPAGATION_NESTED,外部有事务,作为外部事务的子事务,a回滚,b也会回滚,但b回滚,a不会回滚
4.PROPAGATION_MANDATORY,外部没事务,直接抛异常 - 自调用问题
当一个方法被标记了@Transactional 注解的时候,Spring 事务管理器只会在被其他类方法调用的时候生效,而不会在一个类中方法调用生效。这是因为 Spring AOP 工作原理决定的。因为 Spring AOP 使用动态代理来实现事务的管理,它会在运行的时候为带有 @Transactional 注解的方法生成代理对象,并在方法调用的前后应用事物逻辑。如果该方法被其他类调用我们的代理对象就会拦截方法调用并处理事务。但是在一个类中的其他方法内部调用的时候,我们代理对象就无法拦截到这个内部调用,因此事务也就失效了。 - 为什么aspectJ取代spring aop能解决自调用问题
当一个方法被标记了 @Transactional 注解时,Spring 默认使用 AOP 代理来管理事务。这意味着当你从外部类调用这个带有 @Transactional 注解的方法时,Spring 会创建一个代理,在这个代理中会开启一个新的事务,然后再调用实际的方法。如果这个方法在同一个类的内部被另一个方法调用,代理就不会介入,因为内部方法调用不会通过代理,这就是为什么在一个类的内部方法调用不会触发事务的原因。
AspectJ 不同于 Spring AOP,它是一种基于编译时的织入(weaving)技术,可以改变你的类文件,而不仅仅是在运行时创建代理。AspectJ 织入可以在编译期(compile-time)、类加载期(load-time)或运行期(runtime)发生,这意味着它可以修改类的实际字节码。因此,即使是内部方法调用,AspectJ 也能够织入事务管理的代码,因为它直接修改了类的字节码,使得事务管理成为了类的内在行为。
使用 AspectJ 来管理事务时,即使是同一类中的方法相互调用,也能够保证 @Transactional 注解的方法被正确地处理,因为事务相关的代码已经被织入到了类的字节码中。这样,无论是外部调用还是内部调用,事务都会按预期工作。不过,需要注意的是,AspectJ 需要额外的编译步骤,并且可能会增加应用程序的复杂性。
- springmvc
核心思想是把业务逻辑、数据、显示分离来组织代码。