Spring框架之揭秘Bean的生命周期与单例详解【面试题超详细回答】

目录

一、前言

1.1.介绍Spring框架和Bean的概念

二、Bean的实例化阶段

2.1.Bean的实例化过程

2.2.介绍默认构造函数和工厂方法的使用

三、Bean的初始化阶段

3.1.InitializingBean接口和@PostConstruct注解的使用

3.2.Bean的初始化方法配置和执行顺序

四、Bean的销毁阶段

4.1.DisposableBean接口和@PreDestroy注解的使用

4.2.Bean的销毁方法配置和执行顺序

五、面试题讲解

5.1.Bean的生命周期

结论:

5.2.Spring中JavaBean是单例还是多例

论证:

5.3.单例的JavaBean和多例JavaBean是什么的时候才会创建

论证:

5.4.我们使用单例一定会初始化JavaBean吗

论证:


一、前言

1.1.介绍Spring框架和Bean的概念

嘿,朋友们!让我来给大家解析一下Spring框架和Bean的故事。

Spring框架就像是一个守护神,它包揽了Java应用程序的管理工作。它有点像一场超级派对组织者,让你能够轻松地管理和控制你的应用程序。它的目标是提供一种高效、灵活的方式来构建企业级应用程序。

那么Bean又是什么呢?Bean其实就是Spring框架中的真实对象。你可以把Bean想象成小伙伴们,在Spring框架的聚会上尽情闹腾!每个Bean都有自己的生命周期,就像人类一样有出生、成长、工作和退休等阶段。

首先,Bean的实例化就像我们的诞生一样,Spring负责创建Bean对象并把它们装载到容器中。接着,Bean的属性赋值就像我们在成长过程中学习新技能一样,它们被注入各种属性,变得丰富多彩。

接下来,是Bean的初始化阶段。就像我们开始工作前需要做准备一样,Bean也可以执行一些初始化操作,包括调用特定的初始化方法,或者通过Bean后置处理器做一些善后工作。

最后,当应用程序不再需要某个Bean时,它就像我们退休一样,可以执行一些销毁操作,例如释放资源、关闭数据库连接等。

总之,Spring框架和Bean就像是一个个聚会,这些小伙伴们在其中进出各个阶段,带来欢乐和成就。不过,别担心,Spring框架是一个负责任的派对组织者,它会确保每个Bean都会得到妥善地管理和照顾!

希望这个幽默风趣的介绍让你对Spring框架和Bean有了更好的理解!

二、Bean的实例化阶段

2.1.Bean的实例化过程

Bean的实例化过程是指将定义的Bean转换为可用的对象实例的过程。在Spring框架中,Bean的实例化可以分为以下几个阶段:

  1. 加载Bean的定义:使用配置文件(如XML配置文件)或注解方式将Bean的定义加载到容器中,由Spring框架负责解析。

  2. 实例化Bean:根据Bean的定义和配置信息,在内存中创建一个Bean的实例。这可以通过构造函数实例化、工厂方法实例化或通过对象反射等方式来进行。

  3. 属性赋值:对已经实例化的Bean对象进行属性赋值,包括基本数据类型、引用类型和集合类型等。属性可以通过注解或XML配置文件进行依赖关系的注入。

  4. Aware接口回调:如果Bean实现了Aware接口,Spring会自动检测并调用相应的回调方法,例如ApplicationContextAware接口可以获取ApplicationContext对象。

  5. 自定义初始化方法:如果Bean配置了初始化方法(可以通过注解或XML配置文件),Spring会在Bean完成属性赋值后调用该方法进行一些自定义的初始化操作。

  6. 后置处理器方法调用:如果配置了Bean后置处理器(BeanPostProcessor),Spring会自动检测并调用其相关方法进行额外的处理操作。

  7. Bean准备就绪:经过以上步骤,Bean实例化过程完成,可以被容器管理和使用。

2.2.介绍默认构造函数和工厂方法的使用

默认构造函数: 默认构造函数是一个无参的构造函数,它没有任何参数传递给对象创建过程。Spring会自动调用默认构造函数来实例化Bean对象。这种方式被广泛应用,可以方便地创建对象,并且不需要通过其他方式传递参数。例如:

public class MyBean {
    // 默认构造函数
    public MyBean() {
        // 初始化操作
    }
}

工厂方法: 工厂方法是一种通过特殊的方法来实例化Bean对象的方式。通常情况下,这个方法是在一个专门的工厂类中定义的。通过工厂方法,我们可以有更多的灵活性来创建Bean对象,并且可以自定义传递参数等操作。例如:

public class MyBeanFactory {
    // 工厂方法
    public static MyBean createMyBean() {
        // 创建Bean对象的逻辑
        return new MyBean();
    }
}

