Bean 的作用域和生命周期

Bean 的作用域和生命周期

  • Bean 的作用域
    • 案例
    • Bean 的六种作用域
    • Bean 的多例模式
      • 直接通过 prototype
      • 通过 ConfigurableBeanFactory
  • Spring 的执行流程
  • Bean 的生命周期
    • 生命周期代码演示

Bean 的作用域

在使用 Bean 的时候,一个公共的 Bean,交给 A用户 和 B用户 来使用,如果 A用户 偷偷修改了 Bean 的数据,那么 B用户 拿到的数据和预期的就不一样了。

案例

先在 Spring 当中存储一个 User 对象:

@Component
public class Users {
    @Bean
    public User user1() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }
}

A用户 拿到对象,并作出修改:

@Component
public class BeanScope1 {
    @Autowired
    private User user1;
    public User getUser() {
        User user = user1;
        user.setName("李四");
        return user;
    }
}

把张三修改成了李四,再获取对象进行输出的时候:

@Component
public class BeanScope1 {
    @Autowired
    private User user1;
    public User getUser() {
        User user = user1;
        user.setName("李四");
        return user;
    }
}

运行结果如下:
Bean 的作用域和生命周期_第1张图片
产生这样的原因是因为 Bean 在 Spring 中,默认情况下是单例状态,也就是所有人的使用都是同一个对像。这样可以很好的节约资源,避免资源的浪费。

作用域就是:Bean 在 Spring 中只有一份,它是全局共享的,那么当其他人修改了这个值之后,另外一个人读取到的就是被修改的值。

Bean 的六种作用域

  1. singleton:单例作用域(默认)在这种模式下的 Bean 在 IoC 容器里面,只存在一个实例。
  2. prototype:原型作用域(多例模式)每次对作用域下的 Bean 请求,都会创建新的实例,然后再去获取 Bean。
  3. request:请求作用域(Spring MVC)每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
  4. session:会话作用域(Spring MVC)在一个 http session 中,定义一个 Bean 实例。记录用户的登录信息。
  5. application:全局作用域(Spring MVC)更多人使用的时候,就用 application。也是单例模式,应用在 Web 应用的上下文信息。
  6. websocket:Http WebSocket 作用域(Spring WebSocket)在 WebSocket 会话中使用。

设置作用域的时候,只需要通过 @Scope 注解就可以了

  1. 直接设置值:@Scope(“prototype”)
  2. 使用枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

默认情况下的单例作用域

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
        User user1 = beanScope1.getUser();
        System.out.println("BeanScope1:" + user1);

        BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
        User user2 = beanScope2.getUser();
        System.out.println("BeanScope2:" + user2);
    }
}

Bean 的作用域和生命周期_第2张图片
可以发现 User 对象全被改了,防止被改的话,就在存 User 对象之前,给它设置 @Scope

Bean 的多例模式

也就是为了防止出现像上面这种情况,在使用的时候已经被别人修改。

直接通过 prototype

@Component
public class Users {
    @Bean(name = "user1")
    @Scope("prototype")
    public User user1() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }
}

代码如下:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
        User user1 = beanScope1.getUser();
        System.out.println("BeanScope1:" + user1);

        BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
        User user2 = beanScope2.getUser();
        System.out.println("BeanScope2:" + user2);
    }
}

运行结果如下:
Bean 的作用域和生命周期_第3张图片
就是通过 prototype 对对象设置,每次一个请求就生成一个对象。

通过 ConfigurableBeanFactory

也就是设置 @Scope 的 ConfigurableBeanFactory.SCOPE_PROTOTYPE 来保证多例模式

@Component
public class Users {
    @Bean(name = "user1")
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public User user1() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }
}

运行结果如下:
Bean 的作用域和生命周期_第4张图片
仍然可以保证多例模式。

Spring 的执行流程

  1. Spring 项目在运行的时候,会先启动容器,也就是我们在 main 方法里面写了这样的代码之后:

    Bean 的作用域和生命周期_第5张图片
    在这里启动容器之后,就要加载配置文件:beans.xml 了。

  2. 然后接下来就是根据配置完成 Bean 的初始化:

    Bean 的作用域和生命周期_第6张图片
    会通过这里的扫描路径,来存入 Bean 。将指定路径种带有五大类注解的普通类存入 Spring 当中,还有类里面带有方法注解的方法,其返回对象也存入 Spring 当中。

  3. 注册 Bean 对象到容器中,只有在包扫描的路径上的类,且使用 Spring 的注解才可以被注册到容器当中:
    Bean 的作用域和生命周期_第7张图片

  4. 装配 Bean 属性,也就是把 Bean 注册到其他类当中:
    Bean 的作用域和生命周期_第8张图片

总的来说:先去启动容器,加载 xml 配置文件。然后,扫描五大类注解,随后,将具有五大类注解的类,存入 Spring 当中。如果 存入的过程中,存在属性的注入,就先执行属性的注入。然后,再继续执行 类 的 实例化。实例化之后,将其存入Spring 中。

Bean 的生命周期

生命周期就是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期。Bean 的生命周期有以下几步:

  1. 实例化 Bean(为 Bean 分配内存空间)
  2. 设置属性(Bean 注入和装配)
  3. Bean 初始化
    a)执行各种通知(各种Aware)如:BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法。
    b)执行 BeanPostProcessor 初始化前置方法。
    c)执行 @PostConstruct 初始化方法,依赖注入操作之后被执行。
    d)执行自己指定的 init-method 方法(如果有指定的话
    e)执行 BeanPostProcessor 初始化后置方法。
  4. 使用 Bean
  5. 销毁 Bean,通过方法来销毁容器:如 @PreDestroy、DisposableBean、destroy-method

执行流程图
Bean 的作用域和生命周期_第9张图片

生命周期代码演示

代码如下:

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class BeanLifeComponent implements BeanNameAware {
    @PostConstruct
    public void postConstruct() {
        System.out.println("执⾏ PostConstruct()");
    }
    public void init() {
        System.out.println("执⾏ BeanLifeComponent init-method");
    }
    @PreDestroy
    public void preDestroy() {
        System.out.println("执⾏:preDestroy()");
    }
    public void setBeanName(String s) {
        System.out.println("执⾏了 setBeanName ⽅法:" + s);
    }
}

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">
<content:component-scan base-package="com.component">
</content:component-scan>
<beans>
<bean id="beanLifeComponent"
class="com.component.BeanLifeComponent" init-method="init"></bean>
</beans>
</beans>

启动类:

import com.controller.BeanLife;
import 
org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        BeanLife life = context.getBean(BeanLife.class);
        System.out.println("执⾏ main ⽅法");
// 执⾏销毁⽅法
        context.destroy();
    }
}

运行结果如下:
Bean 的作用域和生命周期_第10张图片
造成两次通知,是因为 BeanLifeComponent 有注解,原先代码又配置了扫描路径,所以还会加载一次。

现设置属性,再初始化,是为了防止还没有注入的时候,就初始化,然后空指针异常。

你可能感兴趣的:(JavaEE,java,spring,后端,Bean,作用域)