Java开发学习之Bean的作用域和生命周期详解

2022-11-13 10:11:47 作用 生命周期 详解

一、Bean 的作用域

在之前学习Java基础的时候,有接触到作用域这样的概念。一个变量并不一定在任何区域都是有效的,限定这个变量的可用性的代码范围就是该变量的作用域。

但是在这里 Bean 的作用域的概念和以前所认为的作用域有所不同。

Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式。

接下来,将会举一个案例来讲讲什么是作用域,什么是行为模式

案例概要:

创建一个共有的 Bean ,使用者A和使用者B都对该 Bean 进行了使用

使用者A在进行使用的时候,创建一个新的变量接收注入进来的 Bean,进行修改,将修改后的结果进行返回;

使用者B 直接将注入进来的 Bean 进行返回,不进行任何操作

代码实现:

步骤一:创建出一个公共的 Bean

@Component
public class UserComponent {
    @Bean
    public User getUser() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setPassWord("111111");
        return user;
    }
}

步骤二:使用者A获取到公共 Bean,进行修改

@Controller
public class UserControllerA {
    @Autowired
    private User user;
    public User getUser1() {
        System.out.println("使用者A拿到的原始user:" + user);
        User myUser = user;
        myUser.setName("李四");
        return myUser;
    }
}

步骤三:使用者B直接将获取到的公共的 Bean 进行返回

@Controller
public class UserControllerB {
    @Autowired
    private User user;
    public User getUser2() {
        return user;
    }
}

步骤四:main 中获取 UserControllerA 类和 UserControllerB 类使用查看

public class Start {
    public static void main(String[] args) {
        //获取 Spring 上下文
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        //获取到Spring容器中的 UserControllerA 类(Bean 对象)
        UserControllerA userControllerA = context.getBean("userControllerA",UserControllerA.class);
        //使用 Bean 对象
        System.out.println("使用者A->"+userControllerA.getUser1());
		//获取到Spring容器中的 UserControllerA 类(Bean 对象)
        UserControllerB userControllerB = context.getBean("userControllerB",UserControllerB.class);
        //使用 Bean 对象
        System.out.println("使用者B->"+userControllerB.getUser2());
    }
}

预期结果:

使用者 A修改后,其结果是修改过的;使用者 B 没有修改过,其结果应该和原始user一样

结果显示:

和预期结果有所不同,使用者 A 和使用者 B 所得到的结果都是被修改过的。

这是因为在 Spring 中,Bean 默认情况下是单例状态,大家用的都是同一份对象,是全局共享的,当有其他人修改了该对象,另一个人所获取到的对象就是被修改过的,这便是 Bean 六大作用域之一——单例作用域(singleton)

在写 WEB 项目的时候,我们知道 DataSource 就是单例模式,使用单例,好处多多,可以确保所有对象都访问唯一实例,而且减少了内存的开支和系统性能的开销,因此 Bean 默认情况下是单例状态。

若想要按照预期结果输出,就需要将 Bean 的作用域设置成原型作用域,即无论谁来使用 Bean 对象,获取到的都是原始的 Bean 对象,大家各玩各的。

需要在注入的对象上使用注解修改作用域,有以下两种方法(Scope就是作用域的意思)

  • 直接将作用域以 String 类型写到()中
  • 使用全局参数,类似于枚举
@Component
public class UserComponent {
    //@Scope("prototype")    //方法一
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //方法二
    @Bean
    public User getUser() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setPassWord("111111");
        return user;
    }
}

Bean 的 6 种作用域

singleton:单例作用域

  • Bean 在 ioc 容器中只存在一个实例
  • 当 Bean 对象属性状态无需更新时使用该作用域
  • Spring 支持

prototype:原型作⽤域/多例作⽤域

  • 每次获取该 Bean 时都会创建新的实例,即获取的都是原始的 Bean
  • 当 Bean 对象属性状态会更新时使用该作用域
  • Spring 支持

request:请求作用域

  • 每次 Http 请求都会创建新的 Bean 实例
  • 一次 HTTP 请求和响应共享的 Bean
  • 限定 springMVC

