侧边栏壁纸
博主头像
快乐江湖的博客博主等级

更多内容请点击CSDN关注“快乐江湖”

  • 累计撰写 127 篇文章
  • 累计创建 33 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

第四章:Bean作用域和生命周期

快乐江湖
2023-11-20 / 0 评论 / 0 点赞 / 5 阅读 / 22928 字

一:Bean作用域

(1)定义

Bean作用域:在JavaSE中我们说作用域指的是限定程序中变量的可用范围。Bean作用域是指在Spring框架中管理的对象的生命周期和可见范围,或者说它的某种行为模式。因此Bean作用域定义了Bean对象的创建和销毁时机,以及在应用程序中可访问Bean对象的范围

(2)案例说明

如下,有一份公共的Bean名字叫做UserBeans,A用户拿到后同时做了修改,B用户只是拿到

代码如下

// User
package com.demo.model;  
  
import lombok.Getter;  
import lombok.Setter;  
import lombok.ToString;  
  
@Getter  
@Setter  
@ToString  
public class User {  
    private int id;  
    private String name;  
    private String password;  
}

// 公共Bean
package com.demo.component;  
  
import com.demo.model.User;  
import org.springframework.context.annotation.Bean;  
import org.springframework.stereotype.Component;  
  
@Component  
public class UserBeans {  
    @Bean  
    public User user1() {  
        User user = new User();  
        user.setId(1);  
        user.setName("张三");  
        user.setPassword("123");  
        return user;  
    }  
}

// A用户使用并修改
package com.demo.controller;  
  
import com.demo.model.User;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class UserController {  
  
    @Autowired  
    private User user1;  
  
    public void getUser(){  
        System.out.println("A | user1:" + user1);  
        User u = user1;  
        u.setName("李四");  
        System.out.println("A | u:" + u);  
  
    }  
}

// B用户仅使用
package com.demo.controller;  
  
import com.demo.model.User;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class UserController2 {  
  
    @Autowired  
    private User user1;  
  
    public void getUser() {  
        System.out.println("B | user1:" + user1);  
    }  
  
}

// 调用
import com.demo.controller.UserController;  
import com.demo.controller.UserController2;  
import com.demo.model.User;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
  
        UserController userController = context.getBean("userController", UserController.class);  
        userController.getUser();  
  
        UserController2 userController2 = context.getBean("userController2", UserController2.class);  
        userController2.getUser();  
  
  
    }  
}

我们预期的结输出果应该是下面这样

A | user1:User(id=1, name=张三, password=123)
A | u:User(id=1, name=李四, password=123)
B | user1:User(id=1, name=张三, password=123)

但实际输出却是下面这样

A | user1:User(id=1, name=张三, password=123)
A | u:User(id=1, name=李四, password=123)
B | user1:User(id=1, name=李四, password=123)

可以发现,B用户再获取时那份公共的Bean竟然也被修改了。由于是独立开发,实际上B并不知道这是因为A的修改所致


造成这个问题的主要原因是:在Spring中,Bean作用域默认是单例状态(singleton),也就是说所有人使用的都是同一个对象(因为单例模式可以提高性能)。为了不让别人修改,我们可以设置Bean的作用域

(3)六种作用域

Spring六种作用域:Spring在初始化一个Bean的实例时,会指定该实例的作用域,共有六种(最后四种是基于Spring MVC生效的,普通Spring项目中只有前两种)

  • singleton:单例作用域
    • 描述:该作用域下的Bean在IoC容器中仅存在一个实例;获取Bean及注入Bean都是同一个对象
    • 场景:通常无状态的Bean使用该作用域(无状态表示Bean对象属性状态不需要更新)
  • prototype:原型作用域
    • 描述:每次对该作用域下的Bean的请求都会创建新的实例;获取Bean及注入Bean都是新的对象实例
    • 场景:通常有状态的Bean会使用该作用域
  • request:请求作用域
    • 描述:每次Http请求都会创建新的Bean实例
    • 场景:一次Http请求和响应会共享Bean
  • session:会话作用域
    • 描述:在一个Session中,定义一个Bean实例
    • 场景:例如记录一个用户的登录信息
  • application:全局作用域
    • 描述:在一个Http Servlet Context中,定义一个Bean实例
    • 场景:比如记录一个应用的共享信息
  • websocket:HTTP WebSocket作用域
    • 描述:在一个Http WebSocket的生命周期中,定义一个Bean实例
    • 场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀ 次初始化后,直到WebSocket结束都是同⼀个Bean

