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. 做一些獨立的監控框架、診斷工具

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