session:会话作用域

  • 在一个 HTTP session中,创建一个 Bean 实例
  • 用户会话共享一个 Bean
  • 限定 Springmvc

application:全局作用域

  • 在一个 HTTP Servlet Context中,创建一个 Bean 实例
  • 一个 WEB 上下文中共享一个 Bean
  • 限定 SpringMVC

websocket: HTTP WebSocket 作⽤域(不常用)

  • 在一个 HTTP WebSocket 中,创建一个 Bean 实例
  • 限定 Spring WebSocket

单例作用域和全局作用域比较像,但全局作用域范围没有单例作用域大,前者是 Spring 核心的作用域,后者是 Spring Web 中的作用域,前者作用于 IoC 容器,后者作用于 Servlet 容器

二、Spring 的执行流程

Spring 的执行流程也可以说是 Bean的执行流程,主要分成4部分

  • 启动 Spring 容器
  • 加载 XML 文件,实例化 Bean(进行内存的分配)
  • Bean 存到 Spring 容器中(五大类注解,方法注解)
  • 将存储的 Bean 中的注入的对象属性进行初始化,即进行装配(取出 Bean)

三、Bean 的生命周期

Bean 的生命周期即 Bean 从诞生到销毁的整个过程

实例化 Bean 对象,申请内存空间

设置 Bean 的属性,进行依赖注入和装配

Bean 的初始化

  • 各种 Aware 感知:BeanNameAware、BeanFactoryAware、ApplicationContextAware、…
  • 执⾏ BeanPostProcessor 初始化前置⽅法
  • 初始化方法:构造器方法 @PostConstructor (对象加载完依赖注入后执行)
  • 初始化方法:init-method
  • 执⾏ BeanPostProcessor 初始化后置⽅法

使用 Bean

销毁 Bean

  • 销毁方法:@PreDestroy
  • 接口方法:DisposableBean
  • 销毁方法:destroy-method

补充

实例化和初始化的区别

  • 实例化:这里的实例化和从前的实例化对象是有区别的,这里的实例化就负责内存的分配,一个从无到有的过程。实例化和属性设置时 Java 级别的系统“事件”,操作过程是不可人工干预的
  • 初始化:是对 Bean 进行填充的过程,程序员可以进行介入,真正的将 Bean 放到 Spring 容器中

@PostConstructor 和 @PreDestroy 是注解方式进行初始化和注销,init-method 和 destroy-method 是 XML 方法进行初始化和注销,一般只要使用其中的一种进行初始化

设置属性一定要在初始化之前,因为初始化也可能需要使用到注入的对象,如果没有进行属性的设置,初始化就会出现问题

案例:生命周期演示

@Component
public class BeanLife implements BeanNameAware {
    @Override
    public void setBeanName(String s) {
        System.out.println("BeanName 感知:"+ s);
    }
    @PostConstruct
    public void postConstructor() {
        System.out.println("执行初始化方法:PostConstructor");
    }
    @PreDestroy
    public void preDestroy() {
        System.out.println("执行销毁方法:PreDestroy");
    }
    public void initMethod() {
        System.out.println("执行初始化方法:init-method");
    }
    public void destroyMethod() {
        System.out.println("执行销毁方法:destroy-method");
    }
}

XML

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 配置一下:bean注解扫描的根路径(方面后面更简单存储对象到spring容器)-->
    <content:component-scan base-package="com.bit.beans"></content:component-scan>
    <beans>
        <bean id="beanLife" class="com.bit.beans.Component.BeanLife" init-method="initMethod"
        destroy-method="destroyMethod"></bean>
    </beans>
</beans>

调用

public class Start {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new
                ClassPathXmlApplicationContext("spring.xml");
        BeanLife beanLife = context.getBean("beanLife",BeanLife.class);
        System.out.println("---使用 Bean 对象---");
        System.out.println("---注销 Bean 对象--- ");
        context.destroy();//容器的销毁相当于销毁所有的 Bean
    }
}

结果显示:

流程图展示:

到此这篇关于Java开发学习之Bean的作用域和生命周期详解的文章就介绍到这了,更多相关Bean作用域和生命周期内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章