示例代码:展示如何使用默认构造函数和工厂方法来实例化Bean对象:

public class MyBean {
    private String message;

    // 默认构造函数
    public MyBean() {
        this.message = "Hello, world!";
    }

    // getter和setter方法

    public static MyBean createMyBean() {
        MyBean myBean = new MyBean();
        myBean.setMessage("Hello, Spring!");
        return myBean;
    }
}

三、Bean的初始化阶段

3.1.InitializingBean接口和@PostConstruct注解的使用

InitializingBean接口使用

该接口定义了一个方法afterPropertiesSet(),当Bean的所有属性都被设置好后,Spring容器会自动调用这个方法完成初始化操作。

import org.springframework.beans.factory.InitializingBean;

public class MyBean implements InitializingBean {
    private String message;
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean: Bean is being initialized with message: " + message);
    }
}

@PostConstruct注解使用

这个注解标记在Bean的初始化方法上,表示该方法会在Bean的属性设置完成后自动执行,完成Bean的初始化操作。

import javax.annotation.PostConstruct;

public class MyBean {
    private String message;
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct: Bean is being initialized with message: " + message);
    }
}

需要注意的是,InitializingBean接口和@PostConstruct注解可以同时使用,但这两种方式并不是必须的,您可以根据需求选择其中之一进行Bean的初始化。

在Spring中,还可以通过配置文件来指定Bean的初始化方法,在XML配置文件中使用<bean>标签的init-method属性来设置。示例如下:

<bean id="myBean" class="com.example.MyBean" init-method="init">
    <property name="message" value="Hello World" />
</bean>

3.2.Bean的初始化方法配置和执行顺序

无论是使用InitializingBean接口、@PostConstruct注解还是XML配置文件,它们都可以用来指定Bean的初始化方法,并且执行顺序是相同的:首先执行属性的依赖注入,然后执行初始化方法。

public class Example {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        MyBean bean = context.getBean(MyBean.class);
        // Bean已经被初始化完成并且可以使用
        
        ((ConfigurableApplicationContext) context).close();
    }
}

在上述示例中,创建了一个ApplicationContext对象并加载了一个XML配置文件。通过调用getBean()方法获取MyBean的实例,Spring容器会自动进行属性的注入和初始化操作。当我们关闭应用程序的时候,可以调用((ConfigurableApplicationContext) context).close()方法关闭容器。

四、Bean的销毁阶段

4.1.DisposableBean接口和@PreDestroy注解的使用

DisposableBean接口使用

该接口定义了一个方法destroy(),当Bean需要被销毁时,Spring容器会自动调用这个方法完成清理工作。示例如下:

import org.springframework.beans.factory.DisposableBean;

public class MyBean implements DisposableBean {
    private String message;
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean: Bean is being destroyed with message: " + message);
    }
}

@PreDestroy注解使用

这个注解标记在Bean的销毁方法上,表示该方法会在需要销毁Bean时自动执行,完成清理工作。示例如下:

import javax.annotation.PreDestroy;

public class MyBean {
    private String message;
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    @PreDestroy
    public void cleanup() {
        System.out.println("@PreDestroy: Bean is being destroyed with message: " + message);
    }
}

Bean的销毁也可以通过配置文件来指定Bean的销毁方法,在XML配置文件中使用<bean>标签的destroy-method属性来设置。示例如下:

<bean id="myBean" class="com.example.MyBean" destroy-method="cleanup">
    <property name="message" value="Goodbye World" />
</bean>

4.2.Bean的销毁方法配置和执行顺序

首先执行Bean的销毁方法,然后容器关闭或销毁。

public class Example {
    public static void main(String[] args) {
        // 创建并启动Spring容器
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        // 获取Bean实例
        MyBean bean = context.getBean(MyBean.class);
        
        // 使用Bean...
        
        // 手动关闭容器
        context.close();
    }
}

在上述示例中,我们创建了一个ApplicationContext对象并加载了一个XML配置文件。通过调用getBean()方法获取MyBean的实例,Spring容器会自动执行Bean的销毁方法。当我们手动关闭容器时,可以调用context.close()方法触发Bean的销毁。

五、面试题讲解

5.1.Bean的生命周期

1.通过XML、Java annotation(注解)以及Java Configuration(配置类)等方式加载Spring Bean

2.BeanDefinitionReader:解析Bean的定义。在Spring容器启动过程中,会将Bean解析成Spring内部的BeanDefinition结构;理解为:将spring.xml中的<bean>标签转换成BeanDefinition结构有点类似于XML解析

