Spring5源码14-SpringMVC入口及启动流程
欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
1. 前言
Spring 的 Mvc
基于 Servlet
功能实现的,通过实现Servlet 接口的 DispatcherServlet
来封装其核心功能实现,通过将请求分派给处理程序
,同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上载文件的支持
。默认的处理程序是非常简单的Controller 接口
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
}
对Spring mvc 或者其他成熟的MVC 框架而言,解决的问题无外乎以下几点:
- 将web 页面的请求传给服务器
- 根据不同得到请求处理不同的逻辑单元
- 返回处理结果数据并跳转至相应页面
1.1 各个上下文的区别
在开始前,我们首先需要了解 ServletContext、ServletConfig、BeanFactory、ApplicationContext、WebApplicationContext
的区别:
-
ServletContext
:应该说是Serlvet层面的上下文。包含了WebApplicationContext
。Servlet规范中的概念,本质上并不是Spring的概念。他是servlet用来和容器间进行交互的接口的组合。也就是说,这个接口定 义了一系列的方法, servlet通过这些方法可以很方便地与自己所在的容器进行一些交互,比如通过getMajorVersion与getMinorVersion来获取容器的版本信息等.从它的定 义中也可以看出,在一个应用中(一个JVM)只有一个ServletContext,换句话说,容器中所有的servlet都共享同一个ServletContext. -
ServletConfig
:它与ServletContext
的区别在于,servletConfig
是针对servlet
而言的,每个servlet
都有它独有的serveltConfig
信息,相互之间不共享. -
BeanFactory
:Spring 中最基础的容器, 提供了最简单的IOC
功能。 -
ApplicationContext
:这个类是Spring实现容器功能的核心接口,它也是Spring实现IoC功能中最重要的接口,从它的名字中可以看出,它维护了整个程序运行期间所需要的上下文信息
, 注意这里的应用程序并不一定是web程序,也可能是其它类型的应用. 在Spring中允许存在多个applicationContext,这些context相互之间还形成了父与子,继承与被继承的关系,这也是通常我们所说的,在spring中存在两个context,一个是root context
,一个是servlet applicationContext
的意思。这点后面会进一步阐述. -
WebApplicationContext
: 其实这个接口不过是applicationContext
接口的一个子接口罢了,只不过说它的应用形式是web罢了。它在ApplicationContext
的基础上,添加了对ServletContext
的引用,即getServletContext
方法.
他们四者的关系如下:
ServletContext
针对Servlet来说,是Servlet的全局上下文。目前看到应该是作用于最大的,可以在前后端传递数据ServletConfig
针对的每个Servlet ,是每个Servlet的配置内容,即init-param
标签内容BeanFactory
提供了最基础的SpringIOC功能.ApplicationContext
在BeanFactory
的基础上增加了更多的功能。这里实际上是ApplicationContext
中有BeanFactory变量而并非ApplicationContext继承了BeanFactory。 关于IOC的–些功能,ApplicationContext实际.上还是委托给了BeanFactory完成,即调用BeanFactory的方法。
1.2 Servlet 的生命周期
Java Servlet 是运行在 Web
服务器或应用服务器上的程序,它是作为来自 Web
浏览器或其他 HTTP
客户端的请求和 HTTP
服务器上的数据库或应用程序之间的中间层。
Servlet
是一个java 编写的程序,此程序基于http 协议,在服务端运行达到是按照servlet 规范编写的一个类。主要处理客户端的请求并将结果发送到客户端。Servlet 的生命周期是由Servlet 容器来控制的,可以分为三个阶段。
1.2.1 初始化阶段
servle
t容器加载 servlet类, 把 servlet 类的 .class 文件中的数据读取到内存中servlet
容器创建了一个ServletConfig对象。ServletConfig 对象包含了 servlet 的初始化配置信息servlet
容器创建了一个servlet 对象servlet
容器调用servlet
对象的init
方法进行初始化。
1.2.2 运行阶段
当一个 servlet
容器收到一个请求后,servlet
容器会针对这个请求创建一个 servletRequest
和 servletResponse
对象,然后调用service
方法。并将这两个参数传递给 service
方法。service 方法通过 servletRequest 对象获取请求的信息,并处理该请求再通过 servletResponse 对象生成这个请求的相应结果。然后销毁servletRequest 和 servletResponse 对象。
1.2.3 销毁阶段
当web
应用被终止时,servlet
容器会先调用servlet
对象的destory
方法,然后再销毁servlet 对象,同时也会销毁与 servlet 对象想关联的servletConfig 对象。我们可以在destory 方法的实现中,释放servlet 所占用的资源,如关闭数据库连接等。
2. 环境搭建
2.1 新建mvc工程
不在阐述
2.2 代码
仿照官网案例,使用注解版:
/**
* 只要写了这个,相当于配置了SpringMVC的DispatcherServlet
* 1、Tomcat一启动就加载他
* 1)、创建了容器、制定了配置类(所有ioc、aop等spring的功能就ok)
* 2)、注册一个Servlet; DispatcherServlet;
* 3)、以后所有的请求都交给了 DispatcherServlet;
* 效果,访问Tomcat部署的这个Web应用下的所有请求都会被 DispatcherServlet 处理
* DispatcherServlet就会进入强大的基于注解的mvc处理流程(@GetMapping)
* 必须Servlet3.0以上才可以;Tomcat6.0以上才支持Servlet3.0规范
*
* Servlet3.0是javaEE的Web的规范标准,Tomcat是Servlet3.0规范的一个实现;
*/
public class AppStarter implements WebApplicationInitializer {
@Override
public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
//1、创建ioc容器
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
//2、传入一个配置类
context.register(SpringConfig.class);
//以上截止,ioc容器都没有启动
//3、配置了 DispatcherServlet,利用Servlet的初始化机制
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/"); //映射路径
//启动了容器?上面的Servlet添加到 servletContext 里面以后,Tomcat就会对 DispatcherServlet进行初始化
//<servlet></servlet>
// servletContext.addServlet("abc",XXXX.class)
}
}
创建配置类SpringConfig:
/**
* Spring不扫描controller组件、
* AOP咋实现的,事务 看Spring容器
*/
@Configuration
@ComponentScan(value = "com.hsf.web")
public class SpringConfig {
// Spring父容器
}
2.3 配置tomcat启动
启动tomcat,并访问 http://localhost:8080/springmvc_source/ 即可。
2.4 启动流程
2.4.1 Tomcat通过SPI机制扫描
org.apache.catalina.startup.ContextConfig#webConfig
...
if (ok) {
// 5.查找ServletContainerInitializer实现,并创建实例,查找范围分为两部分。
// 6.初始化typeInitializerMap和initializerClassMap两个映射(主要用于后续的注解检测)
processServletContainerInitializers();
}
...
接下来:
protected void processServletContainerInitializers() {
/**
* 5. 查找META-INF/services/javax.servlet.ServletContainerInitializer配置文件,
* 获取ServletContainerInitializer,这里的ServletContainerInitializer可以使用HandlesTypes注解指定需要处理的类型
* - Web应用下的包:如果javax.servlet.context..orderedLibs不为空,仅搜索该属性包含的包,否则搜索WEB-INF/Iib下所有包。
* - 容器包:搜索所有包。
*/
List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.servletContainerInitializerFail",
context.getName()),
e);
ok = false;
return;
}
...
}
扫描META-INF/services/javax.servlet.ServletContainerInitializer
并加载对应的类,在Spring-web工程下正好有:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
// 所有非接口 非抽象类WebApplicationInitializer实现类
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
// todo 遍历所有符合条件的WebApplicationInitializer 回调onStartup方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
SpringServletContainerInitializer
只处理WebApplicationInitializer
,通过代码可以知道会 遍历WebApplicationInitializer
并执行onStartup
方法。
在这里,有一个问题,谁在什么时候调用了ServletContainerInitializer.onStartup 方法呢?具体可看Tomcat源码系列
2.4.2 Tomcat的初始化方法
DispatcherServlet
的继承图:
调用Servlet的init方法,会直接调到FrameworkServlet#initServletBean
方法:
protected final void initServletBean() throws ServletException {
...
long startTime = System.currentTimeMillis();
try {
// todo
this.webApplicationContext = initWebApplicationContext();
// 留给子类覆盖操作
initFrameworkServlet();
}
...
}
protected WebApplicationContext initWebApplicationContext() {
// 先会 获取之前的WebApplicationContext(构建父子容器)
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 1. 如果 webApplicationContext 在构造函数的时候被注入,则 wac != null, 则可以直接使用
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// 当前 web-ios 容器
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
// todo 父子容器的体现
cwac.setParent(rootContext);
}
// todo 2. 刷新上下文环境 配置并且刷新容器
configureAndRefreshWebApplicationContext(cwac);
}
}
}
...
return wac;
}
在configureAndRefreshWebApplicationContext(cwac);
中会刷新Spring容器。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
...
// todo 刷新AbstractApplicationContext#refresh
wac.refresh();
}
在这里会启动Spring容器的刷新操作。
2.5 流程框图
3. 父子容器(注解版)
3.1 父子容器
官网的框图:
以前xml配置版:
- 在
web.xml
中配置ContextLoaderListener
,指定Spring配置文件
的位置 - 在
web.xml
中配置DispatcherServlet
,指定SpringMVC配置文件
位置 - 以上会产生父子容器
父容器(Spring配置文件进行包扫描并保存所有组件的容器),子容器(SpringMVC配置文件进行包扫描并保存所有组件的容器),子容器通过webloc.setParent(springloc))
可以调用父容器,双亲委派,容器隔离
。
3.2 WebApplicationInitializer继承图
继承AbstractAnnotationConfigDispatcherServletInitializer
可以更快的整合SpringMVC和Spring容器。代码如下:
/**
* 最快速的整合注解版SpringMVC和Spring的
*/
public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override //根容器的配置(Spring的配置文件===Spring的配置类)
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringConfig.class};
}
@Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMVCConfig.class};
}
@Override //Servlet的映射,DispatcherServlet的映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// super.customizeRegistration(registration);
// registration.addMapping("");//
}
}
SpringConfig
和SpringMVCConfig
的配置如下:
/**
* Spring不扫描controller组件、
* AOP咋实现的,事务 看Spring容器
*/
@Configuration
@ComponentScan(value = "com.hsf.web",excludeFilters = {
@ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
})
public class SpringConfig {
// Spring父容器
}
/**
* SpringMVC只扫描controller组件,可以不指定父容器类,让MVC扫所有。@Component+@RequestMapping就生效了
*/
@ComponentScan(value = "com.hsf.web",includeFilters = {
@ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
},useDefaultFilters = false)
@Configuration
public class SpringMVCConfig {
// SpringMVC 子容器,能扫描的Spring容器中的组件
}
我们在QuickAppStarter
类中的三个方法分别打上 断点,Spring容器和SpringMVC的刷新流程是在什么时机触发的?
3.3 父子容器启动流程分析
3.3.1 WebApplicationInitializer.onStartup方法
分析和第2小节一样,我们直接来到QuickAppStarter.onStartup
方法,需要看其父类的 AbstractDispatcherServletInitializer.onStartup
方法:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// todo 先执行父类的onStartup方法
super.onStartup(servletContext);
// todo 注册 DispatcherServlet
registerDispatcherServlet(servletContext);
}
3.3.1.1 先执行父类AbstractContextLoaderInitializer.onStartup(servletContext);
:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// todo 注册ContextLoadListener.contextInitialized
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
// todo 创建 根容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
我们来AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext
方法:
// 重写了爷爷类的根 容器方法
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
// todo 获取根 配置
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 创建IOC容器,并把配置类注册进来
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
getRootConfigClasses();
其实就是从子类(我们写的QuickStarter.getRootConfigClasses()
)获取根 容器的配置类。
3.3.1.2 在执行registerDispatcherServlet
AbstractDispatcherServletInitializer#registerDispatcherServlet
:
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
// 创建Servlet 容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
核心方法AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext
:
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 获取web的配置文件
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
getServletConfigClasses()
其实就是调用子类(我们写的QuickStarter.getServletConfigClasses()
)的配置类。
3.3.2 何时刷新容器呢?
ContextLoaderListener何时被调用?
Servlet应用监听器回调
:在当前web应用启动以后(Tomcat把Web应用加载以后),调用contextInitialized方法,tomcat源码对应为:
org.apache.catalina.core.StandardContext#startInternal:
protected synchronized void startInternal() throws LifecycleException {
...
// Configure and call application event listeners
// 21.实例化应用监听器(ApplicationListener),分为事件监听器(ServletContextAttribute-
//Listener ServletRequestAttributeListener ServletRequestListener,HttpSessionldListener,HttpSession-
//AttributeListener)以及生命周期监听器(HttpSessionListener、ServletContextListener)。这些监听
//器可以通过Contexti部署描述文件、可编程的方式(ServletContainerInitializer)或者Web.xml添加,
//并且触发ServletContextListener..contextInitialized。
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
...
}
public boolean listenerStart() {
...
Object instances[] = getApplicationLifecycleListeners();
if (instances == null || instances.length == 0) {
return ok;
}
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextEvent tldEvent = null;
if (noPluggabilityListeners.size() > 0) {
noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
tldEvent = new ServletContextEvent(noPluggabilityServletContext);
}
for (Object instance : instances) {
if (!(instance instanceof ServletContextListener)) {
continue;
}
ServletContextListener listener = (ServletContextListener) instance;
try {
fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
fireContainerEvent("afterContextInitialized", listener);
getLogger().error(sm.getString("standardContext.listenerStart",
instance.getClass().getName()), t);
ok = false;
}
}
return ok;
}
获取所有的生命周期监听器getApplicationLifecycleListeners()
:
@Override
public Object[] getApplicationLifecycleListeners() {
return applicationLifecycleListenersObjects;
}
我们在第3.3.1小节中registerContextLoaderListener
:
protected void registerContextLoaderListener(ServletContext servletContext) {
// todo 创建 根容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
...
}
其中,servletContext.addListener(listener);
就是加入到ApplicationContext的
applicationLifecycleListenersObjects属性中。
下面,我们分析下ContextLoaderListener.contextInitialized()
都做了什么?
org.springframework.web.context.ContextLoaderListener#contextInitialized
:
@Override
public void contextInitialized(ServletContextEvent event) {
// 初始化web-ios容器
initWebApplicationContext(event.getServletContext());
}
核心方法ContextLoader#initWebApplicationContext
:
// 初始化 webApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 初始化完成的 webApplicationContext 会被保存到 servletContext 的属性中,key 为 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
// 所以这里是判断是否已经初始化了webApplicationContext,就抛出异常(web.xml 中声明了多次ContextLoader 的定义),不可重复初始化。
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!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
// todo 创建 WebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// todo 刷新上下文环境
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将创建好的 WebApplicationContext 保存到 servletContext 中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// 映射当前的类加载器 与 创建的实例到全局变量 currentContextPerThread 中。
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
因为 我们在之前 已经创建了根容器,所以就直接进入configureAndRefreshWebApplicationContext(cwac, servletContext);
方法:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
...
// todo 刷新容器
wac.refresh();
}
在这里,直接 根 容器的刷新操作。
DispatcherServlet 何时初始化?
Servlet初始化回调
:调用链路:Servlet.init() -> GenericServlet.init()->HttpServletBean.init()
:
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 1. 封装及验证初始化参数
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 2. 将当前 Servlet 实例转化为 BeanWrapper 实例
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 3. 注册于相对于 Resource 的属性编辑器
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// 4. 属性注入
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// todo 5.servletBean的初始化 留给子类的模板方法
initServletBean();
}
核心方法initServletBean()
,留给子类实现,我们直接看FrameworkServlet#initServletBean
:
@Override
protected final void initServletBean() throws ServletException {
...
long startTime = System.currentTimeMillis();
try {
// todo
this.webApplicationContext = initWebApplicationContext();
// 留给子类覆盖操作
initFrameworkServlet();
}
...
}
核心方法initWebApplicationContext()
:
protected WebApplicationContext initWebApplicationContext() {
// 先会 获取之前的WebApplicationContext(构建父子容器)
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 1. 如果 webApplicationContext 在构造函数的时候被注入,则 wac != null, 则可以直接使用
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// 当前 web-ios 容器
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
// todo 父子容器的体现
cwac.setParent(rootContext);
}
// todo 2. 刷新上下文环境 配置并且刷新容器
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 3. 如果构造函数并没有注入,则wac为null,根据 contextAttribute 属性加载 WebApplicationContext
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
// 根据 contextAttribute 属性加载 WebApplicationContext
wac = findWebApplicationContext();
}
// 4. 如果上面两个方式都没有加载到 WebApplicationContext,则尝试自己加载
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// todo 自己尝试创建WebApplicationContext
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// todo 5.刷新
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
我们关注下面的核心点:
- 构建父子容器
cwac.setParent(rootContext);
- 刷新子容器
configureAndRefreshWebApplicationContext(cwac);
:
在这里会 调用子容器的刷新方法。子容器启动,并且构建了父子容器。protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { ... // todo 刷新AbstractApplicationContext#refresh wac.refresh(); }
3.4 SpringMVC父子容器启动流程图
参考文章
Spring5源码注释github地址 Spring源码深度解析(第2版) spring源码解析 Spring源码深度解析笔记 Spring注解与源码分析 Spring注解驱动开发B站教程
转载自:https://juejin.cn/post/7137851664207183902