spring常问面试题(面试官问为什么用Spring)
原创声明
这是本人署名原创文章,未经许可不支持转载。本公众号所有文章均原创。为了容易理解和记忆,文章以图解为主、代码为辅。如果您感兴趣,欢迎关注!文:吴潇/Java高级工程师
过完年了,应该有很多程序员开始准备面试了吧?这里帮大家整理了一些关于Spring的知识。相信绝大多数Java都曾经被面试官问到过"什么是IoC"、什么是"依赖注入",为什么要使用Spring,Spring是怎么初始化、Spring怎么解决循环依赖、bean的类型有哪些等问题。面试被问到这些问题,不会,然后上网逐个搜索答案总不是个好习惯,应该彻底把Spring弄懂才对。看完这篇文章之后,面对什么是Spring,为什么要使用Spring这类问题的时候,再也不会说出"因为用起来方便"这样让人尴尬的回答了,并且还能对Spring框架有自己的看法,让面试官给对你产生好感。
讲解Spring容器初始化过程前,顺便说一下,没必要畏惧阅读Spring的源码。Spring源码相对于JDK而言,简直太容易看懂,代码清晰明了,设计模式应用得恰到好处,也没有java.util.concurrent包中那些令人头疼的循环和状态变量,类名、变量名、函数名几乎不用解释,代码看一遍就能懂,学会非常容易。网上还有人宣称"Spring源码至少要看3遍"的,我觉得看一遍差不多了吧,看3遍太浪费时间。
Spring是容器还是工厂使用过Spring的同学都知道,"Spring是一个容器"。其实,这种说法不太准确,Spring提供的容器不止一种,例如有ClasspathXmlApplicationContext、XmlWebApplicationContext等多种容器,所有这些容器的父类都是BeanFactory。
但是为什么起名叫"工厂"呢?单纯的"容器"只起到一个存放对象的作用,如果只是想把对象存放起来,那么ArrayList,HashSet等Java集合类完全可以胜任,根本用不着Spring。Spring容器并不是单纯用于存放对象的,它存放对象定义(bean definition),而不止是存放对象(bean)。当需要一个对象时,我们从bean容器获取,而bean容器(BeanFactory)负责根据定义创建对象,所以说它是一个"工厂"。
反过来,它又为什么叫做"容器"?这是因为在大多数时候,bean都被定义为单例(singleton,与GoF中的singleton略有不同),且这些bean的类是Spring提供的ClassLoader加载的,单例的bean被实例化一次之后就一直保存在这个工厂中,以后每次请求获得这个bean都是同一个对象,相当于这个对象被放在BeanFactory中了,所以它又可以视为一个容器。
而对于非singleton的bean来说,例如scope等于prototype的bean,BeanFactory就主要表现为一个工厂了,每次请求都返回一个不同的实例。
既然说到scope,顺便分享一道常见的Spring面试题,"Spring中bean/对象的类型(scope)有哪些"?回答:取决于你所使用的具体容器。如果使用的是基本容器,例如ClasspathXmlApplicationContext,那么只有singleton和prototype可用;如果使用的是Web容器(例如WebXmlApplicationContext),那么就有singleton, prototype,request,session和global等可用;另外,scope还可以自定义。
Spring通过把自己设计成一个管理应用程序模块定义的容器以及工厂,而不是管理模块自身的容器,让模块可以分别独立开发,实现了模块之间的解耦。
深入理解Dependency Injection和Inversion of Control依赖注入(Depdendency Injection)和控制反转(IoC,Inversion of Control)是同一个概念,Spring实践了这个思想,为Java Web应用程序建立起一个基本框架。换句话说,Spring是Java Web应用程序的基本框架。下次面试官如果问你,"为什么要使用Spring",你可以回答"它提供了一个Web应用程序的基本框架",然后说它是工厂和容器,提供了IoC,不要回答"用起来方便"。
因为任何一个大型应用程序都包含很多组件(components),组件之间互相调用,并且在运行时并非所有组件都必须创建。如果没有一个整体的结构(框架),程序将会变得混乱、难以维护。假如不使用Spring,那么每次需要调用另一个组件的时候就new一个对象,或者getInstance得到一个实例。当组件越来越多,程序规模越来越大,这种方式会让组件之间的耦合度很高,难以维护。
Spring框架基于BeanFactory,把模块的配置信息(bean definition)统一管理起来(有的模块可以根据需要创建,若不调用则不创建),同时,还把模块的配置参数也统一管理(每个模块不必自己读配置参数,由Spring加载)。程序结构变清晰很多。在此基础上,加上依赖注入的功能,即Spring负责把模块依赖的其他模块推送(push)进来,而不是模块自己去拉取(pull),所以是一个"反转(IoC)",进一步简化了大型应用程序开发。
根据上面讨论,我们总结一下,为什么要使用Spring:
- Spring提供一个容器/工厂,统一管理模块的定义,根据需要创建。
- 把模块的配置参数统一管理,模块不需要自行读取配置。
- Spring提供依赖注入,把依赖的模块自动推送进来,不需要模块自己拉取。
- 此外,Spring提供了对很多其他第三方框架的集成功能,减少了样板代码(boilerplate)。
在Web应用程序启动过程中,Spring容器中的每个bean也有各自的初始化顺序。一个bean,从bean definition被加载到初始化完成,按照以下顺序执行:
1. BeanFactory加载完bean definition和class,实例化除了bean对象。
2. 检查有没有实现BeanNameAware,有则调用setBeanName(得到bean id)
3. 检查有没有实现BeanClassLoaderAware,有则调用setBeanClassLoader。
4. 检查有没有实现EnvironmentAware,有则调用setEnvironment。
5. 检查有没有实现EmbeddedValueResolverAware,有则调用setEmbeddedValueResolver。
6. 检查有没有实现ResourceLoaderAware,有则调用setResourceLoader。
7. 检查有没有实现ApplicationEventPublisherAware,有则调用setApplicationEventPublisher。
8. 检查有没有实现MessageSourceAware,有则调用setMessageSource。
9. 检查有没有实现ApplicationContextAware,有则调用setApplicationContext。
10. 检查有没有实现ServletContextAware,有则调用setServletContext。
11. 调用BeanPostProcessors中的所有postProcessBeforeInitialization,对bean进行一些更进一步的配置。
12. 调用InitializingBean接口中的afterPropertiesSet执行bean自身提供的初始化代码。
13. 调用通过其他方式指定的init-method方法,执行bean自身的初始化。
14. 调用BeanPostProcessors中的所有postProcessAfterInitialization方法。
经历了上述的14个步骤,spring bean才完整地创建出来,很不容易。从以上步骤可以了解到,在初始化方法中哪些资源是已经准备好可以访问的,哪些是还不能访问的。
以上我们分析了Spring的相关基础知识,希望对您的工作和面试都有帮助。如果对互联网编程技术、Java、Spring、Android、C/C 、Linux、个性化推荐、Community Detection、Machine Learning、Deep Learning、Data Mining、Gnuplot、LaTeX等技术感兴趣的话,欢迎关注本公众号(不限于Java)。,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com