1:servlet2.5时代

servlet 2.5时代,servlet配置在web.xml中,然后web容器通过加载解析web.xml完成诸如servlet,filter,listener等组件加载和初始化,简单来回忆下这个过程,这里以servlet和filter二者为例来进行说明。

1.1:定义servlet

public class HelloWorldServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/plain");
		PrintWriter out = resp.getWriter();
		out.println("hello world from before servlet 3");
	}

}

1.2:定义filter

public class HelloWorldFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("触发 hello world 过滤器 before servlet 3...");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

1.3:在web.xml中配置

<servlet>
	<servlet-name>HelloWorldServlet</servlet-name>
	<servlet-class>dongshi.beforeservlet3.servlet.HelloWorldServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>HelloWorldServlet</servlet-name>
	<url-pattern>/hello</url-pattern>
</servlet-mapping>

<filter>
	<filter-name>HelloWorldFilter</filter-name>
	<filter-class>dongshi.beforeservlet3.filter.HelloWorldFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>HelloWorldFilter</filter-name>
	<url-pattern>/hello</url-pattern>
</filter-mapping>

1.4:启动web服务测试

在这里插入图片描述

2:servlet3.0时代

servlet3.0规范中增加了一个非常重要的接口javax.servlet.ServletContainerInitializer,该接口定义如下:

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

其中onStartUp方法就是我们可以大做文章的地方
我们接着再来看javax.servlet.ServletContext接口,该接口定义了若干个关于动态添加servlet,filter,listener等的方法,servlet相关如下:

public ServletRegistration.Dynamic addServlet(String servletName, String className);
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);
public ServletRegistration.Dynamic addServlet(String servletName, Class <? extends Servlet> servletClass);
public <T extends Servlet> T createServlet(Class<T> clazz) throws ServletException;

filter相关如下:

public FilterRegistration.Dynamic addFilter(
        String filterName, String className);
public FilterRegistration.Dynamic addFilter(
        String filterName, Filter filter);
public FilterRegistration.Dynamic addFilter(String filterName,
        Class <? extends Filter> filterClass);

listener相关如下:

public void addListener(String className);
public <T extends EventListener> void addListener(T t);
public void addListener(Class <? extends EventListener> listenerClass);

下面实例只涉及到servlet和filter,接下来我们需要做的就是实现javax.servlet.ServletContainerInitializer接口,然后通过javax.servlet.ServletContext的相关addXXX的API动态的添加servlet,filter,listener即可。我们所做的这些最终还是需要让web容器知道才可以,使用SPI便可以,具体做法是在classpath下定义多层文件夹META-INF/services,然后创建名称为javax.servlet.ServletContainerInitializer文本文件,将我们实现的javax.servlet.ServletContainerInitializer的实现类的全限定名称,以一行一个的格式配置进去即可。

2.1:定义servlet

public class HelloWorldAfter3Servlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/plain");
		PrintWriter out = resp.getWriter();
		out.println("hello world after servlet 3.0");
	}

}

2.2:定义filter

public class HelloWorldAfter3Filter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("触发 hello world 过滤器 after servlet 3.0...");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

2.3:定义javax.servlet.ServletContainerInitializer实现类

public class CustomServletContainerInitializer implements ServletContainerInitializer  {
	private final static String JAR_HELLO_URL = "/hello111";

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

		System.out.println("创建 helloWorldServlet after servlet 3...");

		ServletRegistration.Dynamic servlet = servletContext.addServlet(
				HelloWorldAfter3Servlet.class.getSimpleName(),
				HelloWorldAfter3Servlet.class);
		servlet.addMapping(JAR_HELLO_URL);

		System.out.println("创建 helloWorldFilter after servlet 3...");

		FilterRegistration.Dynamic filter = servletContext.addFilter(
				HelloWorldAfter3Filter.class.getSimpleName(), HelloWorldAfter3Filter.class);

		EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
		dispatcherTypes.add(DispatcherType.REQUEST);
		dispatcherTypes.add(DispatcherType.FORWARD);

		filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL);

	}
}

