一:Bean作用域
(1)定义
Bean作用域:在JavaSE中我们说作用域指的是限定程序中变量的可用范围。Bean作用域是指在Spring框架中管理的对象的生命周期和可见范围,或者说它的某种行为模式。因此Bean作用域定义了Bean对象的创建和销毁时机,以及在应用程序中可访问Bean对象的范围
(2)案例说明
如下,有一份公共的Bean名字叫做UserBeans
,A用户拿到后同时做了修改,B用户只是拿到
代码如下
// User
package com.demo.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class User {
private int id;
private String name;
private String password;
}
// 公共Bean
package com.demo.component;
import com.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserBeans {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("张三");
user.setPassword("123");
return user;
}
}
// A用户使用并修改
package com.demo.controller;
import com.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private User user1;
public void getUser(){
System.out.println("A | user1:" + user1);
User u = user1;
u.setName("李四");
System.out.println("A | u:" + u);
}
}
// B用户仅使用
package com.demo.controller;
import com.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController2 {
@Autowired
private User user1;
public void getUser() {
System.out.println("B | user1:" + user1);
}
}
// 调用
import com.demo.controller.UserController;
import com.demo.controller.UserController2;
import com.demo.model.User;
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.getUser();
UserController2 userController2 = context.getBean("userController2", UserController2.class);
userController2.getUser();
}
}
我们预期的结输出果应该是下面这样
A | user1:User(id=1, name=张三, password=123)
A | u:User(id=1, name=李四, password=123)
B | user1:User(id=1, name=张三, password=123)
但实际输出却是下面这样
A | user1:User(id=1, name=张三, password=123)
A | u:User(id=1, name=李四, password=123)
B | user1:User(id=1, name=李四, password=123)
可以发现,B用户再获取时那份公共的Bean竟然也被修改了。由于是独立开发,实际上B并不知道这是因为A的修改所致
造成这个问题的主要原因是:在Spring中,Bean作用域默认是单例状态(singleton),也就是说所有人使用的都是同一个对象(因为单例模式可以提高性能)。为了不让别人修改,我们可以设置Bean的作用域
(3)六种作用域
Spring六种作用域:Spring在初始化一个Bean的实例时,会指定该实例的作用域,共有六种(最后四种是基于Spring MVC生效的,普通Spring项目中只有前两种)
- singleton:单例作用域
- 描述:该作用域下的Bean在IoC容器中仅存在一个实例;获取Bean及注入Bean都是同一个对象
- 场景:通常无状态的Bean使用该作用域(无状态表示Bean对象属性状态不需要更新)
- prototype:原型作用域
- 描述:每次对该作用域下的Bean的请求都会创建新的实例;获取Bean及注入Bean都是新的对象实例
- 场景:通常有状态的Bean会使用该作用域
- request:请求作用域
- 描述:每次Http请求都会创建新的Bean实例
- 场景:一次Http请求和响应会共享Bean
- session:会话作用域
- 描述:在一个Session中,定义一个Bean实例
- 场景:例如记录一个用户的登录信息
- application:全局作用域
- 描述:在一个Http Servlet Context中,定义一个Bean实例
- 场景:比如记录一个应用的共享信息
- websocket:HTTP WebSocket作用域
- 描述:在一个Http WebSocket的生命周期中,定义一个Bean实例
- 场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀ 次初始化后,直到WebSocket结束都是同⼀个Bean
(4)作用域设置
使用@Scope
注解可以声明Bean的作用域。@Scope
既可以修饰方法也可以修饰类,有以下两种设置方式
- 直接设置值:例如
@Scope(prototype)
- 使用枚举设置:例如
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
对于上面的案例,我们将作用域由singleton改为prototype
package com.demo.component;
import com.demo.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
public class UserBeans {
@Scope("prototype")
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("张三");
user.setPassword("123");
return user;
}
}
结果如下图所示
二:Spring执行流程和生命周期
(1)Spring执行流程
(2)Bean生命周期
Bean生命周期:所谓生命周期是指一个对象从诞生到销毁的整个过程。Bean生命周期分为以下五个部分
- 实例化Bean:为Bean分配内存空间
- 设置属性:Bean的注入和装配
- Bean初始化:
- 实现了各种Aware通知的方法
- 第一类Aware接口
- 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值
- 如果 Bean 实现了 BeanClassLoaderAware 接口,则 Spring 调用 setBeanClassLoader() 方法传入classLoader的引用
- 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用
- 第二类Aware接口
- 如果 Bean 实现了 EnvironmentAware 接口,则 Spring 调用 setEnvironment() 方法传入当前 Environment 实例的引用
- 如果 Bean 实现了 EmbeddedValueResolverAware 接口,则 Spring 调用 setEmbeddedValueResolver() 方法传入当前 StringValueResolver 实例的引用
- 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用
- …
- 第一类Aware接口
- 执行BeanPostProcessor初始化前置方法
- 执行初始化方法
- 注解时代:执行
@PostConstruct
初始化方法 - XML时代:执行自己指定的
init-method
方法
- 注解时代:执行
- 执行BeanPostProcession初始化后置方法
- 实现了各种Aware通知的方法
- 使用Bean:
- 销毁Bean:
流程如下
(3)案例
XML方式
// BeanLifeComponet.java
package com.demo.component;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanLifeComponent implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("执行了setBeanName" + s);
}
@PostConstruct
public void postConstruct() {
System.out.println("执行了@PostConstruct");
}
public void init() {
System.out.println("执行了init-method方法");
}
@PreDestroy
public void preDestory() {
System.out.println("执行了preDestory");
}
}
// spring-config.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.demo"></content:component-scan>
<bean id="myComponent" class="com.demo.component.BeanLifeComponent" init-method="init"></bean>
</beans>
// App.java
import com.demo.component.BeanLifeComponent;
import com.demo.controller.UserController;
import com.demo.controller.UserController2;
import com.demo.model.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent beanLifeComponent= context.getBean("myComponent", BeanLifeComponent.class);
System.out.println("使用Bean");
// 销毁Bean
context.destroy();
}
}
注解方式
// BeanLifeComponent.java
package com.demo.component;
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 {
@Override
public void setBeanName(String s) {
System.out.println("执行了setBeanName" + s);
}
@PostConstruct
public void postConstruct() {
System.out.println("执行了@PostConstruct");
}
public void init() {
System.out.println("执行了init-method方法");
}
@PreDestroy
public void preDestory() {
System.out.println("执行了preDestory");
}
}
// spring-config.java
<?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.demo"></content:component-scan>
</beans>
// App.java
import com.demo.component.BeanLifeComponent;
import com.demo.controller.UserController;
import com.demo.controller.UserController2;
import com.demo.model.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
System.out.println("使用Bean");
// 销毁Bean
context.destroy();
}
}
评论区