Spring 去除 web.xml,使用 Java 配置 Servlet 的原理,SPI
1. spring 使用 java 配置 Servlet 代码如下
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
public class WebServletConfiguration implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext ctx) {
AnnotationConfigWebApplicationContext webCtx = new AnnotationConfigWebApplicationContext();
// 注册context
webCtx.register(ApplicationConfig.class);
// 设置context
webCtx.setServletContext(ctx);
// 定义Servlet
ServletRegistration.Dynamic servlet = ctx.addServlet("spring", new DispatcherServlet(webCtx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
}
}
这段代码配置了一个名为 spring 的 Servlet, 所有的请求(/)都映射到这个 Servlet 中。
2. spring 的源码
在上一点中,可以看到关键的代码是现实了 WebApplicationInitializer
这个接口,很容易的找到这个接口唯一被引用的地方,即 org.springframework.web.SpringServletContainerInitializer
// HandlesTypes注解再这里的作用是tomcat回去扫描所有的WebApplicationInitializer的实现类,包括抽象类。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
// webAppInitializerClasses参数返回的就是所有的WebApplicationInitializer的实现类
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);
// 传入servletContext给WebApplicationInitializer的实现类,并调用onStartup方法,此方法又定义Servlet
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
3. tomcat 源码
继续根据实现的接口 ServletContainerInitializer
追溯到 tomcat 源码中。
再 org.apache.catalina.startup.ContextConfig#processServletContainerInitializers
方法中对 ServletContainerInitializer
的实现类进行了扫描,也就是扫描到了 SpringServletContainerInitializer
/**
* Scan JARs for ServletContainerInitializer implementations.
*/
protected void processServletContainerInitializers() {
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;
}
// 省略一堆源码
}
到这里需要知道 SPI(Service Provider Interface),简单的说就是一个服务发现的解决方案,这个方案有 JDK 的实现和 tomcat 的实现,上面源码中的 WebappServiceLoader 就是 tomcat 的实现。
SPI 中不得不说的配置文件就是 spring-web
包下 META-INFO/services
目录下的 javax.servlet.ServletContainerInitializer
文件,内容如下
org.springframework.web.SpringServletContainerInitializer
原理是,文件名称是服务发现的接口全路径名称,内容是接口实现类的全路径名称。SPI 会创建这个实现类的实例,并通过接口调用 org.springframework.web.SpringServletContainerInitializer#onStartup
方法。
4.总结
- 整个过程就不再追溯到 tomcat 的再往上的源码了,我们已经可以了解到 tomcat 和 spring 是怎么样实现了不使用 web.xml 配置 Servlet 的了。
- 如果有继续想要了解 JDK 的 SPI 实现,可以看下
java.util.ServiceLoader
- 如果看完 spring 的这个实现还不是很懂,可以看
logback
日志框架的ch.qos.logback.classic.servlet.LogbackServletContainerInitializer
实现。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于