目录
Bean 的作用域
作用域的定义
Singleton(单例作用域)
Prototype(原型作用域)
Request(请求作用域)
Session(会话请求)
Application(全局作用域)
WebSocket(WebSocket 会话作用域 )
设置 Bean 的作用域
@Scope 注解
使用方式建议
Bean 的作用域
作用域的定义
- 作用域(Scope)指变量、函数或对象在程序中可见和访问的范围
- Bean 的作用域 指 Bean 在 Spring 整个框架中的某种 行为模式
Bean 的 6 种作用域
- Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域
Singleton(单例作用域)
- 该作用域下的 Bean 在 Spring 容器中只存在一个实例:获取 Bean(即通过 context.getBean 等方法获取)及装配 Bean(通过 @Autowired 注入)都是同一个对象
- Spring 容器在第一次请求时创建该实例,并在后续请求中返回相同的实例
- 默认情况下,Spring 的 Bean 作用域为 Singleton
- 因为 单例模式 的性能更好
Prototype(原型作用域)
可理解为 多例作用域
每次请求获取 Bean 时,都会创建一个新的实例
每次获取 Bean 时都会返回一个新的对象,即原型作用域的 Bean 实例不会被共享
Request(请求作用域)
- 在 Web 应用程序中,每个 HTTP 请求将创建一个新的 Bean 实例,并且该实例仅在当前请求的范围内可见
- 对于每个请求,都会有一个单独的 Bean 实例
Session(会话请求)
- 在 Web 应用程序中,每个用户会话均会创建一个新的 Bean 实例,并且该实例仅在当前用户会话的范围内可见
- 对于每个用户会话,都会有一个单独的 Bean 实例
Application(全局作用域)
- 在 Web 应用程序中,整个应用程序范围内只会创建一个Bean 实例,并且该实例将被共享和重用
WebSocket(WebSocket 会话作用域 )
- 在基于 WebSocket 的应用程序中,每个 WebSocket 会话均会创建一个新的 Bean 实例,并且该实例仅在当前 WebSocket 会话的范围内可见
注意:
- 在普通的 Spring 项目中只有前两种作用域,即前两种为 Spring 核心作用域
- 后四种状态是 Spring MVC 中的作用域
建议阅读下文之前 点击下方链接了解 Lombok 的作用
Lombok 的作用和使用
实例理解
- 我们先创建一个实体类 User
import lombok.Data; @Data public class User { public int id; public String name; }
- 再创建一个 UserBean 类,用来向 Spring 容器中 存储 User 类型的 Bean 对象
- 此处向 Spring 容器中存入一个 id 为 user,且 name = 张三的 Bean 对象
import com.java.demo.enity.User; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class UserBeans { @Bean public User user() { User user = new User(); user.setId(1); user.setName("张三"); return user; } }
- 新创建 UserController 类
- 该类中新创建了一个 myUser 对象,且该对象赋值于注入进来的 user Bean 对象
- 并修改 myUser 对象的 name 属性
- 将从 user Bean 对象赋值而来的 name = 张三,修改为 name = 小林
import com.java.demo.enity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class UserController { @Autowired private User user; public void printUser() { System.out.println("UserController 类注入的 user Bean 对象,也就是 user 变量的初始值 -> " + user.toString()); // 修改 User User myUser = user; myUser.setName("小林"); System.out.println("UserController 类中的 myUser 对象 -> " + myUser.toString()); System.out.println("UserController 类修改 myUser 对象 name 属性后再次打印 user 变量 -> " + user.toString()); } }
- 新创建 UserController2 类
- 同样在该类中注入 user Bean 对象
import com.java.demo.enity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class UserController2 { @Autowired private User user; public void printUser2() { System.out.println("UserController2 类注入的 user Bean 对象 -> " + user.toString()); } }
- 最后再创建一个启动类
- 该类用来从 Spring 容器中拿 UserController 类和 UserController2 类的 Bean 对象,并调用它们的成员方法
import com.java.demo.controller.UserController; import com.java.demo.controller.UserController2; 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.printUser(); UserController2 userController2 = context.getBean("userController2",UserController2.class); userController2.printUser2(); } }
运行结果:
- 该运行结果与我们的预期结果不同诶!
- 为啥 UserController2 类中从 Spring 容器中拿到的 user Bean 对象其 name = 小林 ?
原因解释:
- 正因为默认情况下,Spring 的 Bean 作用域为 Singleton,即 单例作用域
- 即存储在 Spring 容器中的 id = user 的 Bean 对象仅有一份
- 所以此处的 user 变量和 myUser 变量 均指向同一个对象(引用)
- 即 myUser 变量在对其 name 进行修改时
- 其他任何注入了 id = user 的 Bean 对象 的变量,也会跟着修改 name
设置 Bean 的作用域
@Scope 注解
- 我们可以通过 @Scope 注解来设置 Bean 对象的作用域
实例理解
- 如果我们不主动设置该 Bean 对象的作用域,其默认为 Singleton(单例作用域)
- 所以为了解决上述实例问题所产生的问题,我们可以将该 Bean 对象主动设置为 prototype(原型作用域),即多例模式
方式一:直接在 @Scope 注解中填入 prototype
import com.java.demo.enity.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component public class UserBeans { @Bean @Scope("prototype") public User user() { User user = new User(); user.setId(1); user.setName("张三"); return user; } }
方式二:使用 ConfigurableBeanFactory 接口中定义的常量 SCOPE_PROTOTYPE,该常量表示原型作用域
import com.java.demo.enity.User; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component public class UserBeans { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public User user() { User user = new User(); user.setId(1); user.setName("张三"); return user; } }
- 当我们修改完 Bean 对象的作用域时,再次运行启动类,观察运行结果
运行结果:
- UserController2 类中从 Spring 容器中拿到的 user Bean 对象其 name = 张三
- 也就是说,在 UserController 类中,进行的修改操作并未影响到 Spring 容器中的初始 Bean 对象
使用方式建议
- 上述两种方式起到的效果是相同的,但是推荐使用第二种方式
- 使用第一种 直接填入 prototype 方式的前提是 你能够清楚的记得该单词是如何拼写的
- 而第二种方式,IDEA 自带自动补全提示,可以保证我们不出错!