2.4:创建META-INF/services/javax.servlet.ServletContainerInitializer

  • 目录结构
    在这里插入图片描述

  • 内容

dongshi.afterservlet3.CustomServletContainerInitializer

2.5:启动web容器

  • 启动过程中加载custom的ServletContainerInitializer
    在这里插入图片描述
  • 测试访问
    在这里插入图片描述

3:HandlesTypes注解

该注解用在javax.servlet.ServletContainerInitializer实现类上,用于定义感兴趣的类型,传入的参数为接口Class数组,web容器在加载时会将感兴趣的类型的所有子类作为参数传递进来,例如定义如下接口:

public interface MyHandlerTypeClass {
}

然后定义两个实现类:

public class MyHandlerTypeClassSon implements MyHandlerTypeClass {
}
public class MyHandlerTypeClassSon2 implements MyHandlerTypeClass {
}

然后在javax.servlet.ServletContainerInitializer实现类定义如下:

@HandlesTypes(value = {MyHandlerTypeClass.class})
public class CustomServletContainerInitializer implements ServletContainerInitializer  {
}

意思就是请web容器在启动时,将MyHandlerTypeClass.class的子类作为参数传递到方法onStartup(Set<Class<?>> c, ServletContext servletContext)c中。debug的话可以看到如下结果:
在这里插入图片描述

4:spring对于servlet3.0的支持

那么spring又是如何支持servlet3.0的呢,毫无疑问,想要使用,spring和我们一样也需要实现javax.servlet.ServletContainerInitializer接口来提供一个具体的实现类,我们可以通过idea来方便的找到这类,肯定是以org.springframework开头的,如下图:
在这里插入图片描述
红框中的就是我们要找的目标spring定义的custom的javax.servlet.ServletContainerInitializer类,也可以看出其实在spring-web模块中的,那么根据规范,肯定是需要定义META-INF/services/javax.servlet.ServletContainerInitializer文件的,然后将org.springframework.web.SpringServletContainerInitializer配置到文件中,确实是有的,如下图:
在这里插入图片描述
具体内容为:

org.springframework.web.SpringServletContainerInitializer

是吧?和我们的猜测是一样的。

4.1:SpringServletContainerInitializer

org.springframework.web.SpringServletContainerInitializer类是spring提供的javax.servlet.ServletContainerInitializer的实现类,实现脱离web.xml对于web相关组件的加载,其源码如下:

// <1>
@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) {
			    // <2>
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
				}
			}
		}
		// <3>
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

<1>处代码,就是告知web容器如tomcat,在调用我的onStartup方法的时候,请将所有的org.springframework.web.WebApplicationContext的子类作为方法参数给我,这里的方法自然就是onStartup,参数就是webAppInitializerClasses<2>处代码是因为有些web服务器厂商并不会按照<1>处的配置进行传递参数,所以这里进行了相关的判断,避免因为web服务器厂商实现问题,导致自己出现问题。<3>处代码是调用具体的初始化器们完成相关web组件的注册的工作。接下来继续看org.springframework.web.WebApplicationInitializer接口。

4.2:WebApplicationInitializer

先看下org.springframework.web.WebApplicationInitializer类结构:
在这里插入图片描述
我们这里重点关注org.springframework.web.servlet.suppport.AbstractDispatherServletInitializer类,在有web.xml情况下,加载springmvc的org.springframework.web.servlet.DispatcherServlet是通过配置<servlet>标签进行配置,可能如下:

<servlet>
	<servlet-name>letsGO</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/letsGO-servlet.xml</param-value>
	</init-param>
	<init-param>
		<param-name>name</param-name>
		<param-value>jack</param-value>
	</init-param>
	<!-- 值越小优先级越高 -->
	<load-on-startup>1</load-on-startup>
</servlet>

那么在没有web.xml的时候,就可以通过该类来完成加载org.springframework.web.servlet.DispathcerServlet的工作了,依赖的方法是org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet,方法核心逻辑大概如下:

// 动态添加DispatcherServlet
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
// 设置高的启动优先级
registration.setLoadOnStartup(1);
// 设置请求映射
registration.addMapping(getServletMappings());
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