相信很多朋友在学习bean的创建和注入时都会有疑问?我们使用不同方式从容器中拿到的bean是同一个bean还是不同的bean,接下来我将为大家解答这个疑问~
概念
用来规定spring容器创建bean实例的数量,生命周期与有效范围,也就是bean什么时候创建,什么时候销毁,和哪个地方可以拿到这个bean。
在讲解前我们先简单回顾一下bean的注入和获取:
注入:通过五大类注解或方法注解(@bean)可以将方法的返回值存放在容器中
五大注解:@component、@controller、@service、@repository、@configuration
@bean的使用方式:setter方法,构造方法…
获取:①applicationcontext.getbean()②@autowired③@resource...
准备工作:
为了更好的展示bean的作用域,我们需要借助代码来加深理解,需要先创建一个对象,我们就以创建一个dog的类为例:
@data
public class dog {
private string name;
}单例singleton
spring在启动前只会创建一次实例,并存放在容器中,无论是getbean()还是通过注解从容器拿到bean对象,都只会是同一个对象,使用结束后,spring会自动销毁。一般注入bean时默认为single从代码中来理解:
定义一个configuration类注入dog类
@data
public class dog {
private string name;
public string getrealaddress() {
// 这里的 this 是当前请求真实绑定的那个 dog 对象
return integer.tohexstring(system.identityhashcode(this));
}
}在启动类中注入对象
@springbootapplication
public class springdemobeanapplication {
public static void main(string[] args) {
applicationcontext context = springapplication.run(springdemobeanapplication.class, args);
dog dog1 = context.getbean(dog.class);
dog1.setname("旺财");
system.out.println(dog1.getname());
dog dog2 = (dog)context.getbean("getdog");
system.out.println(dog2.getname());
}
}分别使用类名.class和bean name的方式来注入bean,观察结果:

可以发现,我们明明没有设置dog2的name值,但结果却显示dog1和dog2的name一样,由此可证,默认情况下的bean是单例模式。
也可通过@scope(configurablebeanfactory.scope_singleton)定义bean的作用域为单例

原型(多例)prototype
spring每次启动都会重新创建一个新的bean对象,并将新的对象存放在容器中,每次使用getbean()从容器中拿到的对象都会不一样。spring 不会管理 prototype bean 的销毁,需要自己手动释放。
@scope(configurablebeanfactory.scope_prototype)
@configuration
public class config {
@bean
@scope(configurablebeanfactory.scope_prototype)//创建一个多例bean
public dog prototypedog(){
dog dog=new dog();
return dog;
}
}@restcontroller
public class controller {
@autowired
private applicationcontext context;
@resource(name = "prototypedog")//注入requestdog
private dog prototypedog;
@requestmapping("/prototypedog")
public string prototypedog(){
// 方法 a:通过注入的代理对象调用方法(最推荐,这也是 spring 期望的用法)
string realaddr1 = requestdog.getrealaddress();
// 方法 b:通过 context 获取并调用方法
dog contextdog = (dog) context.getbean("requestdog");
string realaddr2 = contextdog.getrealaddress();
return "通过注入代理调用的真实地址:【" + realaddr1 + "】
" +
"通过context调用的真实地址:【" + realaddr2 + "】";
}
}查看结果:

多次请求可以看出,每次请求context得到的bean都不一样,符合多例模式的定义,为什么由config注入的bean每次得到的都是一个对象呢?
原因:如果是从单例中注入一个原型,只会在单例注入时从spring容器中获取一个bean对象,后续不会重新获取对象,而conreller类刚好是一个单例bean,这就是为什么上面例子中prototypedog在每次请求中都保持不变的原因。
请求request
在每次请求中,都会创建一个新的bean对象。在同一个请求中,指向的bean相同。但是注意,并不是请求的时候就会注入,而是等到需要到这个bean的时候才会注入。
只有 web 环境生效!非 web 环境退化成单例!
使用@requestscope可以设置为request作用域
代码:
@configuration
public class config {
@bean
@requestscope
public dog requestdog(){
dog dog=new dog();
return dog;
}
}@restcontroller
public class controller {
@autowired
private applicationcontext context;
@resource(name = "requestdog")//注入requestdog
private dog requestdog;
@requestmapping("/requestdog")
public string requestdog(){
// 方法 a:通过注入的代理对象调用方法(最推荐,这也是 spring 期望的用法)
string realaddr1 = requestdog.getrealaddress();
// 方法 b:通过 context 获取并调用方法
dog contextdog = (dog) context.getbean("requestdog");
string realaddr2 = contextdog.getrealaddress();
return "通过注入代理调用的真实地址:【" + realaddr1 + "】
" +
"通过context调用的真实地址:【" + realaddr2 + "】";
}
}观察不同请求的结果:

由此可以看出,不同的请求会创建不同的bean放入容器中,我们从spring容器中拿到的对象也不同。
会话session
在每次会话中都会创建一个新的对象,在同一个会话中指向的bean相同
只有 web 环境生效!非 web 环境退化成单例!
代码:
@configuration
public class config {
@bean
@sessionscope
public dog sessiondog(){
dog dog=new dog();
return dog;
}
}@restcontroller
public class controller {
@autowired
private applicationcontext context;
@resource(name = "sessiondog")
private dog sessiondog;
@requestmapping("/sessiondog")
public string sessiondog(){
// dog dog1=context.getbean(dog.class);
dog contextdog=(dog) context.getbean("sessiondog");
string addr1 = integer.tohexstring(system.identityhashcode(sessiondog));
string addr2 = integer.tohexstring(system.identityhashcode(contextdog));
return "通过config得到的dog:【地址:"+addr1+"】
" + "通过context得到的dog:【地址:"+addr2+"】";
}
}为了观察到不同会话的结果,我们选择使用浏览器和postman同时观察:

可以看出同一个浏览器的不同请求是属于一个会话,所以三次请求得到的一样,postman发送请求的cookie和浏览器的cookie不同,所以地址不同。
application全局web域
整个 web 应用(tomcat)只有 1 个实例,和 singleton 很像,但是层级不一样: singleton 是 spring 容器级别;application 是 servletcontext 级别,多个容器可以共享这个对象。日常开发很少用。
只有 web 环境生效!非 web 环境退化成单例!
总结
| 作用域 | 什么时候创建新对象 | 适用环境 |
|---|---|---|
| singleton | spring 容器启动(默认),全局仅 1 个 | 所有环境 |
| prototype | 每次 getbean 都新建 | 所有环境 |
| request | 每一次 http 请求新建 | 仅 web 环境 |
| session | 每一个用户会话新建 | 仅 web 环境 |
| application | 整个 web 项目只 1 个 | 仅 web 环境 |
面试重点:
1.controller 是singleton 单例,如果在 controller @resource或者@autowired直接注入 request/session 作用域 bean,spring 注入代理对象,代理负责动态获取当前请求 / 会话的真实 bean。
2.request/session 作用域没有 web 依赖(没有 spring-boot-starter-web)直接失效,退化成单例
3.prototype:spring不负责销毁;singleton 容器关闭时销毁;request 请求结束自动销毁
4.为什么bean为prototype时在单例中注入bean只会在spring容器中获取一次,而bean为reuqest或者session时却不会这样呢?
因为当bean的作用域为reuqest或者session时,创建bean使用代理模式,spring会创建一个代理对象放在容器中,此时bean指向的并非一个真实的对象,当创建bean时,代理对象会从容器中去获得真正的对象,并注入给对象。
到此这篇关于spring bean作用域详解之单例、原型、请求与会话的区别对比分析的文章就介绍到这了,更多相关spring bean作用域内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论