Spring扩展之ServletContainerInitializer

1 介绍

       严格讲ServletContainerInitializer(javax.servlet.ServletContainerInitializer)不是Spring的扩展点,而是web容器(jetty、tomcat)的扩展点,Springboot是基于这个扩展点来实现Servlet启动的。

      ServletContainerInitializer从Serlet3.0开始支持,它是web容器在启动阶段提供给组件(这里将脱离于web容器的第三方框架统称组件,如Spring框架)的一个Hook点,组件可以在这里做初始化的工作(注册servlet、filter、listener)。借助ServletContainerInitializer web应用可以无xml(当然还有别的机制实现无xml,例如注解@WebServlet也可以注册servlet)。

      组件如何使用ServletContainerInitializer?Servlet3.0规范要求组件必须在其jar包的META-INF/services目录中创建一个叫做 javax.servlet.ServletContainerInitializer 的文件,文件中指定ServletContainerInitializer的实现类。那么web容器在启动时就会通过jar services API发现并加载,调用其onStartup方法(如何调用?可以赏析jetty源码)。

      说到ServletContainerInitializer,不得不说注解@HandlesTypes(javax.servlet.annotation.HandlesTypes)。ServletContainerInitializer.onStartup方法的第一个参数“Set<Class<?>> c”就是来自于@HandlesTypes。@HandlesTypes指定一些类(可以是注解类),所有继承/实现或被注解的类拼成一个Set传给ServletContainerInitializer。

2 应用

      因为ServletContainerInitializer的特点,可以基于它做一些组件。

2.1 在Spring中应用

      目前Spring框架用到这个机制并进一步提供自己扩展点WebApplicationInitializer(org.springframework.web.WebApplicationInitializer)给web应用开发更多的自由。

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@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) {
				// 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) 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);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

2.2 在 logback中应用

      logback日志工具在高版本也是用ServletContainerInitializer避免在web.xml配置listener,使日志接入更清爽。

package ch.qos.logback.classic.servlet;

import static ch.qos.logback.core.CoreConstants.DISABLE_SERVLET_CONTAINER_INITIALIZER_KEY;

import java.util.Set;

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

import ch.qos.logback.classic.util.StatusViaSLF4JLoggerFactory;
import ch.qos.logback.core.util.OptionHelper;

public class LogbackServletContainerInitializer implements ServletContainerInitializer {

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

        if (isDisabledByConfiguration(ctx)) {
            StatusViaSLF4JLoggerFactory.addInfo("Due to deployment instructions will NOT register an instance of " + LogbackServletContextListener.class
                            + " to the current web-app", this);

            return;
        }

        StatusViaSLF4JLoggerFactory.addInfo("Adding an instance of  " + LogbackServletContextListener.class + " to the current web-app", this);
        LogbackServletContextListener lscl = new LogbackServletContextListener();
        ctx.addListener(lscl);
    }

   
    boolean isDisabledByConfiguration(ServletContext ctx) {
        ...
    }

}

2.3 其它应用场景

  1. 实现一个轻量级web service框架

  2. 做一些独立的监控框架、诊断工具

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章