1. Bean 的作用域案例
package Beans.Component;
import Model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
@Bean
public User getUser(){
User user=new User();
user.setUserId(0);
user.setUserName("张三");
return user;
}
}
package Beans.Controller;
import Model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserControllerA {
@Autowired
private User user;
public User getUser(){
System.out.println("Bean原name:" + user.getUserName());
// 修改公共 Bean
user.setUserName("王五");
return user;
}
}
A使用完之后修改了 User 的 name 属性
我们来看看后续用户使用 User 的情况
package Beans.Controller;
import Model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class BeanScopeControllerB {
@Autowired
private User user;
public User getUser() {
return user;
}
}
import Beans.Controller.UserControllerA;
import Beans.Controller.UserControllerB;
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");
UserControllerA A = context.getBean(UserControllerA.class);
System.out.println("修改之前:" + A.getUser());
UserControllerB B = context.getBean(UserControllerB.class);
System.out.println("修改之后:" + B.getUser());
}
}
// 运行结果
Bean原name:张三
修改之前:User{userId=0, userName='王五'}
修改之后:User{userId=0, userName='王五'}
发现 A 的修改作用到了 B 上
原因分析
以上的原因是因为 Bean 的单例模式导致的,单例模式中可以很大程度提升性能,因此 Spring 的 Bean 作用域默认也是 Singleton。所以中间只要被修改,后续的都会受到影响。
作用域定义
限定代码中变量的作用范围「定义某个变量的区域就是作用域」
Bean 的单例模式修改之后会影响后续的代码使用,从作用域的角度来看会发现 Bean 的单例模式作用域是在整个Spring框架下的
2. Bean 的六种作用域
2.1 singleton「单例作用域」
运用:在普通的 Spring 项目中
描述:该作用域下的 Bean 对象在 IOC 容器中只有一份「即ApplicationContext.getBean( )获取或者@Autowired装配都是同一个 Bean」
场景:通常 无状态 的 Bean 使用该作用域。无状态:Bean 对象的属性状态不需要更新
备注:Spring 默认选择该作用域
package Beans.Component;
import Model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
@Scope("singleton")
@Bean(name = {"getUser", "gU"})
public User getUser(){
User user=new User();
user.setUserId(0);
user.setUserName("张三");
return user;
}
}
// 运行结果
Bean原name:张三
修改之前:User{userId=0, userName='王五'}
修改之后:User{userId=0, userName='王五'}
2.2 prototype「原型作用域」
运用:在普通的 Spring 项目中
描述:每次对该作用于下的 Bean 请求都会创建新的实例「多例模式」:即ApplicationContext.getBean( )获取或者@Autowired装配都是一个新的对象实例
场景:通常 有状态 的 Bean 使用该作用域
package Beans.Component;
import Model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
@Scope("prototype")
@Bean(name = {"getUser", "gU"})
public User getUser(){
User user=new User();
user.setUserId(0);
user.setUserName("张三");
return user;
}
}
// 运行结果
Bean原name:张三
修改之前:User{userId=0, userName='王五'}
修改之后:User{userId=0, userName='张三'}
补充一个额外的注解
package Beans.Component;
import Model.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 UserComponent {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean(name = {"getUser", "gU"})
public User getUser(){
User user=new User();
user.setUserId(0);
user.setUserName("张三");
return user;
}
}
2.3 request「请求作用域」
运用:限定在 SpringMvc 项目中
描述:每次 HTTP 请求都会创建新的 Bean 实例,类似于 prototype
场景:一次 HTTP 请求和响应的共享 Bean
2.4 session「会话作用域」
运用:限定在 SpringMvc 项目中
描述:在一个 HTTP Session 中定一个 Bean 实例
场景:用户回话的共享 Bean,比如:记录一用户的登录信息
2.5 application「全局作用域,了解即可」
运用:限定在 SpringMvc 项目中
描述:在一个 HTTP ServleContext 中,定一个 Bean 实例
场景:Web 应用的上下文信息,比如:记录一个应用的共享信息
2.6 websocket「HTTP WebSocket 作用域,了解即可」
运用:限定在 SpringWebSocket 项目中
描述:再以一个 HTTP WebSocket 的生命周期中,保存了一个 Map 结构的头信息,将用来包裹客户端信息头。第一次初始化后,直到 WebSocket 结束都是同一个 Bean
2.7 单例作用域(singleton)和全局作用域(application)区别
- singleton 是 Spring Core 的作用域;application 是 Spring Web 中的作用域
- singleton 作用域 IOC 容器,而 application 作用域 Servlet 作用域
3. Bean 的原理分析
3.1 Bean 执行流程
Bean执行流程「Spring执行流程」:启动 Spring 容器 -> 实例化 Bean「分配内存,从无到有」 -> Bean 注册到 Spring 中「存」 -> 将 Bean 装配到需要的类中「取」
3.2 Bean 生命周期
了解了 Servlet 的生命周期:每个 Servlet 都是从 Tomcat 实例化出来的对象 init() 自动调用「诞生」 -> 根据每个 HTTP 请求来进行对应的响应「应用」-> 最后结束运行的时候会调用 **destory()**自动销毁
那么 Bean 对像的生命周期又是什么呢?可以分为 5 部分
- 实例化 Bean「为 Bena 分配内存」
- 设置属性「Bean的依赖注入和装配」
- Bean 初始化
- 实现了各种 Aware 通知的方法,如 BeanNameAware,BeanFaactoryAware,ApplicationContextAware 的接口方法
- 执行 BeanPostProcessor 初始化前置方法
- 执行 @PostConstruct 初始化方法,依赖注入操作之后被执行
- 执行自己制定的 init-method 方法「如果有指d定方法」
- 执行 BeanPostProcessor 初始化后置方法
- 使用 Bean
- 销毁 Bean
销毁容器的各种方法,如 @PreDestory,DIsposableBean 接口方法,destory-method
package Beans.Component;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class BeanLife implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("执行:BeanNameAware 方法" + s);
}
/**
* 初始化方法1
*/
@PostConstruct
public void postConstruct1(){
System.out.println("执行:PostConstruct");
}
/**
* 初始化方法2
*/
@PostConstruct
public void init2(){
System.out.println("执行:init-method");
}
/**
* 销毁方法1
*/
@PreDestroy
public void preDestroy(){
System.out.println("执行:preDestroy");
}
/**
* 销毁方法2
*/
public void myDestroy(){
System.out.println("执行:destroy-method");
}
}
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="Beans"></content:component-scan>
<!-- Bean的初始化和销毁 -->
<beans>
<bean id="beanLife" class="Beans.Component.BeanLife" init-method="init2" destroy-method="myDestroy"></bean>
</beans>
</beans>
import Beans.Component.BeanLife;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
BeanLife beanLife=context.getBean(BeanLife.class);
System.out.println("使用Bean");
System.out.println("销毁Bean");
context.destroy();
}
}
这里边一个是 xml的产物,一个是 @注解的产物。描述的都是一个事情,只是形式不一样罢了。
- 初始化: PostConstruct 是通过注解的形式执行调用,init-method 是通过配置 spring-config.xml 执行调用的「注意方法名要关联才可以被调用到」
- 销毁: PreDestroy 是通过注解的形式执行调用,destroy-method 是通过配置 spring-config.xml 执行调用的「注意方法名要关联才可以被调用到」
3.3 实例化 VS 初始化
实例化是步骤2「先分配内存,在分配属性」,初始化「给属性赋值」是步骤3
这俩操作能不能颠倒过来呢?
那之前的代码例子来说
@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 ");
}
}
如果先执行了初始化,则代码15行中的设置属性还没有生成,肯定会出现异常情况。因此必须先设置完属性在初始化
Spring 的加载过程是 先外后内:先把外部的东西准备好,在加载内部的变量