spring为什么能成为如此广为接受的web应用框架首选?借助于javax.servlet提供出来的强大接口,spring可以接管整个web应用。
下面的源码介绍了spring-web是如何基于代码而不是xml来完成应用上下文的创建和实例化的。
从这一步开始,前面的jvm+tomcat+servlet被完美的封装起来而不需要我们操心,我们可以开心的创建自己的web应用,并想出各种模式让编程变得更加简单,就像spring所做的一样。
- 总结
1 利用javax.servlet提供的ServletContainerInitializer接口实现应用启动的监听
2 使用javax.servlet提供的HandlesTypes注解声明哪个类来完成启动配置
3 允许定义多个WebApplicationInitializer,使用@order注解或者Ordered接口排序
4 兼容code-based和xml-based,使用code-based使代码更简单简洁
package org.springframework.web;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* 在servlet 3.0+环境中实现的接口,以便以编程方式配置servletContext,
* 而不是(或可能与传统的基于web.xml的方法结合使用)
*
* 基于Servlet3.0容器自动启动的SpringServletContainerInitializer类将自动检测到此接口的SPI实现类
* 查看SpringServletContainerInitializer的文档信息获取引导机制详细信息
*
* 示例
* 传统的基于XML的方法
* 大部分spring用户创建web应用程序都需要将spring的DispatcherServlet注册到WEB-INF/web.xml文件中进行引用,如下所示:
* <servlet>
* <servlet-name>DispatcherServlet</servlet-name>
* <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
* <load-on-startup>1</load-on-startup>
* </servlet>
* <servlet-mapping>
* <servlet-name>DispatcherServlet</servlet-name>
* <url-pattern>/*</url-pattern>
* </servlet-mapping>
*
*
* 基于代码的WebApplicationInitializer方法
* 下面是等效的DispatcherServlet注册逻辑:
* public class MyWebAppInitializer implements WebApplicationInitializer {
*
* @Override
* public void onStartup(ServletContext container) {
* XmlWebApplicationContext appContext = new XmlWebApplicationContext();
* appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
* ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(appContext));
* dispatcher.setLoadOnStartup(1);
* dispatcher.addMapping("/");
* }
*
* }
*
* 作为上述的替代方案, 你还可以扩展阅读 AbstractDispatcherServletInitializer 的信息
*
* 如您所见,由于servlet 3.0的新servletContext#addservlet方法,我们实际上正在注册DispatcherServlet的一个实例,
* 这意味着DispatcherServlet现在可以像任何其他对象一样被处理——在本例中接收其应用程序上下文的构造函数注入
*
* 这种风格既简单又简洁. 不需要处理init参数等,只需要处理普通的JavaBean样式属性和构造函数参数.
* 在将Spring应用程序上下文注入DispatcherServlet之前,您可以根据需要自由地创建和使用它们.
*
* 大多数主要的spring web组建都支持这种风格的注册. 你会发现DispatcherServlet, FrameworkServlet,
* ContextLoaderListener and DelegatingFilterProxy 都支持参数构造器. 尽管一些组建(比如非spring的,
* 其它第三方)并没有提供使用WebApplicationInitializers的定制化更新,但他们仍然可以在任何情况下使用.
* Sevvlet3.0的ServletConfig接口允许程序式的设置初始参数,上下文参数等待.
*
* 100%基于代码的配置方法
* 在上面的例子中,WEB-INF/web.xml已经成功被WebApplictionInitializer替代了,但事实上spring配置文件
* dispather-config.xml仍然是基于XML格式的.
* WebApplicationInitializer非常使用spring基于代码的@Configuration配置类. 查看Configuration文档获
* 取跟多信息, 但下面的示例演示了如何重构以使用Spring的annotationConfigWebApplicationContext
* 代替xmlWebApplicationContext,以及用户定义的@configuration配置类、 appConfig和
* dispatcherConfig代替Spring XML文件. 以下示例超出了上面的内容,以演示root应用程序上下文的典型配置和对
* ApplicationLoaderListener的注册:
* public class MyWebAppInitializer implements WebApplicationInitializer {
*
* @Override
* public void onStartup(ServletContext container) {
* // Create the 'root' Spring application context
* AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
* rootContext.register(AppConfig.class);
*
* // Manage the lifecycle of the root application context
* container.addListener(new ContextLoaderListener(rootContext));
*
* // Create the dispatcher servlet's Spring application context
* AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
* dispatcherContext.register(DispatcherConfig.class);
*
* // Register and map the dispatcher servlet
* ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
* dispatcher.setLoadOnStartup(1);
* dispatcher.addMapping("/");
* }
*
* }
*
* 作为上述的替代方法,您还可以从AbstractAnnotationConfigDispatcherServletInitializer进行扩展.
*
* 切记WebApplicationInitializer的实现是自动检测的,所以你可以很自由的对自己的应用程序进行封包.
*
* 对WebApplicationInitializer的执行进行排序
* WebApplicationInitializer的实现类可以选择使用spring的类级别注解Order进行注解,也可以通过spring的Ordered接口来实现.
* 这样,initializers将按顺序依次被调用. 这为用户提供了一种机制来确保servlet容器初始化的顺序.
* 预计很少使用此功能,因为典型应用程序可能会将所有容器初始化集中在单个WebApplicationInitializer中.
*
* 注意事项
*
* web.xml版本控制
* WEB-INF/web.xml和WebApplicationInitializer的使用不是互斥的;例如,web.xml可以注册一个servlet,
* WebApplicationInitializer可以注册另一个servlet. 初始值设定项甚至可以通过servletContext#getservletregistration等
* 方法修改web.xml中执行的注册。但是,如果应用程序中存在WEB-INF/web.xml,则其version属性必须设置为“3.0”或更高版本,
* 否则ServletContainerInitializer引导将被servlet容器忽略.
*
* 映射到Tomcat下的“/”
* Apache Tomcat将其内部DefaultServlet映射到“/”,在Tomcat版本<=7.0.14上,不能以编程方式重写此servlet映射。
* 7.0.15解决了这个问题。覆盖“/”Servlet映射也已在GlassFish 3.1下成功测试
*
* @author Chris Beams
* @since 3.1
* @see SpringServletContainerInitializer
* @see org.springframework.web.context.AbstractContextLoaderInitializer
* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
* @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
*/
public interface WebApplicationInitializer {
/**
* 使用初始化此Web应用程序所需的任何servlets、过滤器、侦听器上下文参数和属性配置给定的servletcontext。
* 请参见上面的WebApplicationInitializer示例.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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容器配置,使用webApplicationInitailizer
* 的接口旨在与传统的基于web.xml的方法联合使用.
*
* 运行机制
* 任何符合servlet3.0规范的容器在启动时都会加载此类进行初始化,并调用onStartup(),比如在当前类路径下的spring-web模块的jar包.
* 这一步发生在jar服务接口的ServiceLoader#load(Class)方法检测到spring-web模块的SpringServletContainerInitializer,
* 配置文件路径为spring-web/META-INF/services/javax.servlet.ServletContainerInitializer,
* 参照JAR Services API 文档查看更多信息.
*
* 与web.xml结合使用
* Web应用程序可以选择通过web.xml中的metadata complete属性(该属性控制对servlet注释的扫描)或web.xml中的
* <absolute ordering>元素(该元素也控制哪些web)来限制servlet容器在启动时扫描的类路径量。允许片段(即JAR)
* 执行servletcontainerinitializer扫描。使用此功能时,可以通过将“spring_web”添加到web.xml中的命名web片
* 段列表中来启用springservletcontainerinitializer,如下所示:
*
* <absolute-ordering>
* <name>some_web_fragment</name>;
* <name>spring_web</name>;
* </absolute-ordering>
*
* 与Spring的WebApplicationInitializer的关系
* Spring的WebApplicationInitializer SPI只包含一个方法:WebApplicationInitializer onStartup(servletContext)。
* 签名有意地与servletContainerInitializer onStartup(set,servletContext)非常相似:简单地说,
* SpringServletContainerInitializer负责实例化servletContext并将其委托给任何用户定义的WebApplicationInitializer实现。
* 然后,每个WebApplicationInitializer都有责任完成初始化servletContext的实际工作。在下面的启动OnStartup文档中详细描述了授权的确切过程
*
* 一般注意事项
* 一般来说,这个类应该被视为更重要和面向用户的WebApplicationInitializer SPI的支持基础结构。利用此容器初始值设定项也是完全可选的:
* 虽然在所有servlet 3.0+运行时下都将加载和调用此初始值设定项是真的,但用户仍可以选择是否在类路径上
* 使用任何WebApplicationInitializer实现。如果未检测到WebApplicationInitializer类型,则此容器初始值设定项将不起作用
*
* 请注意,使用这个容器初始值设定项和WebApplication初始值设定项不会以任何方式“绑定”到SpringMVC,
* 除非这些类型是在SpringWeb模块jar中提供的。相反,它们可以被认为是通用的,因为它们能够方便地对servletcontext进行基于代码的配置。
* 换句话说,任何servlet、侦听器或过滤器都可以在WebApplicationInitializer中注册,而不仅仅是SpringMVC特定的组件。
*
* 这个类既不是为扩展而设计的,也不打算被扩展。它应该被视为内部类型,WebApplicationInitializer是面向公共的SPI。
*
* 另请参见WebApplicationInitializer文档
*
* @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 {
/**
* 将servletContext委托给应用程序类路径上存在的任何WebApplicationInitializer实现。
* 由于此类声明了handlesTypes(webapplicationInitializer.class),servlet 3.0+容器将自动扫描类路径以查找Spring
* 的webapplicationInitializer接口的实现,并将所有此类类型的集提供给此方法的webapplicationInitializerClasses参数
* 如果在类路径上未找到WebApplicationInitializer实现,则此方法实际上是一个no-op。将发出一条信息级日志消息,通知用户确
* 实调用了servletContainerInitializer,但未找到WebApplicationInitializer实现。
* 假设检测到一个或多个WebApplicationInitializer类型,则将对其进行实例化(如果存在@order annotation或已实现ordered
* 接口,则对其进行排序)。然后,将在每个实例上调用WebApplicationInitializer启动(servletContext)方法,委托servletContext,
* 以便每个实例可以注册和配置servlet,如Spring的Dispatcher servlet、侦听器(如Spring的ContextLoaderListener)或
* 任何其他servlet。API组件,如过滤器。
*
* @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 {
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);
}
}
}
}
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);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
package javax.servlet;
import java.util.Set;
/**
* 接口,它允许库/运行时收到Web应用程序启动阶段的通知,并执行响应它的servlet、过滤器和监听器的任何必需的编程注册。
* 此接口的实现可以用handlesTypes进行注释,以便(在其启动方法中)接收实现、扩展或已用注释指定的类类型进行注释的应用程序类集。
* 如果此接口的实现不使用handlesTypes批注,或者没有任何应用程序类与批注指定的类匹配,则容器必须将一组空的类传递给onStartup。
* 当检查应用程序的类以查看它们是否与servletContainerInitializer的handlesTypes注释指定的任何条件匹配时,如果缺少应用程序的任何可选
* JAR文件,则容器可能会遇到类加载问题。由于容器不能决定这些类型的类加载失败是否会阻止应用程序正常工作,因此它必须忽略它们,
* 同时提供一个配置选项来记录它们。
* 此接口的实现必须由位于META-INF/service s目录内的JAR文件资源声明,并以此接口的完全限定类名命名,并且将使用运行时的服务提供程序查找
* 机制或容器特定的机制来发现。at在语义上等价于它。在这两种情况下,必须忽略从绝对顺序排除的web片段jar文件中的
* servletcontainerinitializer服务,并且发现这些服务的顺序必须遵循应用程序的类加载委托模型。
*
* @see javax.servlet.annotation.HandlesTypes
*
* @since Servlet 3.0
*/
public interface ServletContainerInitializer {
/**
* 通知此伺服集装箱起始器的应用程序由服务器上下文代表。
*
* 如果此伺服集装箱被填充在一个应用程序的Web-INF/LIB目录中的一个Jar文件中,则只有在启动该应用程序时才使用该伺服集装箱方法。
* 如果这个伺服集装箱在任何Web-INF/LIB目录外的一个Jar文件中被包裹起来,但仍然可以在上面描述,则每次应用程序都会使用该伺服集装箱方法。
*
* @param c 扩展、实现或已用handlesTypes批注指定的类类型批注的应用程序类集,
* 如果没有匹配项,则为空,或者此servletContainerInitializer未用handlesTypes批注。
*
* @param ctx 正在启动的Web应用程序的servletContext,其中找到了C中包含的类
*
* @throws ServletException 当错误发生时抛出
*/
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
package javax.servlet.annotation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 此注释用于声明ServletContainerInitializer可以处理的类类型。
*
* @see javax.servlet.ServletContainerInitializer
*
* @since Servlet 3.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
/**
* 一个ServletContainerInitializer类表示有兴趣。
*
* 如果servletContainerInitializer的实现指定了此注解,servlet容器必须将扩展、实现或已用此注解列出的类类型注解的应用程序类集
* 传递给servletContainerInitializer启动方法servletContainerInitializer(如果找不到匹配的类,则必须传递空值)
*
* @return the classes in which {@link javax.servlet.ServletContainerInitializer
* ServletContainerInitializer} has expressed interest
*/
Class<?>[] value();
}