3.BeanDefinition:包含了很多属性和方法。例如:id、class(类名)、scope、ref(依赖的bean)等等。其实就是将bean(例如<bean>)的定义信息存储到这个对应BeanDefinition相应的属性中例如:

<bean id="" class="" scope=""> -----> BeanDefinition(id/class/scope)

4.BeanFactoryPostProcessor:是Spring容器功能的扩展接口。

注意:

1)BeanFactoryPostProcessor在spring容器加载完BeanDefinition之后,

在bean实例化之前执行的

2)对bean元数据(BeanDefinition)进行加工处理,也就是BeanDefinition

属性填充、修改等操作

5.BeanFactory:bean工厂。它按照我们的要求生产我们需要的各种各样的bean。

例如:

BeanFactory -> List<BeanDefinition>

BeanDefinition(id/class/scope/init-method)

<bean class="com.zking.spring02.biz.BookBizImpl"/>

foreach(BeanDefinition bean : List<BeanDefinition>){

   //根据class属性反射机制实例化对象

   //反射赋值设置属性

}

6.Aware感知接口:在实际开发中,经常需要用到Spring容器本身的功能资源

例如:BeanNameAware、ApplicationContextAware等等

BeanDefinition 实现了 BeanNameAware、ApplicationContextAware

7.BeanPostProcessor:后置处理器。在Bean对象实例化和引入注入完毕后,在显示调用初始化方法的前后添加自定义的逻辑。(类似于AOP的绕环通知)前提条件:如果检测到Bean对象实现了BeanPostProcessor后置处理器才会执行Before和After方法

BeanPostProcessor

1)Before

2)调用初始化Bean(InitializingBean和init-method,Bean的初始化才算完成)

3)After

8.destory:销毁

结论:

1.xml/annotation/configuation 配置JavaBean。

2.BeanDefinitionReader解析配置的Javabean得到BeanDefinition,最终得到一个List集合。

3.触发BeanFactoryPostProcessor,JavaBean初始化之前执行自己的业务。

4.spring中的beanFactory,会通过List<BeanDefinition>集合遍历初始化所有的JavaBean对象(反射实例化)。

5.如果我们自己的JavaBean需要调动spring上下文中的资源,那么需要实现*Aware感知接口

6.如果自己的JavaBean已经初始化好了,还需要做扩展,那么需要借助BeanPostProcessor来实现。

7.销毁(destory)。

5.2.Spring中JavaBean是单例还是多例

1.默认是单例的,但是可以配置多例

2.单例的优点,节约内存,弊端是有变量污染(多例的优点是无变量污染,但是极其消耗内存)

一个简短风趣的故事来表达单例和多例的区别以及各自的优缺点。


在一个奇幻的魔法学校里,有一位特殊的老师叫做“魔力曼”。他拥有超强的魔法能力,并且负责教授学生们各种神奇的法术。

对于魔力曼来说,他是一个不折不扣的单例。整个学校只有他一个人具备如此强大的魔法力量,每个学生和教职员工都会向他寻求帮助和指导。他总是被围绕在学校的中心,众人都追捧他的存在。

然而,由于魔力曼的特殊地位,他的魔法力量渗透到了整个学校的环境中。无论是在课堂上还是在学校其他地方,都充满了他独特的魔法气息。有时候,当学生们试图施展自己的法术时,结果却受到了魔力曼的力量影响,产生了意想不到的效果。

相比之下,还有一位老师叫做“多地云”。她是一个多例,每个班级都能找到她的身影。她拥有温和而灵活的魔法力量,善于根据学生的需求和情况进行调整。

当学生们在多地云的指导下施展法术时,她会根据不同的班级环境和需求,帮助他们发挥出最大的潜力。由于她的魔法力量不会污染其他班级的影响,所以学生们能够更加自由地探索和发展自己的魔法技能。

这个故事告诉我们,单例和多例各有优缺点。单例的优势在于集中管理和权威性,但也容易产生变量污染的问题。而多例则可以为不同场景提供定制化的服务,避免了变量污染的潜在问题。

在设计软件时,我们应该根据具体需求选择适合的模式。对于需要全局统一访问和共享资源的情况,可以使用单例。而对于需要灵活、无变量污染的场景,多例会是更好的选择。我们需要平衡单例和多例的优缺点,并根据具体需求做出明智的选择。

论证:

ParamAction.java

package com.csdn.xw.aop.beanLife;

import java.util.List;

public class ParamAction {
	private int age;
	private String name;
	private List<String> hobby;
	private int num = 1;
	// private UserBiz userBiz = new UserBizImpl1();

	public ParamAction() {
		super();
	}

	public ParamAction(int age, String name, List<String> hobby) {
		super();
		this.age = age;
		this.name = name;
		this.hobby = hobby;
	}

