Spring Framework 已是公認的 Java 標配開發框架了,甚至還有人說 Java 編程就是面向 Spring 編程的,可見 Spring 在整個 Java 體系中的重要位置。
Spring 中包含了眾多的功能和相關模塊,比如 spring-core、spring-beans、spring-aop、spring-context、spring-expression、spring-test 等,本課時先從面試中必問的問題出發,來幫你更好的 Spring 框架。
我們本課時的面試題是,Spring Bean 的作用域有哪些?它的注冊方式有幾種?
#### 典型回答
在 Spring 容器中管理一個或多個 Bean,這些 Bean 的定義表示為 BeanDefinition 對象,這些對象包含以下重要信息:
* Bean 的實際實現類
* Bean 的作用范圍
* Bean 的引用或者依賴項
Bean 的注冊方式有三種:
* XML 配置文件的注冊方式
* Java 注解的注冊方式
* Java API 的注冊方式
* [ ] 1. XML 配置文件注冊方式
```
<bean id="person" class="org.springframework.beans.Person">
<property name="id" value="1"/>
<property name="name" value="Java"/>
</bean>
```
* [ ] 2. Java 注解注冊方式
可以使用 @Component 注解方式來注冊 Bean,代碼如下:
```
@Component
public class Person {
private Integer id;
private String name
// 忽略其他方法
}
```
也可以使用 @Bean 注解方式來注冊 Bean,代碼如下:
```
@Configuration
public class Person {
@Bean
public Person person(){
return new Person();
}
// 忽略其他方法
}
```
其中 @Configuration 可理解為 XML 配置里的 <beans> 標簽,而 @Bean 可理解為用 XML 配置里面的 <bean> 標簽。
* [ ] 3. Java API 注冊方式
使用 BeanDefinitionRegistry.registerBeanDefinition() 方法的方式注冊 Bean,代碼如下:
```
public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition personBean = new RootBeanDefinition(Person.class);
// 新增 Bean
registry.registerBeanDefinition("person", personBean);
}
}
```
Bean 的作用域一共有 5 個。
* (1)singleton 作用域:表示在 Spring 容器中只有一個 Bean 實例,以單例的形式存在,是默認的 Bean 作用域。
配置方式,缺省即可,XML 的配置方式如下:
```
<bean class="..."></bean>
```
* (2)prototype 作用域:原型作用域,每次調用 Bean 時都會創建一個新實例,也就是說每次調用 getBean() 方法時,相當于執行了 new Bean()。
XML 的配置方式如下:
```
<bean class="..." scope="prototype"></bean>
```
* (3)request 作用域:每次 Http 請求時都會創建一個新的 Bean,該作用域僅適應于 WebApplicationContext 環境。
XML 的配置方式如下:
```
<bean class="..." scope="request"></bean>
```
Java 注解的配置方式如下:
```
@Scope(WebApplicationContext.SCOPE_REQUEST)
```
或是:
```
@RequestScope(WebApplicationContext.SCOPE_REQUEST)
```
* (4)session 作用域:同一個 Http Session 共享一個 Bean 對象,不同的 Session 擁有不同的 Bean 對象,僅適用于 WebApplicationContext 環境。
XML 的配置方式如下:
```
<bean class="..." scope="session"></bean>
```
Java 注解的配置方式如下:
```
@Scope(WebApplicationContext.SCOPE_SESSION)
```
或是:
```
@RequestScope(WebApplicationContext.SCOPE_SESSION)
```
* (5)application 作用域:全局的 Web 作用域,類似于 Servlet 中的 Application。
XML 的配置方式如下:
```
<bean class="..." scope="application"></bean>
```
Java 注解的配置方式如下:
```
@Scope(WebApplicationContext.SCOPE_APPLICATION)
```
或是:
```
@RequestScope(WebApplicationContext.SCOPE_APPLICATION)
```
#### 考點分析
在 Spring 中最核心的概念是 AOP(面向切面編程)、IoC(控制反轉)、DI(依賴注入)等(此內容將會在下一課時中講到),而最實用的功能則是 Bean,他們是概念和具體實現的關系。和 Bean 相關的面試題,還有以下幾個:
* 什么是同名 Bean?它是如何產生的?應該如何避免?
* 聊一聊 Bean 的生命周期。
#### 知識擴展
* [ ] 1.同名 Bean 問題
每個 Bean 擁有一個或多個標識符,在基于 XML 的配置中,我們可以使用 id 或者 name 來作為 Bean 的標識符。通常 Bean 的標識符由字母組成,允許使用特殊字符。
同一個 Spring 配置文件中 Bean 的 id 和 name 是不能夠重復的,否則 Spring 容器啟動時會報錯。但如果 Spring 加載了多個配置文件的話,可能會出現同名 Bean 的問題。同名 Bean 指的是多個 Bean 有相同的 name 或者 id。
Spring 對待同名 Bean 的處理規則是使用最后面的 Bean 覆蓋前面的 Bean,所以我們在定義 Bean 時,盡量使用長命名非重復的方式來定義,避免產生同名 Bean 的問題。
Bean 的 id 或 name 屬性并非必須指定,如果留空的話,容器會為 Bean 自動生成一個唯一的
名稱,這樣也不會出現同名 Bean 的問題。
* [ ] 2.Bean 生命周期
對于 Spring Bean 來說,并不是啟動階段就會觸發 Bean 的實例化,只有當客戶端通過顯式或者隱式的方式調用 BeanFactory 的 getBean() 方法時,它才會觸發該類的實例化方法。當然對于 BeanFactory 來說,也不是所有的 getBean() 方法都會實例化 Bean 對象,例如作用域為 singleton 時,只會在第一次,實例化該 Bean 對象,之后會直接返回該對象。但如果使用的是 ApplicationContext 容器,則會在該容器啟動的時候,立即調用注冊到該容器所有 Bean 的實例化方法。
getBean() 既然是 Bean 對象的入口,我們就先從這個方法說起,getBean() 方法是屬于 BeanFactory 接口的,它的真正實現是 AbstractAutowireCapableBeanFactory 的 createBean() 方法,而 createBean() 是通過 doCreateBean() 來實現的,具體源碼實現如下:
```
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
if (logger.isTraceEnabled()) {
logger.trace("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;
// 確定并加載 Bean 的 class
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// 驗證以及準備需要覆蓋的方法
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// 給BeanPostProcessors 一個機會來返回代理對象來代替真正的 Bean 實例,在這里實現創建代理對象功能
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
// 創建 Bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
```
doCreateBean 源碼如下:
```
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 實例化 bean,BeanWrapper 對象提供了設置和獲取屬性值的功能
BeanWrapper instanceWrapper = null;
// 如果 RootBeanDefinition 是單例,則移除未完成的 FactoryBean 實例的緩存
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 創建 bean 實例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 獲取 BeanWrapper 中封裝的 Object 對象,其實就是 bean 對象的實例
final Object bean = instanceWrapper.getWrappedInstance();
// 獲取 BeanWrapper 中封裝 bean 的 Class
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 應用 MergedBeanDefinitionPostProcessor 后處理器,合并 bean 的定義信息
// Autowire 等注解信息就是在這一步完成預解析,并且將注解需要的信息放入緩存
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 為了避免循環依賴,在 bean 初始化完成前,就將創建 bean 實例的 ObjectFactory 放入工廠緩存(singletonFactories)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 對 bean 屬性進行填充
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
// 調用初始化方法,如 init-method 注入 Aware 對象
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
} else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
// 如果存在循環依賴,也就是說該 bean 已經被其他 bean 遞歸加載過,放入了提早公布的 bean 緩存中
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 如果 exposedObject 沒有在 initializeBean 初始化方法中被增強
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 依賴檢測
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 如果 actualDependentBeans 不為空,則表示依賴的 bean 并沒有被創建完,即存在循環依賴
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
try {
// 注冊 DisposableBean 以便在銷毀時調用
registerDisposableBeanIfNecessary(beanName, bean, mbd);
} catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
```
從上述源碼中可以看出,在 doCreateBean() 方法中,首先對 Bean 進行了實例化工作,它是通過調用 createBeanInstance() 方法來實現的,該方法返回一個 BeanWrapper 對象。BeanWrapper 對象是 Spring 中一個基礎的 Bean 結構接口,說它是基礎接口是因為它連基本的屬性都沒有。
BeanWrapper 接口有一個默認實現類 BeanWrapperImpl,其主要作用是對 Bean 進行填充,比如填充和注入 Bean 的屬性等。
當 Spring 完成 Bean 對象實例化并且設置完相關屬性和依賴后,則會調用 Bean 的初始化方法 initializeBean(),初始化第一個階段是檢查當前 Bean 對象是否實現了 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware 等接口,源碼如下:
```
private void invokeAwareMethods(final String beanName, final Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
```
其中,BeanNameAware 是把 Bean 對象定義的 beanName 設置到當前對象實例中;BeanClassLoaderAware 是將當前 Bean 對象相應的 ClassLoader 注入到當前對象實例中;BeanFactoryAware 是 BeanFactory 容器會將自身注入到當前對象實例中,這樣當前對象就會擁有一個 BeanFactory 容器的引用。
初始化第二個階段則是 BeanPostProcessor 增強處理,它主要是對 Spring 容器提供的 Bean 實例對象進行有效的擴展,允許 Spring 在初始化 Bean 階段對其進行定制化修改,比如處理標記接口或者為其提供代理實現。
在初始化的前置處理完成之后就會檢查和執行 InitializingBean 和 init-method 方法。
InitializingBean 是一個接口,它有一個 afterPropertiesSet() 方法,在 Bean 初始化時會判斷當前 Bean 是否實現了 InitializingBean,如果實現了則調用 afterPropertiesSet() 方法,進行初始化工作;然后再檢查是否也指定了 init-method,如果指定了則通過反射機制調用指定的 init-method 方法,它的實現源碼如下:
```
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
// 判斷當前 Bean 是否實現了 InitializingBean,如果是的話需要調用 afterPropertiesSet()
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) { // 安全模式
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((InitializingBean) bean).afterPropertiesSet(); // 屬性初始化
return null;
}, getAccessControlContext());
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
} else {
((InitializingBean) bean).afterPropertiesSet(); // 屬性初始化
}
}
// 判斷是否指定了 init-method()
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
// 利用反射機制執行指定方法
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
```
初始化完成之后就可以正常的使用 Bean 對象了,在 Spring 容器關閉時會執行銷毀方法,但是 Spring 容器不會自動去調用銷毀方法,而是需要我們主動的調用。
如果是 BeanFactory 容器,那么我們需要主動調用 destroySingletons() 方法,通知 BeanFactory 容器去執行相應的銷毀方法;如果是 ApplicationContext 容器,那么我們需要主動調用 registerShutdownHook() 方法,告知 ApplicationContext 容器執行相應的銷毀方法。
注:本課時源碼基于 Spring 5.2.2.RELEASE。
#### 小結
本課時我們講了 Bean 的三種注冊方式:XML、Java 注解和 JavaAPI,以及 Bean 的五個作用域:singleton、prototype、request、session 和 application;還講了讀取多個配置文件可能會出現同名 Bean 的問題,以及通過源碼講了 Bean 執行的生命周期,它的生命周期如下圖所示:

