spring mvc源码学习笔记之十

前面的文章介绍了用 WebApplicationInitializer 或者 AbstractAnnotationConfigDispatcherServletInitializer 来代替 web.xml

我们学 java web 的时候就知道,servlet 容器会自动加载 web.xml

那么,疑问就来了,WebApplicationInitializer 或者 AbstractAnnotationConfigDispatcherServletInitializer 既然替代了 web.xml,那应该也会被 servlet 容器加载,是不是这样呢?答案是:是的。

而完成这个加载工作的是 SpringServletContainerInitializer 类。看下它的源码:

java 复制代码
package org.springframework.web;  
  
import java.lang.reflect.Modifier;  
import java.util.LinkedList;  
import java.util.List;  
import java.util.ServiceLoader;  
import java.util.Set;  
import javax.servlet.ServletContainerInitializer;  
import javax.servlet.ServletContext;  
import javax.servlet.ServletException;  
import javax.servlet.annotation.HandlesTypes;  
  
import org.springframework.core.annotation.AnnotationAwareOrderComparator;  
import org.springframework.lang.Nullable;  
import org.springframework.util.ReflectionUtils;  
  
/**
* 本类继承了 servlet 3.0 的 ServletContainerInitializer 接口。
* 下面这段话的意思是:
* servlet 3.0 的 ServletContainerInitializer 接口是用来让 servlet 容器支持基于编码的配置。
* 在这个过程中要结合 spring 的 WebApplicationInitializer。
* 而 spring 的 WebApplicationInitializer 可以替换 web.xml 也可以结合 web.xml 一起使用。
*
* Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based  
* configuration of the servlet container using Spring's {@link WebApplicationInitializer}  
* SPI as opposed to (or possibly in combination with) the traditional  
* {@code web.xml}-based approach.  
*
* 当 servlet 容器启动的时候,如果累路径下有 spring-web 的话,这个类会被加载,并且 onStartup 方法会被调用。
* 这背后的原理在于 java 的 SPI 机制。
* spring-web 模块下有个 META-INF/services/javax.servlet.ServletContainerInitializer 文件,
* 其内容就是当前类的完全限定名。
*
* <h2>Mechanism of Operation</h2>  
* This class will be loaded and instantiated and have its {@link #onStartup}  
* method invoked by any Servlet 3.0-compliant container during container startup assuming  
* that the {@code spring-web} module JAR is present on the classpath. This occurs through  
* the JAR Services API {@link ServiceLoader#load(Class)} method detecting the  
* {@code spring-web} module's {@code META-INF/services/javax.servlet.ServletContainerInitializer}  
* service provider configuration file. See the  
* <a href="http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider">  
* JAR Services API documentation</a> as well as section <em>8.2.4</em> of the Servlet 3.0  
* Final Draft specification for complete details.  
*  
* <h3>In combination with {@code web.xml}</h3>  
* A web application can choose to limit the amount of classpath scanning the Servlet  
* container does at startup either through the {@code metadata-complete} attribute in  
* {@code web.xml}, which controls scanning for Servlet annotations or through an  
* {@code <absolute-ordering>} element also in {@code web.xml}, which controls which  
* web fragments (i.e. jars) are allowed to perform a {@code ServletContainerInitializer}  
* scan. When using this feature, the {@link SpringServletContainerInitializer}  
* can be enabled by adding "spring_web" to the list of named web fragments in  
* {@code web.xml} as follows:  
*  
* <pre class="code">  
* {@code  
* <absolute-ordering>  
* <name>some_web_fragment</name>  
* <name>spring_web</name>  
* </absolute-ordering>  
* }</pre>  
*
* SpringServletContainerInitializer 可以看做一个简单的代理,真正的工作还是交给了 WebApplicationInitializer 。
*
* <h2>Relationship to Spring's {@code WebApplicationInitializer}</h2>  
* Spring's {@code WebApplicationInitializer} SPI consists of just one method:  
* {@link WebApplicationInitializer#onStartup(ServletContext)}. The signature is intentionally  
* quite similar to {@link ServletContainerInitializer#onStartup(Set, ServletContext)}:  
* simply put, {@code SpringServletContainerInitializer} is responsible for instantiating  
* and delegating the {@code ServletContext} to any user-defined  
* {@code WebApplicationInitializer} implementations. It is then the responsibility of  
* each {@code WebApplicationInitializer} to do the actual work of initializing the  
* {@code ServletContext}. The exact process of delegation is described in detail in the  
* {@link #onStartup onStartup} documentation below.  
*  
* <h2>General Notes</h2>  
* In general, this class should be viewed as <em>supporting infrastructure</em> for  
* the more important and user-facing {@code WebApplicationInitializer} SPI. Taking  
* advantage of this container initializer is also completely <em>optional</em>: while  
* it is true that this initializer will be loaded and invoked under all Servlet 3.0+  
* runtimes, it remains the user's choice whether to make any  
* {@code WebApplicationInitializer} implementations available on the classpath. If no  
* {@code WebApplicationInitializer} types are detected, this container initializer will  
* have no effect.  
*  
* <p>Note that use of this container initializer and of {@code WebApplicationInitializer}  
* is not in any way "tied" to Spring MVC other than the fact that the types are shipped  
* in the {@code spring-web} module JAR. Rather, they can be considered general-purpose  
* in their ability to facilitate convenient code-based configuration of the  
* {@code ServletContext}. In other words, any servlet, listener, or filter may be  
* registered within a {@code WebApplicationInitializer}, not just Spring MVC-specific  
* components.  
*
* 注意:不要扩展这个类,也不要继承这个类。
* 这个类应该被视为内部类型,不对外。
* 对外的是 WebApplicationInitializer。
*
* <p>This class is neither designed for extension nor intended to be extended.  
* It should be considered an internal type, with {@code WebApplicationInitializer}  
* being the public-facing SPI.  
*  
* <h2>See Also</h2>  
* See {@link WebApplicationInitializer} Javadoc for examples and detailed usage  
* recommendations.<p>  
*  
* @author Chris Beams  
* @author Juergen Hoeller  
* @author Rossen Stoyanchev  
* @since 3.1  
* @see #onStartup(Set, ServletContext)  
* @see WebApplicationInitializer  
*/  
@HandlesTypes(WebApplicationInitializer.class)  
public class SpringServletContainerInitializer implements ServletContainerInitializer {  
  
{  
    System.out.println("@HandlesTypes(WebApplicationInitializer.class) 这个注解表明了当前类 SpringServletContainerInitializer "
                   + " 需要处理的类型是 WebApplicationInitializer ");  
}  
  
/**  
* Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}  
* implementations present on the application classpath.
* 
* <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},  
* Servlet 3.0+ containers will automatically scan the classpath for implementations  
* of Spring's {@code WebApplicationInitializer} interface and provide the set of all  
* such types to the {@code webAppInitializerClasses} parameter of this method.  
* <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,  
* this method is effectively a no-op. An INFO-level log message will be issued notifying  
* the user that the {@code ServletContainerInitializer} has indeed been invoked but that  
* no {@code WebApplicationInitializer} implementations were found.  
* <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,  
* they will be instantiated (and <em>sorted</em> if the @{@link  
* org.springframework.core.annotation.Order @Order} annotation is present or  
* the {@link org.springframework.core.Ordered Ordered} interface has been  
* implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}  
* method will be invoked on each instance, delegating the {@code ServletContext} such  
* that each instance may register and configure servlets such as Spring's  
* {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},  
* or any other Servlet API componentry such as filters.  
* @param webAppInitializerClasses all implementations of  
* {@link WebApplicationInitializer} found on the application classpath  
* @param servletContext the servlet context to be initialized  
* @see WebApplicationInitializer#onStartup(ServletContext)  
* @see AnnotationAwareOrderComparator  
*/  
@Override  
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)  
throws ServletException {  
  
servletContext.log("SpringServletContainerInitializer 利用了 java 的 SPI 机制");  
  
List<WebApplicationInitializer> initializers = new LinkedList<>();  
  
if (webAppInitializerClasses != null) {  
for (Class<?> waiClass : webAppInitializerClasses) {  
// Be defensive: Some servlet containers provide us with invalid classes,  
// no matter what @HandlesTypes says...  
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);  
}  
}  
}  
}  
  
