目录
前言
一、一个案例引出作用域问题
Lombok的介绍:
二、Bean的六种作用域
2.1、作用域的定义
2.2、Bean的六种作用域
2.2.1、Singleton(单例作用域)
2.2.2、prototype(原型(多例)作用域)
2.2.3、request(请求作用域)
2.2.4、session(会话作用域)
2.2.5、application(全局作用域)
2.2.6、websocket(HTTP WebSocket 作⽤域)
2.3、设置Bean的作用域
三、Spring的执行流程
四、Bean的生命周期
Bean的生命周期可以分为以下五个过程:
代码演示Bean的生命周期:
本篇博客主要介绍Bean的六种作用域和Bean的生命周期,Lombok的安装及使用,以及Spring的执行流程。
现在我们有这么一个业务场景,我们在一个项目中有一个公共的对象,是所有人都可对其操作的。假设现在有张三和李四两个人都在使用这个对象。但是这个对象我们的预想是不修改的,那么假如这时候张三对对象进行修改能不能成功呢?
定义的公共类代码:
具体张三和李四操作如下:获取到注入的对象并使用,其中李四对对象进行了修改操作
执行结果:
通过结果我们也可以看出来,两个类获取到的Bean对象其实是同一个,这说明了我们存入的对象只有一份,属于单例模式,每次拿到对象都是同一个,并不会说是拷贝的一份。而作为一个公共对象,我们则是希望它保持原来的样子,无论谁取出这个对象去使用,都是一份拷贝,都不会对元对象造成影响,这就涉及到了Bean作用域的问题了。
原因分析:
造成上面的情况就是因为Bean的作用域问题,在spring中,默认的作用域是单例作用域,就是singleton模式。具体的如何解决请继续往下看。
在上面的公共类代码中,我们使用到了@Data的注解,这个注解其实就是来自于Lombok的,这个注解的作用就是可以自动生成get和set,还有toString方法等等;
Lombok是什么:
Lombok是一个Java库,它通过注解的方式简化了Java代码的编写。它的目标是减少Java代码中的冗余,提高开发效率。
使用Lombok,开发者可以通过在Java类上添加注解来自动生成一些常见的代码,如getter和setter方法、构造函数、equals和hashCode方法等。这样可以减少手动编写这些重复的代码的工作量,同时也减少了出错的可能性。
除了自动生成代码,Lombok还提供了其他一些有用的注解,如@Data注解可以自动生成所有属性的getter和setter方法、@NoArgsConstructor注解可以生成无参构造函数等。这些注解可以简化代码,使得代码更加清晰、简洁。
要使用Lombok,只需在项目中添加Lombok的依赖,并在需要使用Lombok的类上添加相应的注解即可。在编译过程中,Lombok会根据注解自动生成相应的代码,从而简化了开发过程。
总的来说,Lombok是一个能够通过注解自动生成Java代码的库,它可以减少代码的冗余,提高开发效率。
怎样才能使用Lombok?
第一步:先在pom.xml中引入依赖:
org.projectlombok
lombok
1.18.24
provided
第二步:添加Lombok插件
我们添加Lombok插件的主要目的就是为了我们在写代码的时候可以有相应的提示,当我们使用了Lombok的注解之后,如果在写代码的时候没有相应的提示,大概率就是没有安装插件导致的。
安装如下所示:到Plugin中搜索Lombok就可以
之前我们学习的作用域,就是变量的可用的范围,就是变量的作用域。⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀ 个⼈读取到的就是被修改的值。
Singleton(单例):在整个程序中只存在一个Bean实例,每次获取到的Bean对象都是同一个,我们如果对这个获取到的实例对象进行修改,是会影响到容器中存储的Bean对象的,无论通过 applicationContext.getBean等⽅法获取,还是装配Bean(即通过@Autowired注⼊)获取到的都是同⼀个对象。
prototype(多例作用域):这个prototype刚好和前一个单例作用域恰恰相反,这个是我们每一次获取Bean对象都会得到一个新的实例,我们对新实例的修改不会影响到容器中的Bean对象;
request:代表的是每一次HTTP请求都会创建一个Bean对象,类似于prototype。这个作用域只适用于Spring Web(Spring MVC)。
Session:这个作用域顾名思义就是每一个Session会话都会创建一个新的Bean对象,每个会话共享一个Bean对象。只限于Spring Web中使用。
application:这个作用域顾名思义就是全局共享一个Bean对象,这个也是只适用于Spring Web的,可以理解为跟Singleton一样,只是application是适用于Spring Web的。
websocket:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例,限定Spring WebSocket中使⽤。
关于上述六种作用域的一些说明:
上面六个作用域中前两个是属于普通Spring项目中的作用域,后面是四种是属于Spring Web中的作用域。
关于application和Singleton的区别说明:
不同点:
singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器;
相同点:
application和singleton是属于不同容器中的单例作用域。
对于我们前面案例中出现的问题,本质上就是作用域的问题,要解决前面的问题我们只要对Bean的作用域进行设置即可,代码如下:
设置Bean的作用域可以使用@Scope注解来实现,如下所示:一般我们更推荐使用:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE),这样子写是更能保证不出错的,如果出错了,IDEA也会报错,而第二种写法则不会有报错提示,就算写错了,也不容易发现。
修改后的执行结果:
定义:在这里我们可以将生命周期理解为一个对象从从诞生到销毁的整个过程,这个过程我们就称为对象的生命周期。
接下来我们直接来介绍一下Bean的生命周期,接着在使用代码的形式来演示Bean的生命周期。
1.实例化Bean的过程(为Bean分配内存空间)
2.设置属性(Bean的注入与装配的过程)
3.Bean的初始化
3.1实现了各种 Aware 通知的⽅法,如 BeanNameAware、BeanFactoryAware、 ApplicationContextAware 的接⼝⽅法;
3.2执行了初始化的前置方法(BeanPostProcessor)
3.3执行初始化方法
3.4执⾏⾃⼰指定的 init-method⽅法(需要自己指定)
3.5执行初始化的后置方法(BeanPostProcessor)
4.使用Bean
5.销毁Bean(可以使用:@PreDestroy、DisposableBean 接口方法、destroy-method)
理解生命周期:
生命周期我们可以和我们建房子来作类比,这样子可以更好的理解和记忆Bean的生命周期。
我们建房子一般分为以下几个过程:
1.划一块地,搭建房子首先要先有一块地(这对应实例化Bean,为Bean分配内存空间)
2.搭建房子框架,有了地之后,在这块地上面先把整个房子的外观和框架先搭建起来(对应为Bean设置属性)
3.装修房子,把房子的外壳都搭建起来之后,依旧是还不可以住人的(也就是Bean对象还不能使用),所以接下来需要对房子进行装修(对应初始化Bean对象)
执行各种通知方法及初始化的前置后置方法可以理解为就是在为房子购买冰箱彩电家具的过程。
4.入住房子(对应使用Bean)
5.拆除房子,一般现在的水泥保质期都是七十年,所以房子一般住七十年之后就需要拆除了(对应销毁Bean)。
这个问题其实还是比较好理解的,因为我们在初始化方法中是有可能会使用到某一些类的或者别的一些类中的属性等等,所以这时候要是没有先设置属性,那么在初始化方法中使用这些属性的时候,就会出现错误;而我们在设置属性之前到底有没有初始化是并不重要的,所以设置属性就需要在初始化方法之前。
以下为BeanLife类中的代码:为演示各种通知与初始化方法的代码
package com.java.Model;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanLife implements BeanNameAware, BeanFactoryAware,ApplicationContextAware,BeanPostProcessor {
public void sayHi(){
System.out.println("使用Bean的SayHi方法") ;
}
@PostConstruct
public void postConstruct(){//初始化方法
System.out.println("执行了PostConstruct方法");
}
public void init(){//自定义的初始化方法
System.out.println("执行了init方法");
}
@PreDestroy
public void preDestroy(){//销毁方法
System.out.println("执行了销毁方法");
}
@Override
public void setBeanName(String s) {//通知
System.out.println("执行了BeanNameAware通知"+s);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {//通知
System.out.println("执行BeanFactoryAware通知");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {//通知
System.out.println("执行了ApplicationContextAware 通知");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {//前置通知
System.out.println("执行了通知的前置方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//后置通知
System.out.println("执行了通知的后置方法");
return bean;
}
}
启动类代码:
配置文件代码:
执行结果:
关于代码中的几点注意事项:
执行结果我们只需要关注我用红框中的内容就可以,可以发现,这与我们上面的所写的Bean的生命周期是一致的。
为什么执行结果中通知执行了两遍:因为我们在new 上下文对象(ClassPathXmlApplicationContext)的时候是会去执行一遍通知,因此通知就会变成两遍了。
必须在配置文件中为Bean设置作用域为原型作用域,否则是不会执行前置和后置方法的。不配置扫描路径可能会出现不执行了加了@PostConstruct注解的方法。
Spring执行的流程大致可以分为以下四个步骤:
①启动容器;
②实例化Bean;
③将Bean注册到Spring中;(存操作)
④将Bean装配到需要的类中(取操作)
Spring执行流程图: