环境:Spring5.3.23
Spring Scope Bean是Spring用于管理Bean的作用域的一种机制。它定义了容器中Bean的生命周期和实例化策略,即如何创建Bean实例。
在Spring中,Bean的作用域包括单例(singleton)、原型(prototype)、请求(request)、会话(session)等。每个作用域都有其特定的使用场景和行为:
此外,Spring还提供了其他一些作用域应用(Application)、WebSocket,以满足不同场景的需求。
通过合理地选择Bean的作用域,可以优化应用的性能和资源利用率。例如,对于需要频繁创建和销毁实例的Bean,使用原型作用域会更高效;而对于需要在多个请求或会话之间共享状态的Bean,则可以选择单例或会话作用域。附官方图:
图片
接下来将分别介绍每一种作用域bean。
static class Person { @Override public String toString() { return super.toString() + " - " + this.hashCode() + "" ; }}
默认使用@Bean,@Service,@Controller注解标注的注解都是单例的。也可以同@Scope注解指定作用域为单例
@Bean// 不指定@Scope默认就是单例@Scope(value = "singleton")public Person person() { return new Person() ;}
测试
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { context.registerBean(Config.class) ; context.refresh() ; System.out.println(context.getBean(Person.class)) ; System.out.println(context.getBean(Person.class)) ;}
控制台输出
com.pack.main.scope.ScopeMain5$Person@5e0e82ae - 1578009262com.pack.main.scope.ScopeMain5$Person@5e0e82ae - 1578009262
每次获取的都是同一个实例。
原理
public abstract class AbstractBeanFactory { protected <T> T doGetBean(...) { // ... // 判断是否是单例 if (mbd.isSingleton()) { // 先从单例池中查找是否已经存在,不存在则调用createBean创建, // 然后存入单例池中 sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } }); } // ... }}
每次从容器中请求Bean时,都会创建一个新的Bean实例。
@Bean@Scope(value = "prototype")public Person person() { return new Person() ;}
控制台输出
com.pack.main.scope.ScopeMain5$Person@fa4c865 - 262457445com.pack.main.scope.ScopeMain5$Person@3bd82cf5 - 1004023029
每次获取都是不同的对象。
原理
public abstract class AbstractBeanFactory { protected <T> T doGetBean(...) { // ... // 判断是否是单例 if (mbd.isSingleton()) { // ... } // 判断是否是原型 else if (mbd.isPrototype()) { Object prototypeInstance = null; try { // 不存在什么缓存池,直接创建bean实例返回 prototypeInstance = createBean(beanName, mbd, args); } } // ... }}
这里考虑一个问题,如何在单例bean中正确的注入原型bean?
接下来都是与web环境相关了,所以这里演示的示例会以SpringBoot3.0.5环境演示。
基础类
@Component@Scope(value = "request")public class Person {}
测试类
@RestController@RequestMapping("/scopes")public class ScopeController { @Resource private Person person ; @Resource private PersonService ps ; @GetMapping("/request") public Person request() { System.out.println("ScopeController: " + person) ; ps.query() ; return person ; }}
Service
@Servicepublic class PersonService { @Resource private Person person ; public void query() { System.out.println("PersonService: " + person) ; }}
如果上面这样配置,启动服务将会报错:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-6.0.7.jar:6.0.7]
该错误的原因就是你在一个单例bean中注入一个request作用域的bean,而request作用域bean的生命周期是在一个web请求开始创建的,所以这里你当然是没法注入的。
解决办法:
@Component@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)public class Person {}
测试结果
ScopeController: com.pack.scopes.Person@106a9684 - 275420804PersonService: com.pack.scopes.Person@106a9684 - 275420804ScopeController: com.pack.scopes.Person@64396678 - 1681483384PersonService: com.pack.scopes.Person@64396678 - 1681483384
每次请求接口都获取的不是同一个实例。并且在一个完整的请求中获取的Person都是同一个。
该注解原理与上面其实一致的
@Scope(WebApplicationContext.SCOPE_REQUEST)public @interface RequestScope { @AliasFor(annotation = Scope.class) // 设置好了使用代理 ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}
@Component@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)// 与request一样,必须设置代理模式或者使用下面这个注解// @SessionScopepublic class Person {}
测试
ScopeController: com.pack.scopes.Person@2b56038d - 727057293PersonService: com.pack.scopes.Person@2b56038d - 727057293ScopeController: com.pack.scopes.Person@2b56038d - 727057293PersonService: com.pack.scopes.Person@2b56038d - 727057293
多次访问都是同一个session;你再换个浏览器访问
ScopeController: com.pack.scopes.Person@1aa201fd - 446824957PersonService: com.pack.scopes.Person@1aa201fd - 446824957ScopeController: com.pack.scopes.Person@1aa201fd - 446824957PersonService: com.pack.scopes.Person@1aa201fd - 446824957
此时对象就是一个新的了,不同的浏览器访问当然不是同一个session了。
@Scope(value = "application", proxyMode = ScopedProxyMode.TARGET_CLASS)// @ApplicationScope// 都是web环境,所以情况都一样public class Person {}
测试
360浏览器
ScopeController: com.pack.scopes.Person@6371b4b6 - 1668396214PersonService: com.pack.scopes.Person@6371b4b6 - 1668396214
Chrome浏览器
ScopeController: com.pack.scopes.Person@6371b4b6 - 1668396214PersonService: com.pack.scopes.Person@6371b4b6 - 1668396214
他们是同一个对象,application作用域生命周期与整个应用一样,只有你关闭了服务器,在启动后才会是再重新创建的bean对象。
public abstract class AbstractApplicationContext { public void refresh() { postProcessBeanFactory(beanFactory); }}public class AnnotationConfigServletWebServerApplicationContext { protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { super.postProcessBeanFactory(beanFactory); }}public class ServletWebServerApplicationContext { protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // ... registerWebApplicationScopes(); } private void registerWebApplicationScopes() { WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory()); }}public abstract class WebApplicationContextUtils { public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { registerWebApplicationScopes(beanFactory, null); } public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, @Nullable ServletContext sc) { // 注册作用域 beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope()); if (sc != null) { ServletContextScope appScope = new ServletContextScope(sc); beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); } }}
这里每一种web作用域都有一个对应的Scope实现RequestScope,SessionScope,ServletContextScope。
3.2 查找web作用域bean
public abstract class AbstractBeanFactory { protected <T> T doGetBean(...) { // ... // 判断是否是单例 if (mbd.isSingleton()) { // ... } // 判断是否是原型 else if (mbd.isPrototype()) { Object prototypeInstance = null; try { // 不存在什么缓存池,直接创建bean实例返回 prototypeInstance = createBean(beanName, mbd, args); } } // 其它作用域bean,如上面的web作用域 else { String scopeName = mbd.getScope(); Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { // 通过具体Scope的实现类获取bean对象 Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { // 首次都还是会创建 return createBean(beanName, mbd, args); } }); } } } // ... }}
总结:Spring Scope Bean是Spring框架中用于管理Bean的作用域的机制,它定义了Bean的生命周期和实例化策略。通过合理地选择Bean的作用域,可以优化应用的性能和资源利用率。
本文链接:http://www.28at.com/showinfo-26-57916-0.html玩转Spring各种作用域Bean Scope及源码分析
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。