System.out.println("SpringServletContainerInitializer 在其 onStartup 方法中检测类路径下的 WebApplicationInitializer(spring的) ");  
  
if (initializers.isEmpty()) {  
servletContext.log("在类路径下没有检测到 spring 的 WebApplicationInitializer。------- No Spring WebApplicationInitializer types detected on classpath");  
return;  
}  
  
System.out.println("在类路径下检测到了 " + initializers.size() + " 个 spring 的 WebApplicationInitializer。");  
  
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");  
  
  
AnnotationAwareOrderComparator.sort(initializers);  
  
System.out.println("依次调用 WebApplicationInitializer 的 onStartup 方法");  
  
for (WebApplicationInitializer initializer : initializers) {  
  
System.out.println("调用 " + initializer.getClass().getName() + " 的 onStartup 方法");  
  
initializer.onStartup(servletContext);  
}  
  
System.out.println("至此,通过 servlet、java spi 成功引导了 spring ");  
  
}  
  
}

关于这个类有几个需要注意的点:

  • 它实现了 servlet 3.0 的 javax.servlet.ServletContainerInitializer 接口
  • 它加了个 @HandlesTypes(WebApplicationInitializer.class) 注解
  • 它实现了 javax.servlet.ServletContainerInitializer 接口中定义的 onStartup 方法

总结下来。SpringServletContainerInitializer 这个类实现了 servlet 3.0 的 javax.servlet.ServletContainerInitializer 接口,这就决定了在 servlet 容器启动的时候 onStartUp 方法会被自动触发,而在 onStartUp 方法内部 WebApplicationInitializeronStartUp 方法被调用。这就是 WebApplicationInitializer 被容器带起来的过程。

这里只是说了个大概,要想非常清楚,还请自己研究下 servlet 3.0 的 javax.servlet.ServletContainerInitializer 接口。

相关推荐
知识分享小能手1 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
茯苓gao4 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾4 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT4 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa5 小时前
HTML和CSS学习
前端·css·学习·html
看海天一色听风起雨落6 小时前
Python学习之装饰器
开发语言·python·学习
cyforkk6 小时前
Spring 异常处理器:从混乱到有序,优雅处理所有异常
java·后端·spring·mvc
Z_z在努力6 小时前
【杂类】Spring 自动装配原理
java·spring·mybatis
speop7 小时前
llm的一点学习笔记
笔记·学习
非凡ghost7 小时前
FxSound:提升音频体验,让音乐更动听
前端·学习·音视频·生活·软件需求