本文从原理上讲解Spring IoC容器的作用域机制,建议对着源码阅读。
0 引入问题
当我们谈到Spring作用域的时候,自然而然会想到如下作用域(来自spring-core官方文档):
作用域
描述
singleton
(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototype
Scopes a single bean definition to any number of object instances.
request
Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session
Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application
Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket
Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
从功能上看,这些作用域分别定义了调用org.springframework.beans.factory.BeanFactory#getBean()
方法时,容器根据bean definition
实例化bean object
的规则。
从底层实现上看,这些作用域可以分成两类:
内置作用域:singleton
和prototype
。
自定义作用域:request
、session
、application
、websocket
以及我们自定义的作用域。
所有Spring IoC容器中都具备singleton
和prototype
作用域功能,而只有实现Web功能的容器(如org.springframework.web.context.support.GenericWebApplicationContext
接口)中才具备request
和session
等作用域功能。这是因为它们底层的实现机制不同。
spring-core官方文档中说了这么一段话:
我个人这么理解作用域机制的扩展性:
内置的singleton
和prototype
作用域——不可扩展。
可以复写request
和session
等预定义作用域的规则——可扩展。
可以自定义作用域——可扩展。
以上简要概括了Spring IoC容器作用域的基本概念,希望能够引起大家思考以下几个问题(本文后续部分会一一探讨):
什么是作用域?如何使用作用域?
作用域的底层原理?
内置作用域和自定义作用域的区别?
如何自定义作用域?
1 什么是作用域?如何使用作用域?
1.1 什么是作用域?
作用域是个很宽泛的概念,本文讨论的特指是Spring IoC容器中Bean
对象的作用域,简单可以理解成:bean
对象的存活范围。
为了便于深入理解,我们先要大概了解一下Spring IoC容器的工作原理。Spring IoC容器的使用流程大概可以分为以下3个步骤:
配置Bean
:
1 2 3 4 5 6 7 8 9 @Configuration public class AppConfiguration { @Bean public A a () { return new A (); } } class A {}
创建Spring IoC容器,并读取配置信息:
1 ApplicationContext context = new AnnotationConfigApplicationContext (AppConfiguration.class);
从容器中获取bean
,并使用:
1 2 3 4 A a = context.getBean("a" , A.class);System.out.println(a); a = context.getBean("a" , A.class); System.out.println(a);
Bean
作用域本质上指的是多次调用context.getBean()
方法获取到的是否是同一个bean
对象。
上面例子中,默认指定作用域为singleton
,所以两次调用context.getBean()
方法获取到同一个对象。
如果指定作用域为prototype
:
1 2 3 4 5 @Bean @Scope("prototype") public A a () { return new A (); }
此时,两次调用context.getBean()
方法获取到就是两个对象了:
1 2 3 4 A a = context.getBean("a" , A.class);System.out.println(a); a = context.getBean("a" , A.class); System.out.println(a);
1.2 如何使用作用域?
作用域的使用比较简单,只需要在配置Bean
时使用指定作用域即可。我们使用作用域的重点其实在于不同作用域下context.getBean()
的规则。
1.2.1 singleton
singleton
作用域下,多次调用context.getBean()
方法获取到同一个对象。
1 2 3 4 5 6 7 8 9 @Configuration public class AppConfiguration { @Bean public A a () { return new A (); } } class A {}
1 2 3 4 5 ApplicationContext context = new AnnotationConfigApplicationContext (AppConfiguration.class);A a = context.getBean("a" , A.class);System.out.println(a); a = context.getBean("a" , A.class); System.out.println(a);
1.2.2 prototype
prototype
作用域下,每次调用context.getBean()
方法获取到新对象。
1 2 3 4 5 6 7 8 9 10 @Configuration public class AppConfiguration { @Bean @Scope("prototype") public A a () { return new A (); } } class A {}
1 2 3 4 5 ApplicationContext context = new AnnotationConfigApplicationContext (AppConfiguration.class);A a = context.getBean("a" , A.class);System.out.println(a); a = context.getBean("a" , A.class); System.out.println(a);
2 作用域的底层原理?
Bean
作用域本质上指的是多次调用context.getBean()
方法获取到的是否是同一个bean
对象。
所以,作用域底层执行原理在context.getBean()
方法中,其中与作用域有关的执行流程如下:
从BeanFactory
中获取已加载的BeanDefinition
,判断该Bean
的作用域。
如果是singleton
作用域,则执行单例创建规则。
如果是prototype
作用域,则执行原型创建规则。
如果是自定义作用域,则执行自定义创建规则。
相关核心源码如下(org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean()
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { Object prototypeInstance = null ; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); Scope scope = this .scopes.get(scopeName); try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException (beanName, scopeName, ex); } }
2.1 singleton
作用域
singleton
作用域bean
对象的创建过程分为三个步骤:
判断是否为singleton
作用域。
根据singleton
规则创建bean
对象。
对bean
对象进行后处理。
1、判断
mbd.isSingleton()
方法(org.springframework.beans.factory.support.AbstractBeanDefinition#isSingleton
)的源码如下:
1 2 3 public boolean isSingleton () { return SCOPE_SINGLETON.equals(this .scope) || SCOPE_DEFAULT.equals(this .scope); }
其中两个静态变量分别为:
1 2 String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; public static final String SCOPE_DEFAULT = "" ;
所以,我们在声明Bean
时,以下情况会声明为singleton
作用域:
默认情况(即不显示指定作用域),会默认声明为SCOPE_DEFAULT
作用域,而SCOPE_DEFAULT
实际上就是singleton
作用域。
1 2 3 4 @Bean public A a () { return new A (); }
显示指定为singleton
作用域,通过@Scope("singleton")
等方式。
1 2 3 4 5 @Bean @Scope("singleton") public A a () { return new A (); }
显示指定为默认作用域,通过@Scope
等方式。
1 2 3 4 5 @Bean @Scope public A a () { return new A (); }
2、创建单例bean
创建单例bean
的源码如下:
1 2 3 4 5 6 7 8 9 10 11 sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } });
其核心在于org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
方法,其中定义了大部分创建单例bean
的规则(模板方法模式):
为singletonObjects
对象(单例对象缓存)加锁:一次只能创建一个单例对象。
从singletonObjects
中获取当前beanName
的对象。
如果存在,说明已经创建,直接返回。
如果不存在,说明还没有创建,则进行创建对象:
预处理。
创建对象。
后处理。
添加到singletonObjects缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public Object getSingleton (String beanName, ObjectFactory<?> singletonFactory) { synchronized (this .singletonObjects) { Object singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null ) { beforeSingletonCreation(beanName); boolean newSingleton = false ; try { singletonObject = singletonFactory.getObject(); newSingleton = true ; } catch (IllegalStateException ex) { } catch (BeanCreationException ex) { } finally { if (recordSuppressedExceptions) { this .suppressedExceptions = null ; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } }
其中,预处理和后处理都是单例对象创建过程中的回调,可以通过重写自定义回调规则。默认情况下,预处理和后处理会分别标记/清除单例对象“创建中”的标记。
addSingleton(beanName, singletonObject)
方法会将该对象添加到singletonObjects
单例对象缓存和registeredSingletons
已注册单例对象缓存中,并将该对象从singletonFactories
单例工厂缓存和earlySingletonObjects
早期单例对象缓存中移除:
1 2 3 4 5 6 7 8 protected void addSingleton (String beanName, Object singletonObject) { synchronized (this .singletonObjects) { this .singletonObjects.put(beanName, singletonObject); this .singletonFactories.remove(beanName); this .earlySingletonObjects.remove(beanName); this .registeredSingletons.add(beanName); } }
最重要的singletonFactory.getObject()
方法是外部传入的,即调用该方法实际上会执行外部传入的匿名对象中定义的方法:
调用createBean(beanName, mbd, args)
会使用反射机制创建bean
对象。
如果创建失败,则调用destroySingleton(beanName)
方法删除相关缓存信息。
1 2 3 4 5 6 7 8 9 10 try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; }
3、后处理
org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance
方法会对上述根据BeanDefinition
创建处理bean
进行后处理,该方法其实就是Spring AOP功能的入口。其内部会进行如下判断:
容器内部使用。
是否为普通bean
:直接返回。
是否为org.springframework.beans.factory.FactoryBean
实现类:Spring AOP核心类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected Object getObjectForBeanInstance ( Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { if (BeanFactoryUtils.isFactoryDereference(name)) {} if (!(beanInstance instanceof FactoryBean)) { return beanInstance; } Object object = null ; if (mbd != null ) { mbd.isFactoryBean = true ; } else { object = getCachedObjectForFactoryBean(beanName); } if (object == null ) { FactoryBean<?> factory = (FactoryBean<?>) beanInstance; if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object; }
2.2 prototype
作用域
prototype
作用域bean
对象的创建过程分为三个步骤:
判断是否为prototype
作用域。
根据prototype
规则创建bean
对象。
对prototype
对象进行后处理。
1、判断
mbd.isPrototype()
方法(org.springframework.beans.factory.support.AbstractBeanDefinition#isPrototype
)的源码如下:
1 2 3 public boolean isPrototype () { return SCOPE_PROTOTYPE.equals(this .scope); }
其中静态变量为:
1 String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
所以,我们在声明Bean
时,可以通过@Scope("prototype")
等方式显示指定为prototype
作用域:
1 2 3 4 5 @Bean @Scope("prototype") public A a () { return new A (); }
2、创建原型bean
有了以上经验,我们就能很容易理解创建原型bean
的源码:
1 2 3 4 5 6 7 8 9 10 11 Object prototypeInstance = null ;try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); }
3、后处理
该后处理过程与singleton
作用域完全相同。
2.3 自定义作用域
如果BeanDefinition
既不是singleton
,也不是prototype
,那么就会执行自定义作用域的创建规则:
获取BeanDefinition
的scope
属性值。
从BeanFactory
的作用域缓存scopes
中获取对应的作用域。
调用scope.get()
方法,执行自定义创建规则。
后处理:Spring AOP功能入口,与singleton
和prototype
相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 String scopeName = mbd.getScope();Scope scope = this .scopes.get(scopeName);try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException (beanName, scopeName, ex); }
从以上源码中我们可以得出自定义作用域的三个步骤:
创建作用域实现类:实现org.springframework.beans.factory.config.Scope
接口,实现其get()
方法。
将作用域实现类注册到BeanFactory
的scopes
缓存中:key为作用域名,value为自定义作用域对象(org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope
)。
配置Bean
时,指定对应的作用域名。
3 内置作用域和自定义作用域的区别?
通过上述的讲解,想必大家对内置作用域(singleton
和prototype
)和自定义作用域的区别有了本质上的理解。
内置作用域的作用域名和bean
创建规则已经写死到Spring IoC容器中。
自定义作用域通过自定义的作用域名从BeanFactory
的scopes
缓存中找到自定义作用域实现类,根据其中实现的get()
方法创建bean
。同时,自定义作用域的bean
对象存放于自定义的缓存中。
4 自定义作用域案例:request
接下来,我们以request
作用域为例,展示如何自定义作用域。
4.1 RequestScope
实现类
该实现类全限定类名为org.springframework.web.context.request.RequestScope
。
其中,最核心的的部分在于org.springframework.web.context.request.AbstractRequestAttributesScope#get
:
获取当前请求的RequestAttributes
对象。
从缓存中获取bean
。
如果缓存中不存在,则需要重新创建:
使用objectFactory
匿名对象创建bean
。
将bean
放到缓存中。
重新从缓存中获取。
如果缓存中存在,直接返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override public Object get (String name, ObjectFactory<?> objectFactory) { RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); Object scopedObject = attributes.getAttribute(name, getScope()); if (scopedObject == null ) { scopedObject = objectFactory.getObject(); attributes.setAttribute(name, scopedObject, getScope()); Object retrievedObject = attributes.getAttribute(name, getScope()); if (retrievedObject != null ) { scopedObject = retrievedObject; } } return scopedObject; }
4.2 注册作用域
在实现Web功能的容器(如org.springframework.web.context.support.GenericWebApplicationContext
接口)中,会自动将request
等自定义作用域注册到BeanFactory
的scopes
缓存中:
1 2 3 4 5 6 7 8 protected void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) { if (this .servletContext != null ) { beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor (this .servletContext)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); } WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this .servletContext); WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this .servletContext); }
在这里会调用封装好的WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
方法进行注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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); sc.setAttribute(ServletContextScope.class.getName(), appScope); } beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory ()); beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory ()); beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory ()); beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory ()); if (jsfPresent) { FacesDependencyRegistrar.registerFacesDependencies(beanFactory); } }
4.3 配置Bean
在定义Bean
时,通过指定作用域为request
即可:
1 2 3 4 5 @Bean @Scope("request") public A a () { return new A (); }
为了方便,Spring还实现了@RequestScope
注解,使用方式如下:
1 2 3 4 5 @Bean @RequestScope public A a () { return new A (); }
@RequestScope
本质上和@Scope("request")
没有任何区别(不过我们在自定义作用域时可以采用类似的方式来炫技):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Scope(WebApplicationContext.SCOPE_REQUEST) public @interface RequestScope { @AliasFor(annotation = Scope.class) ScopedProxyMode proxyMode () default ScopedProxyMode.TARGET_CLASS; }
5 自定义作用域实战
在文章的最后,我们通过实战来自定义作用域myScope
,用来简单模拟singleton
作用域:
第一次调用context.getBean()
方法时创建新bean
。
之后每次调用context.getBean()
方法都会获取同一个bean
。
5.1 MyScope
实现类
该实现类核心在于get()
方法,其他方法都使用默认实现,因此省略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MyScope implements Scope { private static Map<String, Object> beanMap = new ConcurrentHashMap <>(); @Override public Object get (String name, ObjectFactory<?> objectFactory) { Object bean = beanMap.get(name); if (bean != null ) { bean = objectFactory.getObject(); beanMap.put(name, bean); } return bean; } }
5.2 注册作用域
注册作用域的方法定义为org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration public class AppConfiguration implements BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory (BeanFactory beanFactory) throws BeansException { this .beanFactory = beanFactory; } @PostConstruct public void registerScope () { if (beanFactory instanceof ConfigurableBeanFactory) { ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; configurableBeanFactory.registerScope("myScope" , new MyScope ()); } } }
5.3 配置Bean
1 2 3 4 5 @Bean @Scope("myScope") public A a () { return new A (); }
5.4 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @SpringBootApplication public class Application implements ApplicationContextAware { @Bean public ApplicationRunner runner () { return new ApplicationRunner () { @Override public void run (ApplicationArguments args) throws Exception { A a = applicationContext.getBean("a" , A.class); System.out.println(a); MyScope.getBeanMap().forEach((key, value) -> { System.out.println("key: " + key + ", value: " + value); }); a = applicationContext.getBean("a" , A.class); System.out.println(a); MyScope.getBeanMap().forEach((key, value) -> { System.out.println("key: " + key + ", value: " + value); }); } }; } private ApplicationContext applicationContext; public static void main (String[] args) { SpringApplication.run(Application.class, args); } @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } }
输出结果为:
1 2 3 4 com.xianhuii.springboot.demo.A@757194dc key: a, value: com.xianhuii.springboot.demo.A@757194dc com.xianhuii.springboot.demo.A@757194dc key: a, value: com.xianhuii.springboot.demo.A@757194dc