	public void execute() {
		// userBiz.upload();
		// userBiz = new UserBizImpl2();
		System.out.println("this.num=" + this.num++);
		System.out.println(this.name);
		System.out.println(this.age);
		System.out.println(this.hobby);
	}
}

spring-context.xml

  <bean id="paramAction" class="com.csdn.xw.aop.beanLife.ParamAction">
        <constructor-arg name="name" value="三丰"></constructor-arg>
        <constructor-arg name="age" value="21"></constructor-arg>
        <constructor-arg name="hobby">
            <list>
                <value>抽烟</value>
                <value>烫头</value>
                <value>大保健</value>
            </list>
        </constructor-arg>
    </bean>

Test测试类

package com.csdn.xw.aop.beanLife;

import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

/*
 * spring	bean的生命週期
 * spring	bean的單例多例
 */
public class Demo2 {
	// 体现单例与多例的区别
	@Test
	public void test1() {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml");
//		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml");
		ParamAction p1 = (ParamAction) applicationContext.getBean("paramAction");
		ParamAction p2 = (ParamAction) applicationContext.getBean("paramAction");
		// System.out.println(p1==p2);
		p1.execute();
		p2.execute();
		
//		单例时,容器销毁instanceFactory对象也销毁;多例时,容器销毁对象不一定销毁;
		applicationContext.close();
	}

	// 体现单例与多例的初始化的时间点 instanceFactory
	@Test
	public void test2() {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml");
	}

	// BeanFactory会初始化bean对象,但会根据不同的实现子类采取不同的初始化方式
	// 默认情况下bean的初始化,单例模式立马会执行,但是此时XmlBeanFactory作为子类,单例模式下容器创建,bean依赖没有初始化,只有要获取使用bean对象才进行初始化
	@Test
	public void test3() {
		// ClassPathXmlApplicationContext applicationContext = new
		// ClassPathXmlApplicationContext("/spring-context.xml");

		Resource resource = new ClassPathResource("/spring-context.xml");
		BeanFactory beanFactory = new XmlBeanFactory(resource);
//		InstanceFactory i1 = (InstanceFactory) beanFactory.getBean("instanceFactory");
		
	}

}

运行结果:

这一次我们手动设置为多例scope="prototype"再看看

5.3.单例的JavaBean和多例JavaBean是什么的时候才会创建

 单例:JavaBean跟着Spring上下文初始化,容器生对象生,容器死对象死

 多例:JavaBean是使用的时候才会创建,销毁跟着JVM走

论证:

InstanceFactory.java

package com.csdn.xw.aop.beanLife;

public class InstanceFactory {
	public void init() {
		System.out.println("初始化方法");
	}

	public void destroy() {
		System.out.println("销毁方法");
	}

	public void service() {
		System.out.println("业务方法");
	}
}

spring-context.xml

  <bean id="instanceFactory" class="com.csdn.xw.aop.beanLife.InstanceFactory"
          scope="prototype" init-method="init" destroy-method="destroy"></bean>

可以看到上面的xml配置的是多例的,我们调用一下test2看我们的"Bean初始化"了没有?

可以看到并没有,因为我们只是获取spring的上下文对象而没有去获取Bean对象,现在我们换成单例scope="singleton"来看看结果。

 这时候我们发现不管我们有无获取Bean对象,都已经创建JavaBean了。

总结:为什么单例是容器创建完了它就初始化了呢?因为单例的思想是不管你什么时候用都要去创建,要是等你浏览器发个请求,再去创建是非常降低用户的体验感,反正只会创建一次何不在你启动项目的时候就创建好了,把时间的消耗放到了启动项目上。如果是多例,有一百个你就创建一百个吗?一千个你也创建一千个,万一我一千个我只使用一个呢?剩下的九百九十九个就是浪费了,所以,多例只会在你使用的时候创建。

5.4.我们使用单例一定会初始化JavaBean吗

BeanFactory会初始化bean对象,但会根据不同的实现子类采取不同的初始化方式,默认情况下bean的初始化,单例模式立马会执行,但是此时XmlBeanFactory作为子类,单例模式下容器创建,bean依赖没有初始化,只有要获取使用bean对象才进行初始化。

论证:

调用test3查看一下结果

XmlBeanFactory作为子类,单例模式下容器创建,bean依赖没有初始化,那我们获取并使用一下

 这时候Bean就被创建了。

到这里我的分享就结束了,欢迎到评论区探讨交流!!

如果觉得有用的话还请点个赞吧 ♥  ♥

猜你喜欢

转载自blog.csdn.net/weixin_74318097/article/details/132358501
今日推荐