.通过一个案例来看 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;
}
@Override
public 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 {
@Autowired
private 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 {
@Autowired
private 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 可以在各自的类中被修改,但不能影响到其他类
我们可以加一个作用域
Bean 作用域指的是 Bean 在 Spring 容器中的某种行为(单例,原型…)
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作用域。Spring有 6 种作用域,最后四种是基于 Spring MVC 生效的:
单例模式的 Bean 是线程安全的么?
不是,使用TreadLocal(本地线程变量)
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;
}
}
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;
}
}
Bean 执行流程(Spring 执行流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从无到有) -> Bean 注册到 Spring 中(存操作) -> 将 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 {
@Override
public void setBeanName(String s) {
System.out.println("执行了 BeanNameAware -> " + s);
}
@PostConstruct
public void doPostConstruct() {
System.out.println("执行了 @PostConstruct");
}
public void myinit() {
System.out.println("执行了 myinit");
}
@PreDestroy
public 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();
}
}
<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>
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 {
@Autowired
private User user;
@Override
public void setBeanName(String s) {
System.out.println("执行了 BeanNameAware -> " + s);
}
@PostConstruct
public void doPostConstruct() {
System.out.println("执行了 @PostConstruct");
System.out.println(user.toString());
}
public void myinit() {
System.out.println("执行了 myinit");
}
@PreDestroy
public void doPreDestroy() {
System.out.println("执行了 @PreDestroy");
}
public void useBean() {
System.out.println("使用 Bean");
}
}
在 Bean 的生命周期中,先设置属性再进行初始化的原因是因为在实例化 Bean 对象时,Spring 容器会调用 Bean 的构造方法创建 Bean 对象,并将对象的属性注入到 Bean 中。如果在初始化之前就进行依赖注入,那么被注入的属性可能还没有完全初始化,这样可能会导致对象状态不一致的问题。
当初始化 Bean 时,容器会调用 Bean 的初始化方法。这个时候,Bean 对象已经被完全初始化,可以安全地进行一些初始化操作。因此,先设置属性再进行初始化可以确保 Bean 对象的完整性和正确性。