前言:
💞💞从前⾯的课程我们可以看出 Spring 是⽤来读取和存储 Bean,因此在 Spring 中 Bean 是最核⼼的操作资源,所以接下来我们深⼊学习⼀下 Bean 对象。
前路漫漫,希望大家坚持下去,不忘初心,成为一名优秀的程序员
个人主页⭐: 书生♡
gitee主页🙋♂:奋斗的小白
专栏主页💞:JavaEE进阶专栏
博客领域💥:java编程前端,算法,强训题目
写作风格💞:超前知识点,干货,思路讲解,通俗易懂
支持博主💖:关注⭐,点赞、收藏⭐、留言💬
1.常见的作用域问题
1.1 问题所在
有一个公共类,类对象被@Controller注解修饰,里面的方法被方法注解@Bean修饰。现在有两个用户都使用这个公共类,但是其中一个用户突然改变了里面的值,这个就导致了另一个用户使用的时候变量值已经发生了变化,因此第二个用户得到的应该是修改以后的。
1.2 代码示例
公共类
@Controller
public class StudentBeans {
@Bean
public Student student1() {
// 伪代码,构建对象
Student stu = new Student();
stu.setId(1);
stu.setName("张三");
stu.setAge(18);
return stu;
}
}
一个类
@Controller
public class UserController {
@Resource(name = "user1")
@Autowired
//@Qualifier(value = "user2")
private User user;
public void getUser()
{
System.out.println("user:"+user);
User user2=user;
user2.setName("lisi");
System.out.println("user:"+user2);
}
}
另一个类
@Controller
public class User1Controller {
@Autowired
private User user;
public void getUser()
{
System.out.println("user:"+user);
}
}
咋们看一下结果,会发现我们另一个类的消息也发生了变化,但是我们并没有改变第二各类的信息。
我们可以发现,我们只有在第一个类中,创建了一个局部的变量,将这个局部变量进行改变,但是为什么我们的公用的会发生变化呢?
这里面就包含了我们本篇文章要讲述的作用域问题。
操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同
⼀个对象,这个就导致了,我们一个类改变了,那么就是公共类改变了。
这个时候就会由人问了?我在第一个类里面不是创建一个局部的User类型的变量吗?我改变的也是局部的,为啥会改变公共的呢?其实这个问题很简单,就是因为创建的 局部的User类型变量也是指向这个公共类的,所以哪怕直接改变的是非公共类,公共类也会改变的。这个大家不需要纠结。
之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中 Bean 的作⽤域默认也是 singleton单例模式。
下面我们正式进入我们今天要讲的内容。
2. 作用域
2.1 作用域定义
作用域:就是一个限制变量在程序的使用范围
或者说在源代码中定义变量的某个区域就叫做作⽤域
那Bean的作用域是什么呢?
Bean的作用域就是Bean在Spring整个框架中的某种行为模式。⽐如 singleton 单例作⽤域,就
表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀
个⼈读取到的就是被修改的值。
2.2Bean 的 6 种作⽤域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。
Spring有 6 种作⽤域,最后四种是基于 Spring MVC ⽣效的,也就是说在普通的Spring项目中是见不到的,在普通的 Spring 项⽬中只有前两种。
- singleton:单例作⽤域(默认作用域)
- prototype:原型作⽤域(多例作⽤域)
- request:请求作⽤域
- session:回话作⽤域
- application:全局作⽤域
- websocket:HTTP WebSocket 作⽤域
2.2.1 singleton 单例作⽤域
描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(applicationContext.getBean获取)及装配Bean(@Autowired注⼊)都是同⼀个对象。
场景:通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新
备注:Spring默认选择该作⽤域
2.2.2 prototype 原型作⽤域
描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean( applicationContext.getBean获取)及装配Bean(@Autowired注⼊)都是新的对象 实例。
场景:通常有状态的Bean使⽤该作⽤域
2.2.3request 请求作⽤域
描述:每次前端发送http请求会创建新的Bean实例,类似于prototype
场景:⼀次http的请求和响应的共享Bean,等到第二次请求的时候,就会重新新建一个作用域
备注:限定SpringMVC中使⽤
2.2.4 session 会话作⽤域
描述:在⼀个http session中,定义⼀个Bean实例
场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
备注:限定SpringMVC中使⽤,session会话作用域的范围要比请求作用域更大
2.2.5 application 全局作⽤域(了解)
描述:在⼀个http servlet Context中,定义⼀个Bean实例
场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
备注:限定SpringMVC中使⽤
2.2.6 websocket :HTTP WebSocket 作⽤域
描述:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例
场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。
备注:限定Spring WebSocket中使⽤
2.3 singleton 和 application 的区别
singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。
2.4 设置作用域
我们的程序默认是单例作用域,这个就会导致,我们用的始终是一个Bean对象。会出现不是我们想要的结果,所以我们就要设置作用域。
使⽤ @Scope 标签就可以⽤来声明 Bean 的作⽤域
@Controller
public class UserBean {
@Scope("prototype")//设置作用域
@Bean
public User user1()
{
User user=new User();
user.setId(1);
user.setName("张三");
user.setPassword("123");
return user;
}
}
我们设置作用域有两种方式
一种是只直接设置作用域
@Scope(“prototype”)//设置作用域
另一种是使⽤成员变量设置
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//设置作用域
两种都是可以设置作用域的
3. Spring执行流程
Spring流程分为四步:
第一步:启动容器(加载配置文件)
第二步:完成Bean初始化
这一步有两种方式:
1.使用xml直接注册bean
2.配置bean根(扫描)路径
第三步:注册bean对象到容器中(.将 bean存储到spring 中,通过类注解进行扫描和装配)
第四步:装配Bean属性 (将bean从spring读取出来,装配到相应的类)
Bean 执⾏流程(Spring 执⾏流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从⽆到
有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)。
4.Bean ⽣命周期
4.1Bean ⽣命周期
所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期。
Bean 的⽣命周期分为以下 5 ⼤部分:
1.实例化 Bean(为 Bean 分配内存空间,对应JVM中的加载)
2.设置属性(Bean 注⼊和装配)
3.Bean 初始化
实现了各种 通知的⽅法,
执⾏ 初始化前置⽅法;
执⾏ 初始化⽅法,依赖注⼊操作之后被执⾏;执⾏⾃⼰指定的 init-method方法
执⾏初始化后置⽅法。
4.使⽤ Bean
5.销毁 Bean
整个生命周期就好像我买了一个房子:
1.实例化就像是:买了一个毛坯房,
2.设置属性就等同于:购买装修材料(引入外部资源),
3.初始化就等同于:给房子装修,
3.1里面的各种通知方法:相当于通知装修的工人
3.2初始化前置工作:就相当于制定装修方法
3.3进行初始化:就相当于工人师傅开始装修了
3.4初始化的后置工作:相当于装修后的清理工作
4.使用Bean:就相当于入住房子
5.销毁房子:等同于卖掉房子
销毁容器的各种⽅法,如 @PreDestroy、DisposableBean 接⼝⽅法、destroy-method
4.2 实例化和初始化的区别
实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可⼈⼯⼲预和修改;⽽初始化是给开发者提供的,可以在实例化之后,类加载完成之前进⾏⾃定义“事件”处理。
4.3 Bean生命周期代码
package Spring;
import Spring.demo.component.BeanLifeComponent;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent life = context.getBean("beanLifeComponent",BeanLifeComponent.class);
System.out.println(" 使用Bean⽅法");
// 执⾏销毁⽅法
context.destroy();
}
}
package Spring.demo.component;
import org.springframework.beans.factory.BeanNameAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanLifeComponent implements BeanNameAware {
@PostConstruct
public void postConstruct() {
System.out.println("执⾏ PostConstruct()");
}
public void init() {
System.out.println("执⾏ BeanLifeComponent init-method");
}
public void setBeanName(String s) {
System.out.println("执⾏了通知 ");
}
@PreDestroy
public void preDestroy() {
System.out.println("执⾏:preDestroy()");
}
}
最重要的是配置spring-config.xml文件