目录
一、Bean的初始化过程
1. 加载Spring Bean
2. 解析Bean的定义
3. Bean属性定义
4. BeanFactoryPostProcessor 扩展接口
5. 实例化Bean对象
6. Aware感知
7. 初始化方法
8. 后置处理
9. destroy 销毁
二、Bean的单例与多例模式
2.1 单例模式(Singleton)
2.2 多例模式(Prototype)
2.3 案例演示:
2.3.1 单例模式:
2.3.2 多例模式:
2.4 总结
三、关于bean的生命周期面试题
1. 请详细描述Spring框架Bean的生命周期包括哪些阶段?
2. 请详细描述一下Spring Bean的初始化过程
3. Spring Bean的销毁过程是怎样的?
4. Spring Bean的后置处理器是什么?在项目中如何使用它?
5. Spring Bean的生命周期中,哪些方法可以进行自定义操作?
6. 什么是Bean的作用域?Spring中有哪些Bean的作用域?
7. Bean的作用域是如何实现的?请描述其原理。
8. 请解释Spring中的延迟初始化是什么,如何配置延迟初始化?
一、Bean的初始化过程
1. 加载Spring Bean
通过XML、Java annotation(注解)以及Java Configuration(配置类)等方式加载Spring Bean:这是指在Spring容器启动时,通过配置文件、注解或配置类等方式告诉Spring框架需要创建哪些Bean。
2. 解析Bean的定义
BeanDefinitionReader:这是一个解析器,它将Bean的定义解析成Spring内部的BeanDefinition结构。可以将其理解为将配置文件中的<bean>标签转换为Spring框架内部使用的数据结构。
可理解为:将spring.xml中的 <bean> 标签转换成BeanDefinition结构有点类似于XML解析。
3. Bean属性定义
BeanDefinition:它包含了Bean的各种属性和方法,例如id(标识符)、class(类名)、scope(作用域)、ref(依赖的Bean)等。BeanDefinition实际上是将配置文件中的<bean>标签的定义信息存储到对应的BeanDefinition属性中。
例如:
<bean id="" class="" scope=""> -----> BeanDefinition(id/class/scope)
4. BeanFactoryPostProcessor 扩展接口
BeanFactoryPostProcessor:这是Spring容器的扩展接口,它在Spring容器加载完BeanDefinition之后、Bean实例化之前执行。它可以对Bean的元数据(BeanDefinition)进行加工处理,例如填充或修改BeanDefinition的属性。
5. 实例化Bean对象
BeanFactory:它是Bean的工厂,根据我们的要求生产各种不同的Bean。BeanFactory根据配置文件中的BeanDefinition信息来实例化Bean对象,并进行属性赋值。(根据class属性反射机制实例化对象,反射赋值设置属性)
6. Aware感知
Aware 感知是一种机制,它允许在Bean的生命周期中获取对Spring容器的访问权限。通过实现相应的Aware接口,Bean可以获得与Spring容器的交互能力,以便在特定的生命周期阶段执行自定义操作。
在Spring框架中,有多个Aware接口可供实现,每个接口都提供了不同的功能。以下是一些常见的Aware接口及其作用:
-
BeanNameAware:通过实现BeanNameAware接口,Bean可以获取自己在Spring容器中的名称。这对于需要知道自己在容器中的标识的Bean非常有用。
-
ApplicationContextAware:通过实现ApplicationContextAware接口,Bean可以获取对Spring应用上下文的引用。这使得Bean能够在需要时访问Spring容器中的其他Bean或执行其他与容器相关的操作。
-
BeanFactoryAware:通过实现BeanFactoryAware接口,Bean可以获取对Spring Bean工厂的引用。这使得Bean能够在需要时访问Bean工厂中的其他Bean或执行其他与Bean工厂相关的操作。
通过实现这些Aware接口,Bean可以在初始化过程中获得对Spring容器的访问权限,并在需要时执行特定的操作。这为开发人员提供了更大的灵活性和控制权,使得他们能够根据自己的需求自定义Bean的行为。
7. 初始化方法
如果Bean定义中指定了初始化方法,容器会在属性赋值完成后调用该方法进行一些初始化操作。可以使用@PostConstruct
注解或者实现InitializingBean
接口来指定初始化方法。如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
<bean id="paramAction" class="com.ycxw.beanLife.ParamAction" init-method="init"></bean>
8. 后置处理
BeanPostProcessor:它是一个后置处理器,在Bean对象实例化和依赖注入完成后,在显示调用初始化方法之前或之后添加自定义的逻辑。可以将其类比为AOP中的环绕通知。
注:只有当检测到Bean对象实现了BeanPostProcessor接口时,才会执行Before和After方法。
BeanPostProcessor的执行顺序是:
Before方法 -> 初始化Bean(InitializingBean接口和init-method方法)-> After方法。
9. destroy 销毁
destroy:这是Bean的销毁阶段,如果这个Bean的Spring配置中配置了destroy-method属性,在Spring容器关闭时调用。在销毁阶段,可以执行一些清理工作,例如释放资源等。
<bean id="paramAction" class="com.ycxw.beanLife.ParamAction" destroy-method="destroy"></bean>
二、Bean的单例与多例模式
2.1 单例模式(Singleton)
- 单例模式是指在整个应用程序中只创建一个Bean实例。
- 当使用单例模式时,Spring容器在首次请求该Bean时创建一个实例,并将其缓存起来。之后的每次请求都会返回同一个实例。
- 优点:减少了对象创建和销毁的开销,节省了系统资源。
- 缺点:由于共享同一个实例,可能导致状态信息被多个线程共享,需要注意线程安全问题。
2.2 多例模式(Prototype)
- 多例模式是指每次请求时都会创建一个新的Bean实例。
- 当使用多例模式时,Spring容器在每次请求该Bean时都会创建一个新的实例,而不是返回之前创建的实例。
- 优点:每次请求都会返回一个全新的实例,避免了状态信息共享的问题。
- 缺点:频繁创建和销毁对象可能导致系统资源消耗增加。
2.3 案例演示:
假设我们有一个简单的Counter
类,用于计数:
public class Counter {
private int count;
public int getCount() {
return count;
}
public void increment() {
count++;
}
}
我们将使用Spring配置来演示单例和多例模式的区别。
2.3.1 单例模式:
<bean id="counter" class="com.example.Counter" scope="singleton" />
public class SingletonDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Counter counter1 = context.getBean("counter", Counter.class);
Counter counter2 = context.getBean("counter", Counter.class);
counter1.increment();
System.out.println("Counter 1: " + counter1.getCount()); // 输出:Counter 1: 1
System.out.println("Counter 2: " + counter2.getCount()); // 输出:Counter 2: 1
}
}
在单例模式下,counter1
和counter2
引用的是同一个实例,因此它们的计数值是相同的。
2.3.2 多例模式:
<bean id="counter" class="com.example.Counter" scope="prototype" />
public class PrototypeDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Counter counter1 = context.getBean("counter", Counter.class);
Counter counter2 = context.getBean("counter", Counter.class);
counter1.increment();
System.out.println("Counter 1: " + counter1.getCount()); // 输出:Counter 1: 1
System.out.println("Counter 2: " + counter2.getCount()); // 输出:Counter 2: 0
}
}
在多例模式下,counter1
和counter2
引用的是不同的实例,因此它们的计数值是独立的。
2.4 总结
单例模式和多例模式各有优缺点,具体使用哪种模式取决于应用程序的需求和设计考虑。在需要共享状态或资源的场景下,可以使用单例模式;在需要独立状态或避免状态共享的场景下,可以使用多例模式。
三、关于bean的生命周期面试题
1. 请详细描述Spring框架Bean的生命周期包括哪些阶段?
在Spring框架中,Bean的生命周期包括以下几个阶段:
-
实例化(Instantiation):在这个阶段,Spring会根据配置或注解创建Bean的实例。这可以通过构造函数实例化、工厂方法或者Bean容器中的其他方式来实现。
-
属性赋值(Population):在实例化后,Spring会通过依赖注入(Dependency Injection)或者属性赋值的方式,将Bean的属性值设置好。这可以通过setter方法注入或者字段注入来实现。
-
初始化(Initialization):在属性赋值完成后,Spring会调用Bean的初始化方法。这个方法可以通过实现InitializingBean接口的
afterPropertiesSet()
方法,或者在配置文件中通过init-method
属性指定。 -
使用(In Use):在初始化完成后,Bean进入可用状态,可以被其他对象引用和使用。
-
销毁(Destruction):当Bean不再被需要时,Spring会调用Bean的销毁方法进行资源释放和清理工作。这个方法可以通过实现DisposableBean接口的
destroy()
方法,或者在配置文件中通过destroy-method
属性指定。
Bean的销毁阶段只有在容器关闭时才会触发,或者在使用单例作用域的Bean时,当Bean不再被引用时被垃圾回收器回收。以上是Spring框架中Bean的典型生命周期阶段,每个阶段都提供了相应的扩展点,可以在不同阶段进行自定义操作。
2. 请详细描述一下Spring Bean的初始化过程
👆👆👆 请看本文章第一点内容 !!!👆👆👆
3. Spring Bean的销毁过程是怎样的?
Spring Bean的销毁过程可以分为以下几个步骤:
-
关闭容器:当Spring容器关闭时,会触发Bean的销毁过程。这可以通过调用
ApplicationContext
的close()
方法或者销毁容器的生命周期回调来实现。 -
调用Bean的销毁方法:在容器关闭时,Spring会调用Bean的销毁方法进行资源释放和清理工作。这个方法可以通过实现DisposableBean接口的
destroy()
方法,或者在配置文件中通过destroy-method
属性指定。 -
应用Bean后置处理器:在销毁方法调用之前和之后,Spring会应用注册的Bean后置处理器(BeanPostProcessor)。后置处理器可以对Bean进行额外的处理,例如在Bean销毁前后进行一些自定义逻辑。
Bean的销毁阶段只有在容器关闭时才会触发,或者在使用单例作用域的Bean时,当Bean不再被引用时被垃圾回收器回收。对于原型作用域的Bean,默认情况下,Spring不会管理其销毁过程,需要开发者手动进行资源释放。
同时,可以通过实现DisposableBean
接口或者在配置文件中指定destroy-method
属性来定义Bean的销毁方法。在销毁方法中,可以进行一些资源释放、数据库连接关闭等清理工作,确保系统的正常关闭和资源的释放。
4. Spring Bean的后置处理器是什么?在项目中如何使用它?
Spring Bean后置处理器(BeanPostProcessor)是一种特殊类型的Bean,用于在容器实例化、配置和初始化Bean的过程中进行自定义的处理操作。
在项目中使用Bean后置处理器,可以通过以下步骤:
-
创建后置处理器类:实现
BeanPostProcessor
接口,该接口包含两个方法:postProcessBeforeInitialization()
和postProcessAfterInitialization()
。import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 在Bean初始化之前进行自定义处理操作 return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 在Bean初始化之后进行自定义处理操作 return bean; } }
-
注册后置处理器:在Spring配置文件中,将自定义的后置处理器注册到容器中。
<bean class="com.example.MyBeanPostProcessor" />
-
启动容器:在项目启动时,启动Spring容器,容器会自动识别并应用注册的后置处理器。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
通过使用Bean后置处理器,可以在Bean的初始化前后进行自定义操作,例如对Bean进行增强、属性修改、代理等。常见的应用场景包括:
- 属性值注入前后的自定义处理。
- 在Bean初始化前后执行一些特定的逻辑。
- 动态代理Bean的生成。
- 对特定类型的Bean进行特殊处理等。
需要注意的是,Bean后置处理器会对容器中的所有Bean实例进行处理,因此在编写后置处理器时需要注意逻辑的准确性和性能的影响。
5. Spring Bean的生命周期中,哪些方法可以进行自定义操作?
在Spring Bean的生命周期中,可以进行自定义操作的方法主要包括以下几个:
-
初始化方法(Initialization methods):
- 在配置文件中通过
init-method
属性指定的方法。 - 在Bean类中实现
InitializingBean
接口,并重写afterPropertiesSet()
方法。
初始化方法用于在Bean实例化后,属性注入完成之后执行一些初始化逻辑,例如初始化资源、建立连接等。您可以根据需要选择其中一种方式来定义初始化方法。
- 在配置文件中通过
-
销毁方法(Destruction methods):
- 在配置文件中通过
destroy-method
属性指定的方法。 - 在Bean类中实现
DisposableBean
接口,并重写destroy()
方法。
销毁方法用于在容器销毁Bean之前执行一些清理操作,例如释放资源、关闭连接等。您可以根据需要选择其中一种方式来定义销毁方法。
- 在配置文件中通过
-
后置处理器(Post-processors):
后置处理器可以在Bean的初始化前后对Bean进行自定义处理。
postProcessBeforeInitialization()
方法在初始化方法调用之前被调用,postProcessAfterInitialization()
方法在初始化方法调用之后被调用。通过实现这些方法,您可以对Bean进行额外的操作,例如修改属性值、添加代理等。 - 实现
BeanPostProcessor
接口,并重写postProcessBeforeInitialization()
和postProcessAfterInitialization()
方法。
除了上述方法外,还可以使用其他扩展机制来进行自定义操作,例如使用@PostConstruct
和@PreDestroy
注解来标记初始化方法和销毁方法,或者通过实现BeanFactoryPostProcessor
和BeanDefinitionRegistryPostProcessor
接口来对Bean的定义进行修改。
通过自定义这些方法,您可以在Spring Bean的生命周期中添加自己的逻辑,以满足特定的需求和业务场景。
6. 什么是Bean的作用域?Spring中有哪些Bean的作用域?
在Spring中,Bean的作用域(Scope)指的是定义了Bean的生命周期和可见范围的一组规则。作用域决定了在不同的上下文中访问和使用Bean的方式。
Spring框架提供了以下几种常用的Bean作用域:
-
Singleton(默认):单例作用域。在整个应用程序中,只会创建一个Bean实例,并且所有对该Bean的请求都会返回同一个实例。这是Spring的默认作用域。
-
Prototype:原型作用域。每次请求Bean时,都会创建一个新的Bean实例。每个实例都有自己的状态,因此在使用时需要注意管理和维护。
-
Request:请求作用域。每个HTTP请求都会创建一个新的Bean实例,并且该Bean实例仅在当前请求的范围内可见。在同一个请求内的不同组件可以共享同一个实例。
-
Session:会话作用域。每个HTTP会话(Session)都会创建一个新的Bean实例,并且该Bean实例仅在当前会话的范围内可见。在同一个会话内的不同请求可以共享同一个实例。
-
Global Session:全局会话作用域。仅在基于portlet的web应用中有效。它类似于Session作用域,但是不同portlet之间可以共享同一个实例。
除了上述常用的作用域,Spring还提供了其他一些作用域,如WebSocket作用域、Application作用域等,用于满足特定的应用需求。
要指定Bean的作用域,可以使用@Scope
注解或在XML配置文件中进行配置。例如,使用@Scope("prototype")
将Bean的作用域设置为原型作用域。
需要根据具体的业务需求和场景选择合适的Bean作用域。默认情况下,使用单例作用域是最常见和推荐的方式,但在某些情况下,原型作用域或其他作用域可能更适合。
7. Bean的作用域是如何实现的?请描述其原理。
在Spring中,Bean的作用域是通过Bean的创建和管理方式来实现的。不同作用域的Bean会有不同的创建和销毁策略。
-
Singleton作用域:
- 当容器启动时,Spring会创建并初始化Singleton作用域的Bean实例,并将其放入容器中的单例缓存池中。
- 之后,每次请求该Bean时,容器会直接从单例缓存池中返回已经创建好的Bean实例。
- Singleton作用域的Bean在容器关闭时会被销毁。
-
Prototype作用域:
- 当容器接收到对Prototype作用域Bean的请求时,会创建一个新的Bean实例。
- 容器会对该实例进行初始化和依赖注入,并返回给请求方。
- 对Prototype作用域的Bean,容器不会进行进一步的管理和跟踪,不负责其生命周期的管理。因此,在使用Prototype作用域的Bean时,需要手动管理其销毁。
-
Request作用域和Session作用域:
- 当容器接收到对Request作用域或Session作用域的Bean的请求时,会检查当前的请求或会话中是否存在对应的Bean实例。
- 如果存在,则直接返回该实例;如果不存在,则创建一个新的Bean实例并放入请求或会话中,以便后续的访问和共享。
- Request作用域和Session作用域的Bean在请求或会话结束时会被销毁。
实现Bean作用域的关键在于容器的管理和跟踪机制。容器会根据作用域的不同,采取相应的策略来创建、缓存和销毁Bean实例。对于Singleton作用域的Bean,容器会负责管理其单例缓存池;对于Prototype作用域的Bean,容器只负责创建实例,不负责管理其生命周期;对于Request作用域和Session作用域的Bean,容器会将实例与请求或会话关联起来,以便后续的访问和共享。
需要注意的是,作用域仅仅决定了Bean实例的创建和可见范围,并不影响Bean定义本身。因此,无论是Singleton作用域还是Prototype作用域,Bean的定义方式都是相同的,只是在创建和管理上有所区别。
8. 请解释Spring中的延迟初始化是什么,如何配置延迟初始化?
在Spring中,延迟初始化(Lazy Initialization)是指在容器启动时不立即创建所有的Bean实例,而是在第一次使用该Bean时才进行创建。这可以有效地减少启动时间和资源消耗,特别是对于那些初始化耗时较长或者很少被使用的Bean。
要配置延迟初始化,可以使用两种方式:
-
使用注解配置:
- 在类级别上使用
@Lazy
注解,表示该类的Bean实例需要延迟初始化。例如:@Lazy
。 - 在XML配置文件中,使用
lazy-init="true"
属性来配置延迟初始化。例如:<bean id="myBean" class="com.example.MyBean" lazy-init="true" />
- 在类级别上使用
-
使用编程方式配置:
- 在Java配置类中,使用
@Lazy
注解来标记需要延迟初始化的Bean。例如:@Lazy
。 - 在XML配置文件中,使用
lazy-init="true"
属性来配置延迟初始化。例如:<bean id="myBean" class="com.example.MyBean" lazy-init="true" />
- 在Java配置类中,使用
无论使用注解还是XML配置,配置了延迟初始化的Bean在容器启动时不会被立即创建。而是在第一次被请求或者被依赖注入时才会进行实例化和初始化。这样可以延迟Bean的创建,节省资源并提高应用程序的启动性能。
需要注意的是,延迟初始化只适用于Singleton作用域的Bean。对于Prototype作用域的Bean,Spring无法延迟初始化,因为每次请求都需要创建一个新的实例。
延迟初始化可以根据具体的业务需求和性能考虑来选择是否使用。对于那些启动时间较长或者很少被使用的Bean,延迟初始化可以提高应用程序的启动性能和资源利用率。但对于那些频繁被使用的Bean,延迟初始化可能会导致延迟的开销,需要权衡利弊进行选择。