spring mvc动态注册DispatcherServlet流程分析及模拟

前言

spring mvc动态注册DispatcherServlet的流程是很简单的(抛却实现细节),所以我会以尽量简单的描述来说明spring mvc在摆脱web.xml的情况下,动态的注册DispatcherServlet流程,后面会写一个demo来模拟注册过程。

注册流程

首先,声明2个关键的知识点:

1. 动态注册servlet是servlet3的新特性,即servlet 3.0以后才支持动态注册servlet

在3.0中,ServletContext中也新增了几个接口,标蓝框的忽视,那个是4.0以后才支持

2. javax.servlet.ServletContainerInitializer

这是servlet容器初始化的一个核心接口。

public interface ServletContainerInitializer {

    public void onStartup(Set<Class<?>> c, ServletContext ctx)
        throws ServletException; 
}

它的实现类及子类,当类路径下配置正确的元数据文件,可以在容器初始化的时候自动调用实现类的onStartup方法,另外需要配合注解javax.servlet.annotation.HandlesTypes使用,不明白这几句话的意思的话,后面的示例代码中有使用说明。

既然servlet容器会调用ServletContainerInitializer的onStartup方法,这也是spring mvc动态注册DispatcherServlet的入口,那就看spring mvc对这个接口的关键实现:

//这个注解声明了 WebApplicationInitializer类,
//那么servlet容器会把类路径下所有WebApplicationInitializer的实现类及其子类添加到onStratup方法的第一个参数中
// servlet容器会调用这个类的onStartup方法
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// 将不是接口或抽象类的WebApplicationInitializer实现类及子类实例化
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer) 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;
		}

		AnnotationAwareOrderComparator.sort(initializers);
		servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);
		// 调用上面实例化的所有WebApplicationInitializer实例的onStartup方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

servlet容器会自动调用这个类的onStartup方法,但是首先要告诉servlet容器这个类的存在,上文提到了一个元数据文件,它在spring-web包下,这个文件的要求是这样的:

1. 在类路径的WEB-INF/services目录下

2. 名字是javax.servlet.ServletContainerInitializer

3. 内容是实现类的全路径

如下:

看代码里我加的注释,就知道在servlet容器初始化的时候,会调用所有WebApplicationInitializer实例的onStartup方法,接下来看下WebApplicationInitializer的一个实现类AbstractContextLoaderInitializer的子类的部分代码:AbstractDispatcherServletInitializer

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		registerDispatcherServlet(servletContext);
	}
	// 注册DispatcherServlet
	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return empty or null");

		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext,
				"createServletApplicationContext() did not return an application " +
				"context for servlet [" + servletName + "]");

		DispatcherServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		Assert.notNull(registration,
				"Failed to register servlet with name '" + servletName + "'." +
				"Check if there is another servlet registered under the same name.");

		registration.setLoadOnStartup(1);
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());

		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}
}

到这里DispatcherServlet已经是注册到Servlet容器了。

接下来自己模拟一下这个流程,平常如果需要动态注册servlet也可以考虑这种实现

代码示例

也定义个WebApplicationInitializer接口

package com.xuxd.spring;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

public interface WebApplicationInitializer {

    void onStartup(ServletContext servletContext) throws ServletException;
}

 定义WebApplicationInitializer接口的实现,用来注册servlet

package com.xuxd.spring;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class DispatcherServletInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        registerDispatcherServlet(servletContext);
    }

    protected void registerDispatcherServlet(ServletContext servletContext) throws ServletException {
        DispatcherServlet dispatcherServlet = servletContext.createServlet(DispatcherServlet.class);
        ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
        dynamic.addMapping("/");
        System.out.println("register dispatcherServlet");

    }
}

 spring mvc的DispatcherServlet是spring mvc的核心,负责spring mvc的核心组件的管理及请求分发,这里只是个示例:

package com.xuxd.spring;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import java.io.IOException;
import java.io.PrintWriter;

public class DispatcherServlet extends HttpServlet {

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        res.setContentType("text/plain");
        // 默认采用ISO-8859-1编码
        PrintWriter printWriter = res.getWriter();
        printWriter.print("hello, dispatcherServlet");
    }
}

这里是入口,实现ServletContainerInitializer接口 

package com.xuxd.spring;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();
        if (c != null) {
            for (Class<?> clazz :
                    c) {
                try {
                    initializers.add((WebApplicationInitializer) clazz.newInstance());
                } catch (Exception e) {
                    throw new ServletException(e);
                }
            }
        } else {
            System.out.println("c is null");
        }

        for (WebApplicationInitializer initializer :
                initializers) {
            initializer.onStartup(ctx);
        }
    }
}

 类路径下配置这个文件

我这个工程本来就是一个基本的servlet/jsp工程,pom也没什么依赖,主要是一个servlet api的依赖:

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.0</version>
      <scope>provided</scope>
    </dependency>

配置一下tomcat,运行

浏览器随便发一个请求,响应如下:

正是我的DispatcherServlet返回的内容。

发布了136 篇原创文章 · 获赞 81 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/x763795151/article/details/93204488