不知道各位看官有没有看过中华小当家呢,里面有一集特级厨师测验的非面料理,今天的主题和它差不多。通常我们的springboot项目于传统spring的web工程相比,减少了web.xml,applicationContext.xml,spring-mvc.xml等此类文件,从而节约了大量的配置。今天我们也来玩一玩这种模式。
基于上一篇文章,我们知道在spring-web的jar包中,存在一个javax.servlet.ServletContainerInitializer的文件,它是一个SPI的描述文件,文件内容就是文件名对应的实现类,这里的文件名即是接口全名。在tomcat启动后会调用实现类的onStartUp方法完成一些初始化逻辑。
在正式的流程开始前,先补充点前置知识,最初我们学web的时候,都知道有Servlet,Listener,Filter等三大组件,在Servlet3.0的规范中又有这样的描述,当我们的Servlet和Listener被实例化后,会调用javax.servlet.Servlet#init和javax.servlet.ServletContextListener#contextInitialized。所以无论spring写得怎么深奥,都不会跳出这些规范。

先看下具体的类的派生关系SpringServletContainerInitializer作为ServletContainerInitializer的子类,在启动后会被调用,然后WebApplicationInitializer的子类如上图,AbstractAnnotationConfigDispatcherServletInitializer作为模板类存在,其实我们只要实现两个方法即可,那就是getRootConfigClasses和getServletConfigClasses即可,典型的模板设计模式。这两个类的返回值主要作用其实和SpringApplication.run方法中的class对象一样。下面来一层层的把它给扒开。
AbstractContextLoaderInitializer的主要作用是负责将org.springframework.web.context.ContextLoaderListener加入到ServletContext对象中去,在这个期间,它会执行createRootApplicationContext方法(#1),会实例化一个spring的Context对象,而里面类扫描路径,正是由getRootConfigClasses的返回值进行指定(#2),同理在AbstractDispatcherServletInitializer中将spring-mvc中的核心对象DispatcherServlet注册到Servlet上下文中。并且将另一个spring的Context对象传入DispatcherServlet中(#3)。(这里顺便提一嘴,以前的web工程,配置spring定时任务,可能会出现同一个任务同一时刻执行了2次,可能就和类的扫描路径有关,从而两个spring上下文对象中各自注册了定时任务。所以按照规范,还是尽量将controller与service、dao扫描分开互相不影响。因为在后续操作中,两个Context会形成父子关系,DI的时候如果在子容器找不到,会到父容器中寻找)
1

2

3

现在一切准备工作就绪,就开始轮到Servlet和Listener的init方法了(#4),又是那熟悉的配方,又是那熟悉的味道(#5,#6),到最后肯定是调用Context的refresh,万变不离其宗。
4

5

6

最后,想对各位看官说一句,其实这种方式在springcloud大行其道的现在,实际应用中可以很肯定的说几乎用不到,但是为什么要写呢,就是想更深层次的了解spring源码,了解其中的设计模式,遵循的规范。像设计模式在日常开发中还是很有用的,帮助自己写出简单高效易读的代码。另外spring源码对于Java程序员的重要性,其实不亚于武侠小说中易筋经的存在,有了spring源码的基础,后续看其他框架源码就都不难了。在Java中,除开底层的C++代码,Java源码分为两大派系,一派是以spring为首,另一派是以JDK为首,各有各的难度,前者难在体系宏大,后者难在高度精炼。也希望各位看官在日常养成阅读源码的习惯,像我现在每用到一项新技术,总要翻开源码看看如何实现的,不然总感觉心里没谱,跟没用一样,这也是我寄希望于诸位的,感谢各位看官的阅读,谢谢。
