SpringMvc如何通过零配置起一个DispatcherServlet的
1:拿经常使用Spring+springmvc举例子,
通常我们会配置下面的配置文件。
web.xml: 里面配置监听器,DispatcherServlet 等。 零配置之后就是靠tomcat启动通过SPI机制找ServletContainerInitializer实现类,调用onStartup(servlet上下文参数),在这个方法里通过servlet上下文.add(new DispatcherServlet())。对于监听器现在已经不需要了。
applicationContext.xml: 配置一些bean 和包扫描路径等。 零配置是通过@Bean +@Configuration @ComponentScan注解等。
springmvc.xml: 配置springmvc相关的一些配置。 零配置通过WebMvcConfigurer接口实现。
但是在spring官网上,对于DispatcherServlet的零配置写法,却是用的另外一个接口。
为什么使用WebApplicationInitializer接口也可以加入Servlet呢?上面不是说tomcat通过ServletContainerInitializer实现类加载的吗?这里spring-web做了一层转换。
在spring-web的jar包里面也有一个SPI机制。
javax.servlet.ServletContainerInitializer 的实现类,通过SPI机制加载,这里的实现类是SpringServletContainerInitializer,里面也有一个onStartup方法。这个加载执行就是在tomcat中执行的。
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList(); Iterator var4; if (webAppInitializerClasses != null) { var4 = webAppInitializerClasses.iterator(); while(var4.hasNext()) { Class<?> waiClass = (Class)var4.next(); if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try {
// 添加进initializers 中WebApplicationInitializer接口的实例 initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance()); } catch (Throwable var7) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); } else { servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); var4 = initializers.iterator(); while(var4.hasNext()) {
// 调用WebApplicationInitializer接口实例的onStartup方法 WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next(); initializer.onStartup(servletContext); } } }
既然找到了接口实现类在哪里调用的了,那onStartup方法参数中的webAppInitializerClasses 在哪里被传过来的呢?
其实在 SpringServletContainerInitializer类上有一个注解:HandlesTypes。
@HandlesTypes({WebApplicationInitializer.class})注解其实是个字节码技术,类似于javasist,asm,就是分析jar包中的class文件。
它的作用就是通过字节码技术解析jar包和程序中的class文件,就可以得知这个class实现了那些接口,继承了那些类,比如对比接口是否是WebApplicationInitializer,就相当于找出了这个接口的所有实现类,tomcat在使用的时候就找出的所有实现类赋值给了
onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) 方法的第一个参数。
那这个HandleTypes机制在哪里解析查找的呢?在tomcat启动过程中有一个ContextConfig的类。它里面webConfig()方法中调用一个 processServletContainerInitializers 方法,
根据注释也可以知道,它是扫描jar包中 ServletContainerInitializer 接口实现类
/** * Scan JARs for ServletContainerInitializer implementations. */ protected void processServletContainerInitializers() { List<ServletContainerInitializer> detectedScis; try {
// 自定义一个类加载器进行加载,加载之后放到集合里面 WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<ServletContainerInitializer>( context); detectedScis = loader.load(ServletContainerInitializer.class); } catch (IOException e) { log.error(sm.getString( "contextConfig.servletContainerInitializerFail", context.getName()), e); ok = false; return; } for (ServletContainerInitializer sci : detectedScis) {
// 这个集合中存的key是找到的sci,value暂时是空的集合,下一个步骤里面会填充 initializerClassMap.put(sci, new HashSet<Class<?>>()); // 处理带有@HandlesTypes注解的类 HandlesTypes ht; try { ht = sci.getClass().getAnnotation(HandlesTypes.class); } catch (Exception e) { if (log.isDebugEnabled()) { log.info(sm.getString("contextConfig.sci.debug", sci.getClass().getName()), e); } else { log.info(sm.getString("contextConfig.sci.info", sci.getClass().getName())); } continue; }
//如果不带那个注解就跳过 if (ht == null) { continue; } Class<?>[] types = ht.value(); if (types == null) { continue; } // @HandlesTypes 里面可能有很多class ,这里只有一个WebApplicationInitializer for (Class<?> type : types) { if (type.isAnnotation()) { handlesTypesAnnotations = true; } else { handlesTypesNonAnnotations = true; } Set<ServletContainerInitializer> scis = typeInitializerMap.get(type); if (scis == null) { scis = new HashSet<ServletContainerInitializer>(); typeInitializerMap.put(type, scis); } scis.add(sci); } } }
上面方法执行完,这个typeInitializerMap中存的是<WebApplicationInitializer.class,SpringServletContainerInitializer的实现类集合>。
initializerClassMap 这个集合中存的是<SpringServletContainerInitializer实现类,空的集合>
在webConfig()方法中这个方法执行完,下一步就会填充initializerClassMap中的value值
// Step 3. Look for ServletContainerInitializer implementations // 处理ServletContainerInitializers if (ok) { processServletContainerInitializers(); } if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) { // Steps 4 & 5. processClasses(webXml, orderedFragments); }
在processClasses中会把initializerClassMap 中的value填充,之后它里面就是<SpringServletContainerInitializer实现类,WebApplicationInitializer的实现类集合>
在webConfig()最后一个步骤,把initializerClassMap中的数据传到下一个容器中StandardContext中的 initializers属性中。
// Step 11. Apply the ServletContainerInitializer config to the // context if (ok) { for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } }
然后在StandardContext 中的startInternal()方法中遍历SpringServletContainerInitializer实现类调用onStartup方法传入WebApplicationInitializer的实现类集合。
到这一步,就走到spring-web中的那个 SpringServletContainerInitializer实现类了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现