目录
单例作用域(Singleton)VS 全局作用域(application)
前景引入:
假设现在有⼀个公共的 Bean,提供给 A ⽤户和 B ⽤户使⽤,当A把这个公共Bean修改之后,会导致 B ⽤户获取到这个Bean的时候是修改之后的Bean。
案例如下:
公共Bean:
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java"); // 【重点:名称是 Java】
return user;
}
}
A用户使用时,进行了修改操作:
@Controller
public class BeanScopesController {
@Autowired
private User user1;
public User getUser1() {
User user = user1;
System.out.println("Bean 原 Name:" + user.getName());
user.setName("悟空"); // 【重点:进⾏了修改操作】
return user;
}
}
B用户使用公共Bean:
@Controller
public class BeanScopesController2 {
@Autowired
private User user1;
public User getUser1() {
User user = user1;
return user;
}
}
打印A用户和B用户所持有的公共Bean:
public class BeanScopesTest {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanScopesController beanScopesController =
context.getBean(BeanScopesController.class);
System.out.println("A 对象修改之后 Name:" +
beanScopesController.getUser1().toString());
BeanScopesController2 beanScopesController2 =
context.getBean(BeanScopesController2.class);
System.out.println("B 对象读取到的 Name:" +
beanScopesController2.getUser1().toString());
}
}
运行结果:
原因分析:
操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中Bean 的作⽤域默认也是 singleton 单例模式。
作用域定义
限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。
⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值
Bean的六种作用域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,最后四种是基于 Spring MVC ⽣效的:
- singleton:单例作⽤域
- prototype:原型作⽤域(多例作⽤域)
- request:请求作⽤域
- session:回话作⽤域
- application:全局作⽤域
- websocket:HTTP WebSocket 作⽤域
Singleton
- 官⽅说明:(Default) Scopes a single bean definition to a single object instance for eachSpring IoC container.
- 描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀个对象。
- 场景:通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新
- 备注:Spring默认选择该作⽤域
prototype
- 官方说明: Scopes a single bean definition to any number of object instances
- 描述: 每次对该作用域下的 Bean 的请求都会创建新的实例: 获取 Bean (即通过applicationContext.getBean 等方法获取) 及装配 Bean (即通过 @Autowired 注入) 都是新的对象实例。
- 场景: 通常有状态的 Bean 使用该作用域
request
- 官方说明: Scopes a single bean definition to the lifecycle of a single HTTP requestThat is, each HTTP request has its own instance of a bean created off the back of asingle bean definition. Only valid in the context of a web-aware SpringApplicationContext
- 描述: 每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
- 场景:一次 Http 的请求和响应的共享 Bean.
- 备注: 限定 Spring MVC 框架中使用。
session
- 官方说明: Scopes a single bean definition to the lifecycle of an HTTP Session. Onlyvalid in the context of a web-aware Spring ApplicationContext.
- 描述: 在一个 Http Session 中,定义一个 Bean 实例。
- 场景: 用户会话的共享 Bean,比如: 记录一个用户的登陆信息。
- 备注: 限定 Spring MVC 框架中使用。
application
- 官方说明: Scopes a single bean definition to the lifecycle of a ServletContext. Onlvalid in the context of a web-aware Spring ApplicationContext.
- 描述: 在一个 Http Servlet Context 中,定义一个 Bean 实例。
- 场景: Web 应用的上下文信息,比如: 记录一个应用的共享信息。
- 备注: 限定 Spring MVC 框架中使用。
单例作用域(Singleton)VS 全局作用域(application)
- singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域。
- singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。
如何设置 Bean 作用域?
答: 可以通过 @Scope 注解来设置 Bean 的作用域,它的设置方式有以下两种
- 直接设置作用域的具体值,如: @Scope("prototype");
- 设置 ConfigurableBeanFactory 和 WebApplicationContext 提供的 SCOPE XXX 变量,如@Scope(ConfigurableBeanFactory.SCOPE PROTOTYPE).
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
public class Users {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean(name = "u1")
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java"); // 【重点:名称是 Java】
return user;
}
}
Bean的生命周期
所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期。
Bean 的⽣命周期分为以下 5 ⼤部分:
- 实例化 Bean(从无到有,为Bean分配内存空间,调用Bean的构造方法):实例化≠初始化
- 设置属性(注入属性):在创建实例后,Spring将会通过依赖注入(DI)的方式将Bean的属性赋值。
- 初始化 Bean:执行各种 Aware 通知的⽅法,如 BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接⼝⽅法;调用初始化前置方法 -> 会调用所有实现BeanPostProcessor接口的类的postProcessBeforeInitialization方法。执行初始化方法(注解方式:@PostConstruct,XML方法:init-methodf方法),执行初始化后置方法->调用所有实现了BeanPostProcessor接口的类的postProcessAfterInitialization方法。
- 使⽤ Bean,在初始化后,Bean可以被使用,在这个阶段,Bean执行它的业务逻辑。
- 销毁 Bean:销毁Bean的实例。
需要注意的是:第二步和第三步不能进行调换位置,因为可能在初始化时候用到Bean的属性。
如下所示,可能Bean会在初始化时候调用自己的方法,如果没有设置属性,那么就会导致程序出错。
@Service
public class UserService {
public UserService(){
System.out.println("调⽤ User Service 构造⽅法");
}
public void sayHi(){
System.out.println("User Service SayHi.");
}
}
@Controller
public class UserController {
@Resource
private UserService userService;
@PostConstruct
public void postConstruct() {
userService.sayHi();
System.out.println("执⾏ User Controller 构造⽅法");
}
}
下面举个买房子的例子,方便理解Bean的生命周期:
- 先买房(实例化,从⽆到有);
- 装修(设置属性);
- 买家电,如洗⾐机、冰箱、电视、空调等([各种]初始化);
- ⼊住(使⽤ Bean);
- 卖出去(Bean 销毁)。
以下为代码演示Bean的生命周期过程:
spring-config.xml如下:(bean为BeanComponent)
<?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="beanCompont"
class="com.java.demo.component.BeanCompont" init-method="myInit" scope="prototype"></bean>
</beans>
Bean(BeanComponent)如下:
package com.java.demo.component;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanCompont implements BeanNameAware, BeanPostProcessor {
@Override
public void setBeanName(String s) {
System.out.println("执行了通知 BeanName -> " + s);
}
/**
* xml 方式的初始化方法
*/
public void myInit() {
System.out.println("XML 方式初始化");
}
@PostConstruct
public void doPostConstruct() {
System.out.println("注解初始化方法");
}
public void sayHi() {
System.out.println("执行 sayHi()");
}
@PreDestroy
public void doPreDestroy() {
System.out.println("do PreDestroy");
}
// 初始化前置方法
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("do postProcessBeforeInitialization");
return bean;
}
// 初始化后置方法
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("do postProcessAfterInitialization");
return bean;
}
}
调用类:
package com.java.demo;
import com.java.demo.component.BeanCompont;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanCompont compont = context.getBean("beanCompont", BeanCompont.class);
compont.sayHi();
context.close();
}
}
执行结果如下:
说明:这里同时用注解和xml方式进行Bean的初始化,注解方式的优先级是比较高的。