####
課后問答
- 前言
- 開篇詞
- 開篇詞:大廠技術面試“潛規則”
- 模塊一:Java 基礎
- 第01講:String 的特點是什么?它有哪些重要的方法?
- 第02講:HashMap 底層實現原理是什么?JDK8 做了哪些優化?
- 第03講:線程的狀態有哪些?它是如何工作的?
- 第04講:詳解 ThreadPoolExecutor 的參數含義及源碼執行流程?
- 第05講:synchronized 和 ReentrantLock 的實現原理是什么?它們有什么區別?
- 第06講:談談你對鎖的理解?如何手動模擬一個死鎖?
- 第07講:深克隆和淺克隆有什么區別?它的實現方式有哪些?
- 第08講:動態代理是如何實現的?JDK Proxy 和 CGLib 有什么區別?
- 第09講:如何實現本地緩存和分布式緩存?
- 第10講:如何手寫一個消息隊列和延遲消息隊列?
- 模塊二:熱門框架
- 第11講:底層源碼分析 Spring 的核心功能和執行流程?(上)
- 第12講:底層源碼分析 Spring 的核心功能和執行流程?(下)
- 第13講:MyBatis 使用了哪些設計模式?在源碼中是如何體現的?
- 第14講:SpringBoot 有哪些優點?它和 Spring 有什么區別?
- 第15講:MQ 有什么作用?你都用過哪些 MQ 中間件?
- 模塊三:數據庫相關
- 第16講:MySQL 的運行機制是什么?它有哪些引擎?
- 第17講:MySQL 的優化方案有哪些?
- 第18講:關系型數據和文檔型數據庫有什么區別?
- 第19講:Redis 的過期策略和內存淘汰機制有什么區別?
- 第20講:Redis 怎樣實現的分布式鎖?
- 第21講:Redis 中如何實現的消息隊列?實現的方式有幾種?
- 第22講:Redis 是如何實現高可用的?
- 模塊四:Java 進階
- 第23講:說一下 JVM 的內存布局和運行原理?
- 第24講:垃圾回收算法有哪些?
- 第25講:你用過哪些垃圾回收器?它們有什么區別?
- 第26講:生產環境如何排除和優化 JVM?
- 第27講:單例的實現方式有幾種?它們有什么優缺點?
- 第28講:你知道哪些設計模式?分別對應的應用場景有哪些?
- 第29講:紅黑樹和平衡二叉樹有什么區別?
- 第30講:你知道哪些算法?講一下它的內部實現過程?
- 模塊五:加分項
- 第31講:如何保證接口的冪等性?常見的實現方案有哪些?
- 第32講:TCP 為什么需要三次握手?
- 第33講:Nginx 的負載均衡模式有哪些?它的實現原理是什么?
- 第34講:Docker 有什么優點?使用時需要注意什么問題?
- 彩蛋
- 彩蛋:如何提高面試成功率?