(4)作用域设置

使用@Scope注解可以声明Bean的作用域。@Scope既可以修饰方法也可以修饰类,有以下两种设置方式

  • 直接设置值:例如@Scope(prototype)
  • 使用枚举设置:例如@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

对于上面的案例,我们将作用域由singleton改为prototype

package com.demo.component;  
  
import com.demo.model.User;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Scope;  
import org.springframework.stereotype.Component;  
  
@Component  
public class UserBeans {  
    @Scope("prototype")  
    @Bean  
    public User user1() {  
        User user = new User();  
        user.setId(1);  
        user.setName("张三");  
        user.setPassword("123");  
        return user;  
    }  
}

结果如下图所示

二:Spring执行流程和生命周期

(1)Spring执行流程

(2)Bean生命周期

Bean生命周期:所谓生命周期是指一个对象从诞生到销毁的整个过程。Bean生命周期分为以下五个部分

  • 实例化Bean:为Bean分配内存空间
  • 设置属性:Bean的注入和装配
  • Bean初始化
    • 实现了各种Aware通知的方法
      • 第一类Aware接口
        • 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值
        • 如果 Bean 实现了 BeanClassLoaderAware 接口,则 Spring 调用 setBeanClassLoader() 方法传入classLoader的引用
        • 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用
      • 第二类Aware接口
        • 如果 Bean 实现了 EnvironmentAware 接口,则 Spring 调用 setEnvironment() 方法传入当前 Environment 实例的引用
        • 如果 Bean 实现了 EmbeddedValueResolverAware 接口,则 Spring 调用 setEmbeddedValueResolver() 方法传入当前 StringValueResolver 实例的引用
        • 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用
    • 执行BeanPostProcessor初始化前置方法
    • 执行初始化方法
      • 注解时代:执行@PostConstruct初始化方法
      • XML时代:执行自己指定的init-method方法
    • 执行BeanPostProcession初始化后置方法
  • 使用Bean
  • 销毁Bean

流程如下

(3)案例

XML方式

// BeanLifeComponet.java

package com.demo.component;  
  
import org.springframework.beans.factory.BeanNameAware;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  


public class BeanLifeComponent implements BeanNameAware {  
    @Override  
    public void setBeanName(String s) {  
        System.out.println("执行了setBeanName" + s);  
    }  
  
    @PostConstruct  
    public void postConstruct() {  
        System.out.println("执行了@PostConstruct");  
    }  
  
    public void init() {  
        System.out.println("执行了init-method方法");  
    }  
  
    @PreDestroy  
    public void preDestory() {  
        System.out.println("执行了preDestory");  
    }  
}

// spring-config.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.demo"></content:component-scan>  
  
     <bean id="myComponent" class="com.demo.component.BeanLifeComponent" init-method="init"></bean>  
</beans>

// App.java
import com.demo.component.BeanLifeComponent;  
import com.demo.controller.UserController;  
import com.demo.controller.UserController2;  
import com.demo.model.User;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        BeanLifeComponent beanLifeComponent= context.getBean("myComponent", BeanLifeComponent.class);  
        System.out.println("使用Bean");  
        // 销毁Bean  
        context.destroy();  
    }  
}

注解方式

// BeanLifeComponent.java
package com.demo.component;  
  
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 {  
    @Override  
    public void setBeanName(String s) {  
        System.out.println("执行了setBeanName" + s);  
    }  
  
    @PostConstruct  
    public void postConstruct() {  
        System.out.println("执行了@PostConstruct");  
    }  
  
    public void init() {  
        System.out.println("执行了init-method方法");  
    }  
  
    @PreDestroy  
    public void preDestory() {  
        System.out.println("执行了preDestory");  
    }  
}

// spring-config.java
<?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.demo"></content:component-scan>  
  
</beans>

// App.java
import com.demo.component.BeanLifeComponent;  
import com.demo.controller.UserController;  
import com.demo.controller.UserController2;  
import com.demo.model.User;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        System.out.println("使用Bean");  
        // 销毁Bean  
        context.destroy();  
    }  
}

0

评论区