1.Bean 作用域
.通过一个案例来看 Bean 作用域的问题
假设现在有一个公共的 Bean,提供给 A 用户和 B 用户使用,然而在使用的途中 A 用户却“悄悄”地修改了公共 Bean 的数据,导致 B 用户在使用时发生了预期之外的逻辑错误。
我们预期的结果是,公共 Bean 可以在各自的类中被修改,但不能影响到其他类。
package com.java.demo.model;/*** @projectName: Demo* @package: com.java.demo.model* @className: User* @author: 王嘉辉* @description:* @date: 2023/11/28 16:18* @version: 1.0*/
public class User {private int id;private String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}
}
package com.java.demo.model;import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;/*** @projectName: Demo* @package: com.java.demo.model* @className: Users* @author: 王嘉辉* @description:* @date: 2023/11/28 16:18* @version: 1.0*//*** 公共类*/
@Component
public class Users {/*** 公共对象* @return*/@Bean("user")public User getUser() {User user = new User();user.setId(1);user.setName("凹凸曼");return user;}
}
package com.java.demo.controller;/*** @projectName: Demo* @package: com.java.demo.controller* @className: UserController* @author: 王嘉辉* @description:* @date: 2023/11/26 21:36* @version: 1.0*/import com.java.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;/*** 用户控制器 - 版本* 作者:李四*/
@Controller
public class UserController {@Autowiredprivate User user;public void doMethod() {System.out.println("UserController user -> " + user);}
}
package com.java.demo.controller;/*** @projectName: Demo* @package: com.java.demo.controller* @className: UserController2* @author: 王嘉辉* @description:* @date: 2023/11/28 16:23* @version: 1.0*/import com.java.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;/*** 用户控制器 - 版本2* 作者:张三*/
@Controller
public class UserController2 {@Autowiredprivate User user;public void doMethod() {User user2 = user;System.out.println("UserController2 修改之前: user -> " + user);user2.setName("三三");System.out.println("UserController2 修改之后: user -> " + user);}
}
import com.java.demo.controller.UserController;
import com.java.demo.controller.UserController2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @projectName: Demo* @package: com.java.demo* @className: App* @author: 王嘉辉* @description:* @date: 2023/11/28 16:31* @version: 1.0*/
public class App {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");UserController2 userController2 = context.getBean("userController2",UserController2.class);userController2.doMethod();UserController userController = context.getBean("userController",UserController.class);userController.doMethod();}
}
我们的Users 默认是单例模式,想要 公共 Bean 可以在各自的类中被修改,但不能影响到其他类
我们可以加一个作用域
1.什么叫作用域
Bean 作用域指的是 Bean 在 Spring 容器中的某种行为(单例,原型…)
2.Bean 的 6 种作用域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作用域。Spring有 6 种作用域,最后四种是基于 Spring MVC 生效的:
- singleton:单例作用域
- prototype:原型作用域(多例作用域)
- request:请求作用域 (只适用于Spring MVC 项目)
- session:回话作用域 (一个 HTTP 会话共享一个Bean,只适用于Spring MVC 项目)
- application:全局作用域(表示的是一个 Context 容器共享一个作用域,只适用于Spring MVC 项目)
- websocket:HTTP WebSocket 作用域
单例模式的 Bean 是线程安全的么?
不是,使用TreadLocal(本地线程变量)
3.设置 Bean 的作用域
1.@Scope(“prototype”)
package com.java.demo.model;import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;/*** @projectName: Demo* @package: com.java.demo.model* @className: Users* @author: 王嘉辉* @description:* @date: 2023/11/28 16:18* @version: 1.0*//*** 公共类*/
@Component
public class Users {/*** 公共对象 -> 默认是单例模式* @return*/@Bean(name = "user")@Scope("prototype") //原型模式 | 多例模式(每一次使用注解请求的时候,都会 new 一个新的之前对象)//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public User getUser() {User user = new User();user.setId(1);user.setName("凹凸曼");return user;}
}
2.@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
package com.java.demo.model;import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;/*** @projectName: Demo* @package: com.java.demo.model* @className: Users* @author: 王嘉辉* @description:* @date: 2023/11/28 16:18* @version: 1.0*//*** 公共类*/
@Component
public class Users {/*** 公共对象 -> 默认是单例模式* @return*/@Bean(name = "user")//@Scope("prototype") //原型模式 | 多例模式(每一次使用注解请求的时候,都会 new 一个新的之前对象)@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public User getUser() {User user = new User();user.setId(1);user.setName("凹凸曼");return user;}
}
2.Spring 执行流程和 Bean 的生命周期
1.执行流程
Bean 执行流程(Spring 执行流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从无到有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)。
2.Bean 的生命周期
所谓的生命周期指的是⼀个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做⼀个对象的生命周期。
-
实例化(内存分配空间)
-
设置 Bean 的属性(进行依赖注入,将依赖的 Bean 赋值到当前类的属性上)
-
Bean 的初始化
- 执行各种通知
- 初始化的前置方法
- 初始化方法
- 初始化的后置方法
-
使用 Bean
-
销毁 Bean
实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可人工干预和修改;而初始化是给开发者提供的,可以在实例化之后,类加载完成之前进行自定义“事件”处理。
import org.springframework.beans.factory.BeanNameAware;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;/*** @projectName: untitled* @package: PACKAGE_NAME* @className: BeanLifeComponent* @author: 王嘉辉* @description:* @date: 2023/11/28 21:12* @version: 1.0*/
public class BeanLifeComponent implements BeanNameAware {@Overridepublic void setBeanName(String s) {System.out.println("执行了 BeanNameAware -> " + s);}@PostConstructpublic void doPostConstruct() {System.out.println("执行了 @PostConstruct");}public void myinit() {System.out.println("执行了 myinit");}@PreDestroypublic void doPreDestroy() {System.out.println("执行了 @PreDestroy");}public void useBean() {System.out.println("使用 Bean");}}
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @projectName: untitled* @package: PACKAGE_NAME* @className: BeanLifeTest* @author: 王嘉辉* @description:* @date: 2023/11/28 21:25* @version: 1.0*/
public class BeanLifeTest {public static void main(String[] args) {ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");BeanLifeComponent component =context.getBean("mybean",BeanLifeComponent.class);component.useBean();context.close();}
}
<?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.java.demo"></content:component-scan><bean id="mybean" class="BeanLifeComponent" init-method="myinit" ></bean>
</beans>
符合上述 Bean初始化 的流程
3.为什么要先设置属性在进行初始化
import com.java.demo.model.User;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;/*** @projectName: untitled* @package: PACKAGE_NAME* @className: BeanLifeComponent* @author: 王嘉辉* @description:* @date: 2023/11/28 21:12* @version: 1.0*/
public class BeanLifeComponent implements BeanNameAware {@Autowiredprivate User user;@Overridepublic void setBeanName(String s) {System.out.println("执行了 BeanNameAware -> " + s);}@PostConstructpublic void doPostConstruct() {System.out.println("执行了 @PostConstruct");System.out.println(user.toString());}public void myinit() {System.out.println("执行了 myinit");}@PreDestroypublic void doPreDestroy() {System.out.println("执行了 @PreDestroy");}public void useBean() {System.out.println("使用 Bean");}}
在 Bean 的生命周期中,先设置属性再进行初始化的原因是因为在实例化 Bean 对象时,Spring 容器会调用 Bean 的构造方法创建 Bean 对象,并将对象的属性注入到 Bean 中。如果在初始化之前就进行依赖注入,那么被注入的属性可能还没有完全初始化,这样可能会导致对象状态不一致的问题。
当初始化 Bean 时,容器会调用 Bean 的初始化方法。这个时候,Bean 对象已经被完全初始化,可以安全地进行一些初始化操作。因此,先设置属性再进行初始化可以确保 Bean 对象的完整性和正确性。