springMVC之利用Servlet3.0的SPI机制实现零配置原理

本文深入探讨了SpringMVC如何利用Servlet3.0的SPI机制替代web.xml配置,通过实现WebApplicationInitializer接口,以编程方式配置ServletContext,实现与容器的交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在打开springMVC官方文档的开头介绍DispatcherServlet的时候,他首先介绍了一段配置:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

然后后面又列出了一段web.xml的配置:

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

最后说,下面的这一段配置,可以使用上面的那段java代码来进行代替。

以下tomcat代表web容器,不是特指tomcat,其他web容器也如此。

思考:web.xml是我们配置给tomcat的,然后tomcat会读取web.xml获取相应的配置,然后进行他自己的一堆操作。
而如今现在springMVC说能够通过上面那段代码来进行代替,那么我们是如何通过将自己的配置交给tomcat容器的呢?
而且这是springMVC单独的项目,不是和springBoot整合的,他不存在内嵌Tomcat,那么就深入源码来查看一波。

第一步:既然他代替了web.xml的配置,那么一定是他和tomcat容器
首先分析他给的java代码,发现他让我们实现一个叫做WebApplicationInitializer的接口,
org.springframework.web.WebApplicationInitializer
看这个接口,还是属于spring的,并且他也没有继承其他的接口,和tomcat的关系还是没有找到。
然后看此接口上面的注释:
大概意思是,此接口将在Servlet 3.0+环境中实现,以编程的方式来配置ServletContext。
然后想到Servelt3.0的SPI机制。

SPI ( Service Provider Interface),是JDK提供的一种服务发现机制。可发现并自动加载在ClassPath下的jar包中META-INF/services文件下以服务接口命名的文件内的全限定类名映射的类。当服务的提供者,提供了服务接口的一种实现之后,只需在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件即可被程序加载并使用。

这个根据个人的理解来说一下tomcat下的这个SPI扩展的使用:
1、在项目的跟目录:也就是我们的resources目录下,创建META-INF/services目录,
2、在其下面创建javax.servlet.ServletContainerInitializer文件,
3、文件内容就为我们自定实现了javax.servlet.ServletContainerInitializer接口的实现类
4、容器启动会调用所有实现了ServletContainerInitializer接口的onStartup方法。

HandlesTypes:注解作用:web容器会扫扫描其注解中的value值的所有Class数组所对应的实现类
然后通过参数的方式传入到{@link org.springframework.web.SpringServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)}方法,这种扩展就是为了能够有更多的类能够在容器启动的时候进行初始化。

然后我们在spring-web项目下面找到了关于SPI的配置:
在这里插入图片描述

里面的内容如下:

org.springframework.web.SpringServletContainerInitializer

然后我们找到他:

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@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);
		// 这里就会循环的调用其自定义的onStartup方法。
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

@HandlesTypes注解可以指定多个Class对象:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
    Class<?>[] value();
}

它的作用就是:容器启动的时候,会通过ServiceLoader去加载value里面的所有接口或者抽象类的实现类Class,并且会将值通过参数的形式传递给onStartup的webAppInitializerClasses参数。

然后看springmvc的onStartup方法实现:
首先是将所有的Class进行实例化,然后在循环的调用其对应的onStartup方法,并且将ServletContext传递给子类。
所以这里就可以解释官网提供的那段通过编程的方式进行web.xml配置了。

原始web.xml配置,tomcat容器加载web.xml并且也是将读取到的值放入ServeltContext对象中的,
这里通过函数回调的方式来进行编码的方式向ServletContext中添加Servlet获取其他比如过滤器等
最终的效果是一样的。

这里列出servletContext常用的API

		servletContext.addFilter()// 添加过滤器
		servletContext.addServlet() // 添加Servlet
		servletContext.addListener(); // 添加监听器
		servletContext.setInitParameter() // 添加初始化参数
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值