Bean 的作用域和生命周期
Bean 的作用域和生命周期
- Bean 的作用域
- 案例
- Bean 的六种作用域
- Bean 的多例模式
- 直接通过 prototype
- 通过 ConfigurableBeanFactory
- Spring 的执行流程
- Bean 的生命周期
- 生命周期代码演示
Bean 的作用域
在使用 Bean 的时候,一个公共的 Bean,交给 A用户 和 B用户 来使用,如果 A用户 偷偷修改了 Bean 的数据,那么 B用户 拿到的数据和预期的就不一样了。
案例
先在 Spring 当中存储一个 User 对象:
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
A用户 拿到对象,并作出修改:
@Component
public class BeanScope1 {
@Autowired
private User user1;
public User getUser() {
User user = user1;
user.setName("李四");
return user;
}
}
把张三修改成了李四,再获取对象进行输出的时候:
@Component
public class BeanScope1 {
@Autowired
private User user1;
public User getUser() {
User user = user1;
user.setName("李四");
return user;
}
}
运行结果如下:
产生这样的原因是因为 Bean 在 Spring 中,默认情况下是单例状态,也就是所有人的使用都是同一个对像。这样可以很好的节约资源,避免资源的浪费。
作用域就是:Bean 在 Spring 中只有一份,它是全局共享的,那么当其他人修改了这个值之后,另外一个人读取到的就是被修改的值。
Bean 的六种作用域
- singleton:单例作用域(默认)在这种模式下的 Bean 在 IoC 容器里面,只存在一个实例。
- prototype:原型作用域(多例模式)每次对作用域下的 Bean 请求,都会创建新的实例,然后再去获取 Bean。
- request:请求作用域(Spring MVC)每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
- session:会话作用域(Spring MVC)在一个 http session 中,定义一个 Bean 实例。记录用户的登录信息。
- application:全局作用域(Spring MVC)更多人使用的时候,就用 application。也是单例模式,应用在 Web 应用的上下文信息。
- websocket:Http WebSocket 作用域(Spring WebSocket)在 WebSocket 会话中使用。
设置作用域的时候,只需要通过 @Scope 注解就可以了:
- 直接设置值:@Scope(“prototype”)
- 使用枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
默认情况下的单例作用域:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
User user1 = beanScope1.getUser();
System.out.println("BeanScope1:" + user1);
BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
User user2 = beanScope2.getUser();
System.out.println("BeanScope2:" + user2);
}
}
可以发现 User 对象全被改了,防止被改的话,就在存 User 对象之前,给它设置 @Scope
Bean 的多例模式
也就是为了防止出现像上面这种情况,在使用的时候已经被别人修改。
直接通过 prototype
@Component
public class Users {
@Bean(name = "user1")
@Scope("prototype")
public User user1() {
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
代码如下:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
User user1 = beanScope1.getUser();
System.out.println("BeanScope1:" + user1);
BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
User user2 = beanScope2.getUser();
System.out.println("BeanScope2:" + user2);
}
}
运行结果如下:
就是通过 prototype 对对象设置,每次一个请求就生成一个对象。
通过 ConfigurableBeanFactory
也就是设置 @Scope 的 ConfigurableBeanFactory.SCOPE_PROTOTYPE 来保证多例模式
@Component
public class Users {
@Bean(name = "user1")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public User user1() {
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
运行结果如下:
仍然可以保证多例模式。
Spring 的执行流程
-
Spring 项目在运行的时候,会先启动容器,也就是我们在 main 方法里面写了这样的代码之后:
在这里启动容器之后,就要加载配置文件:beans.xml
了。 -
然后接下来就是根据配置完成 Bean 的初始化:
会通过这里的扫描路径,来存入 Bean 。将指定路径种带有五大类注解的普通类存入 Spring 当中,还有类里面带有方法注解的方法,其返回对象也存入 Spring 当中。 -
注册 Bean 对象到容器中,只有在包扫描的路径上的类,且使用 Spring 的注解才可以被注册到容器当中:
-
装配 Bean 属性,也就是把 Bean 注册到其他类当中:
总的来说:先去启动容器,加载 xml 配置文件。然后,扫描五大类注解,随后,将具有五大类注解的类,存入 Spring 当中。如果 存入的过程中,存在属性的注入,就先执行属性的注入。然后,再继续执行 类 的 实例化。实例化之后,将其存入Spring 中。
Bean 的生命周期
生命周期就是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期。Bean 的生命周期有以下几步:
- 实例化 Bean(为 Bean 分配内存空间)
- 设置属性(Bean 注入和装配)
- Bean 初始化
a)执行各种通知(各种Aware)如:BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法。
b)执行 BeanPostProcessor 初始化前置方法。
c)执行 @PostConstruct 初始化方法,依赖注入操作之后被执行。
d)执行自己指定的 init-method 方法(如果有指定的话
e)执行 BeanPostProcessor 初始化后置方法。 - 使用 Bean
- 销毁 Bean,通过方法来销毁容器:如 @PreDestroy、DisposableBean、destroy-method
执行流程图:
生命周期代码演示
代码如下:
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 {
@PostConstruct
public void postConstruct() {
System.out.println("执⾏ PostConstruct()");
}
public void init() {
System.out.println("执⾏ BeanLifeComponent init-method");
}
@PreDestroy
public void preDestroy() {
System.out.println("执⾏:preDestroy()");
}
public void setBeanName(String s) {
System.out.println("执⾏了 setBeanName ⽅法:" + s);
}
}
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.component">
</content:component-scan>
<beans>
<bean id="beanLifeComponent"
class="com.component.BeanLifeComponent" init-method="init"></bean>
</beans>
</beans>
启动类:
import com.controller.BeanLife;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanLife life = context.getBean(BeanLife.class);
System.out.println("执⾏ main ⽅法");
// 执⾏销毁⽅法
context.destroy();
}
}
运行结果如下:
造成两次通知,是因为 BeanLifeComponent 有注解,原先代码又配置了扫描路径,所以还会加载一次。
现设置属性,再初始化,是为了防止还没有注入的时候,就初始化,然后空指针异常。