1.背景
最近在使用Spring MVC過程中遇到了一些問題,網上搜尋不少帖子後雖然找到了答案和解決方法,但這些答案大部分都只是給了結論,並沒有說明具體原因,感覺總是有點不太滿意。
更重要的是這些所謂的結論大多是抄來抄去,基本源自一家,真實性也有待考證。
要成為一名優秀的碼農,不僅能熟練的複製貼上,更要有打破砂鍋問到底的精神,達到知其然也知其所以然的境界。
那作為程式設計師怎麼能知其所以然呢?
答案就是閱讀原始碼!
此處請大家內心默讀三遍。
用過Spring 的人都知道其核心就是IOC和AOP,因此要想了解Spring機制就得先從這兩點入手,本文主要通過對IOC部分的機制進行介紹。
2. 實驗環境
在開始閱讀之前,先準備好以下實驗材料。
-
Spring 5.0原始碼(github.com/spring-proj…)
-
IDE:Intellij IDEA
IDEA 是一個優秀的開發工具,如果還在用Eclipse的建議切換到此工具進行。
IDEA有很多的快捷鍵,在分析過程中建議大家多用Ctrl+Alt+B快捷鍵,可以快速定位到實現函式。
3. Spring Bean 解析註冊
Spring bean的載入主要分為以下6步:
- (1)讀取XML配置檔案
- (2)XML檔案解析為document文件
- (3)解析bean
- (4)註冊bean
- (5)例項化bean
- (6)獲取bean
3.1 讀取XML配置檔案
檢視原始碼第一步是找到程式入口,再以入口為突破口,一步步進行原始碼跟蹤。
Java Web應用中的入口就是web.xml。
在web.xml找到ContextLoaderListener ,此Listener負責初始化Spring IOC。
contextConfigLocation引數設定了bean定義檔案地址。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring.xml</param-value>
</context-param>
複製程式碼
下面是ContextLoaderListener的官方定義:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
Bootstrap listener to start up and shut down Spring's root WebApplicationContext. Simply delegates to ContextLoader as well as to ContextCleanupListener.
翻譯過來ContextLoaderListener作用就是負責啟動和關閉Spring root WebApplicationContext。
具體WebApplicationContext是什麼?開始看原始碼。
package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
//servletContext初始化時候呼叫
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext();
}
//servletContext銷燬時候呼叫
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
}
}
複製程式碼
從原始碼看出此Listener主要有兩個函式,一個負責初始化WebApplicationContext,一個負責銷燬。
繼續看initWebApplicationContext函式。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//初始化Spring容器時如果發現servlet 容器中已存在根Spring容根器則丟擲異常,證明rootWebApplicationContext只能有一個。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
if (this.context == null) {
//1.建立webApplicationContext例項
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//2.配置WebApplicationContext
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
//3.把生成的webApplicationContext 設定為root webApplicationContext。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
return this.context;
}
複製程式碼
在上面的程式碼中主要有兩個功能:
- (1)建立WebApplicationContext例項。
- (2)配置生成WebApplicationContext例項。
- (3)把生成的webApplicationContext 設定為root webApplicationContext。
3.1.1 建立WebApplicationContext例項
進入CreateWebAPPlicationContext函式
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//得到ContextClass類,預設例項化的是XmlWebApplicationContext類
Class<?> contextClass = determineContextClass(sc);
//例項化Context類
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
複製程式碼
進入determineContextClass函式。
protected Class<?> determineContextClass(ServletContext servletContext) {
// 此處CONTEXT_CLASS_PARAM = "contextClass"String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
//若設定了contextClass則使用定義好的ContextClass。
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
else {
//此處獲取的是在Spring原始碼中ContextLoader.properties中配置的org.springframework.web.context.support.XmlWebApplicationContext類。
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
複製程式碼
3.1.2 配置Web ApplicationContext
進入configureAndReFreshWebApplicaitonContext函式。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//webapplicationContext設定servletContext.
wac.setServletContext(sc);
// 此處CONFIG_LOCATION_PARAM = "contextConfigLocation",即讀即取web.xm中配設定的contextConfigLocation引數值,獲得spring bean的配置檔案.
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
//webApplicationContext設定配置檔案路徑設。
wac.setConfigLocation(configLocationParam);
}
//開始處理bean
wac.refresh();
}
複製程式碼
3.2 解析XML檔案
上面wac變數宣告為ConfigurableWebApplicationContext型別,ConfigurableWebApplicationContext又繼承了WebApplicationContext。
WebApplication Context有很多實現類。 但從上面determineContextClass得知此處wac實際上是XmlWebApplicationContext類,因此進入XmlWebApplication類檢視其繼承的refresh()方法。
沿方法呼叫棧一層層看下去。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//獲取beanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 例項化所有宣告為非懶載入的單例bean
finishBeanFactoryInitialization(beanFactory);
}
}
複製程式碼
獲取beanFactory。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//初始化beanFactory
refreshBeanFactory();
return beanFactory;
}
複製程式碼
beanFactory初始化。
@Override
protected final void refreshBeanFactory() throws BeansException {
DefaultListableBeanFactory beanFactory = createBeanFactory();
//載入bean定義
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
複製程式碼
載入bean。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//建立XmlBeanDefinitionReader例項來解析XML配置檔案
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
initBeanDefinitionReader(beanDefinitionReader);
//解析XML配置檔案中的bean。
loadBeanDefinitions(beanDefinitionReader);
}
複製程式碼
讀取XML配置檔案。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
//此處讀取的就是之前設定好的web.xml中配置檔案地址
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
//呼叫XmlBeanDefinitionReader讀取XML配置檔案
reader.loadBeanDefinitions(configLocation);
}
}
}
複製程式碼
XmlBeanDefinitionReader讀取XML檔案中的bean定義。
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
Resource resource = resourceLoader.getResource(location);
//載入bean
int loadCount = loadBeanDefinitions(resource);
return loadCount;
}
}
複製程式碼
繼續檢視loadBeanDefinitons函式呼叫棧,進入到XmlBeanDefinitioReader類的loadBeanDefinitions方法。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//獲取檔案流
InputStream inputStream = encodedResource.getResource().getInputStream();
InputSource inputSource = new InputSource(inputStream);
//從檔案流中載入定義好的bean。
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
}
複製程式碼
最終將XML檔案解析成Document文件物件。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
//XML配置檔案解析到Document例項中
Document doc = doLoadDocument(inputSource, resource);
//註冊bean
return registerBeanDefinitions(doc, resource);
}
複製程式碼
3.3 解析bean
上一步完成了XML檔案的解析工作,接下來將XML中定義的bean註冊到webApplicationContext,繼續跟蹤函式。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//使用documentRedder例項讀取bean定義
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
}
複製程式碼
用BeanDefinitionDocumentReader物件來註冊bean。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//讀取document元素
Element root = doc.getDocumentElement();
//真正開始註冊bean
doRegisterBeanDefinitions(root);
}
複製程式碼
解析XML文件。
protected void doRegisterBeanDefinitions(Element root) {
//預處理XML
preProcessXml(root);
//解析註冊bean
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
}
複製程式碼
迴圈解析XML文件中的每個元素。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//如果該元素屬於預設名稱空間走此邏輯。Spring的預設namespace為:http://www.springframework.org/schema/beans“
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
//對document中的每個元素都判斷其所屬名稱空間,然後走相應的解析邏輯
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
//如果該元素屬於自定義namespace走此邏輯 ,比如AOP,MVC等。
delegate.parseCustomElement(root);
}
}
複製程式碼
下面是預設名稱空間的解析邏輯。
不明白Spring的名稱空間的可以網上查一下,其實類似於package,用來區分變數來源,防止變數重名。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//解析import元素
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//解析alias元素
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//解析bean元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//解析beans元素
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
複製程式碼
這裡我們就不一一跟蹤,以解析bean元素為例繼續展開。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//解析bean
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 註冊bean
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
}
複製程式碼
解析bean元素,最後把每個bean解析為一個包含bean所有資訊的BeanDefinitionHolder物件。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
複製程式碼
3.4 註冊bean
接下來將解析到的bean註冊到webApplicationContext中。接下繼續跟蹤registerBeanDefinition函式。
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 獲取beanname
String beanName = definitionHolder.getBeanName();
//註冊bean
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 註冊bean的別名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
複製程式碼
跟蹤registerBeanDefinition函式,此函式將bean資訊儲存到到webApplicationContext的beanDefinitionMap變數中,該變數為map型別,儲存Spring 容器中所有的bean定義。
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
//把bean資訊儲存到beanDefinitionMap中
this.beanDefinitionMap.put(beanName, beanDefinition);
//把beanName 儲存到List 型別的beanDefinitionNames屬性中
this.beanDefinitionNames.add(beanName);
}
複製程式碼
3.5 例項化bean
Spring 例項化bean的時機有兩個。
一個是容器啟動時候,另一個是真正呼叫的時候。
如果bean宣告為scope=singleton且lazy-init=false,則容器啟動時候就例項化該bean(Spring 預設就是此行為)。否則在呼叫時候再進行例項化。
相信用過Spring的同學們都知道以上概念,但是為什麼呢?
繼續從原始碼角度進行分析,回到之前XmlWebApplication的refresh()方法。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//生成beanFactory,
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 例項化所有宣告為非懶載入的單例bean
finishBeanFactoryInitialization(beanFactory);
}
複製程式碼
可以看到獲得beanFactory後呼叫了 finishBeanFactoryInitialization()方法,繼續跟蹤此方法。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 初始化非懶載入的單例bean
beanFactory.preInstantiateSingletons();
}
複製程式碼
預先例項化單例類邏輯。
public void preInstantiateSingletons() throws BeansException {
// 獲取所有註冊的bean
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 遍歷bean
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//如果bean是單例且非懶載入,則獲取例項
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
getBean(beanName);
}
}
}
複製程式碼
獲取bean。
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
複製程式碼
doGetBean中處理的邏輯很多,為了減少干擾,下面只顯示了建立bean的函式呼叫棧。
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
//建立bean
createBean(beanName, mbd, args);
}
複製程式碼
建立bean。
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
複製程式碼
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// 例項化bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
複製程式碼
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
//例項化bean
return instantiateBean(beanName, mbd);
}
複製程式碼
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
//呼叫例項化策略進行例項化
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName,
}
複製程式碼
判斷哪種動態代理方式例項化bean。
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
//使用JDK動態代理
if (bd.getMethodOverrides().isEmpty()) {
return BeanUtils.instantiateClass(constructorToUse);
}
else {
//使用CGLIB動態代理
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
複製程式碼
不管哪種方式最終都是通過反射的形式完成了bean的例項化。
public static <T> T instantiateClass(Constructor<T> ctor, Object... args)
ReflectionUtils.makeAccessible(ctor);
return ctor.newInstance(args);
}
複製程式碼
3.6 獲取bean
我們繼續回到doGetBean函式,分析獲取bean的邏輯。
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
//獲取beanName
final String beanName = transformedBeanName(name);
Object bean
// 先檢查該bean是否為單例且容器中是否已經存在例化的單例類
Object sharedInstance = getSingleton(beanName);
//如果已存在該bean的單例類
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else{
// 獲取父BeanFactory
BeanFactory parentBeanFactory = getParentBeanFactory();
//先判斷該容器中是否註冊了此bean,如果有則在該容器例項化bean,否則再到父容器例項化bean
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
String nameToLookup = originalBeanName(name);
// 如果父容器有該bean,則呼叫父beanFactory的方法獲得該bean
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
//如果該bean有依賴bean,先實遞迴例化依賴bean。
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
registerDependentBean(dep, beanName);
getBean(dep);
}
}
//如果scope為Singleton執行此邏輯
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
//呼叫建立bean方法
return createBean(beanName, mbd, args);
}
}
});
}
//如果scope為Prototype執行此邏輯,每次獲取時候都例項化一個bean
else if (mbd.isPrototype()) {
Object prototypeInstance = null;
prototypeInstance = createBean(beanName, mbd, args);
}
//如果scope為Request,Session,GolbalSession執行此邏輯
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return createBean(beanName, mbd, args);
}
});
}
}
}
return (T) bean;
}
複製程式碼
上面方法中首先呼叫getSingleton(beanName)方法來獲取單例bean,如果獲取到則直接返回該bean。方法呼叫棧如下:
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
複製程式碼
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//從singletonObjects中獲取bean。
Object singletonObject = this.singletonObjects.get(beanName);
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
複製程式碼
getSingleton方法先從singletonObjects屬性中獲取bean 物件,如果不為空則返回該物件,否則返回null。
那 singletonObjects儲存的是什麼?什麼時候儲存的呢?
回到doGetBean()函式繼續分析。如果singletonObjects沒有該bean的物件,進入到建立bean的邏輯。處理邏輯如下:
//獲取父beanFactory
BeanFactory parentBeanFactory = getParentBeanFactory();
//如果該容器中沒有註冊該bean,且父容器不為空,則去父容器中獲取bean後返回
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
複製程式碼
下面是判斷容器中有沒有註冊bean的邏輯,此處beanDefinitionMap相信大家都不陌生,在註冊bean的流程裡已經說過所有的bean資訊都會儲存到該變數中。
public boolean containsBeanDefinition(String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
return this.beanDefinitionMap.containsKey(beanName);
}
複製程式碼
如果該容器中已經註冊過bean,繼續往下走。先獲取該bean的依賴bean,如果鑹子依賴bean,則先遞迴獲取相應的依賴bean。
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
registerDependentBean(dep, beanName);
getBean(dep);
}
}
複製程式碼
依賴bean建立完成後,接下來就是建立自身bean例項了。
獲取bean例項的處理邏輯有三種,即Singleton、Prototype、其它(request、session、global session),下面一一說明。
3.6.1 Singleton
如果bean是單例模式,執行此邏輯。
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
//建立bean回撥
return createBean(beanName, mbd, args);
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
複製程式碼
獲取單例bean,如果已經有該bean的物件直接返回。如果沒有則建立單例bean物件,並新增到容器的singletonObjects Map中,以後直接從singletonObjects直接獲取bean。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
//如果singletonObjects中沒有該bean
if (singletonObject == null) {
//回撥引數傳進來的ObjectFactory的getObject方法,即呼叫createBean方法建立bean例項
singletonObject = singletonFactory.getObject();
//置新建立單例bean標誌位為true。
newSingleton = true;
if (newSingleton) {
//如果是新建立bean,註冊新生成bean物件
addSingleton(beanName, singletonObject);
}
}
//返回獲取的單例bean
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
複製程式碼
把新生成的單例bean加入到型別為MAP 的singletonObjects屬性中,這也就是前面singletonObjects()方法中獲取單例bean時從此Map中獲取的原因。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//把新生成bean物件加入到singletonObjects屬性中。
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.registeredSingletons.add(beanName);
}
}
複製程式碼
3.6.2 Prototype
Prototype是每次獲取該bean時候都新建一個bean,因此邏輯比較簡單,直接建立一個bean後返回。
else if (mbd.isPrototype()) {
Object prototypeInstance = null;
//建立bean
prototypeInstance = createBean(beanName, mbd, args);
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
複製程式碼
3.6.3 request、session、global session
else {
//獲取該bean的scope
String scopeName = mbd.getScope();
//獲取相應scope
final Scope scope = this.scopes.get(scopeName);
//獲取相應scope的例項化物件
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return createBean(beanName, mbd, args);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
複製程式碼
從相應scope獲取物件例項。
public Object get(String name, ObjectFactory<?> objectFactory) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
//先從指定scope中獲取bean例項,如果沒有則新建,如果已經有直接返回
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null) {
//回撥函式呼叫createBean建立例項
scopedObject = objectFactory.getObject();
//建立例項後儲存到相應scope中
attributes.setAttribute(name, scopedObject, getScope());
}
}
return scopedObject;
}
複製程式碼
判斷scope,獲取例項函式邏輯。
public Object getAttribute(String name, int scope) {
//scope是request時
if (scope == SCOPE_REQUEST) {
//從request中獲取例項
return this.request.getAttribute(name);
}
else {
PortletSession session = getSession(false);
if (session != null) {
//scope是globalSession時,從application中獲取例項
if (scope == SCOPE_GLOBAL_SESSION) {
//從globalSession中獲取例項
Object value = session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
return value;
}
else {
//從session中獲取例項
Object value = session.getAttribute(name);
return value;
}
}
return null;
}
}
複製程式碼
在相應scope中設定例項函式邏輯。
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
this.request.setAttribute(name, value);
}
else {
PortletSession session = getSession(true);
if (scope == SCOPE_GLOBAL_SESSION) {
session.setAttribute(name, value, PortletSession.APPLICATION_SCOPE);
}
else {
session.setAttribute(name, value);
}
}
}
複製程式碼
以上就是Spring bean從無到有的整個邏輯。
4. 小結
從原始碼角度分析 bean的例項化流程到此基本接近尾聲了。
回到開頭的問題,ContextLoaderListener中初始化的WebApplicationContext到底是什麼呢?
通過原始碼的分析我們知道WebApplicationContext負責了bean的建立、儲存、獲取。其實也就是我們平時所說的IOC容器,只不過名字表述不同而已。
5. 尾聲
本文主要是講解了XML配置檔案中bean的解析、註冊、例項化。對於其它名稱空間的解析還沒有講到,後續的文章中會一一介紹。
希望通過本文讓大家在以後使用Spring的過程中有“一切盡在掌控之中”的感覺,而不僅僅是稀裡糊塗的使用。
想要了解更多,關注公眾號:七分熟pizza