234390216 阅读(15) 评论(0)

通过ServletContainerInitializer注册Servlet对象

Servlet3通过SPI的机制允许我们自定义一个ServletContainerInitializer的实现类,Servlet容器会在启动的时候自动调用实现类的onStartup方法,我们可以在该方法中进行一些Servlet对象的注册。ServletContainerInitializer接口的定义如下:

public interface ServletContainerInitializer {

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

在下面的代码中自定义了一个ServletContainerInitializer的实现类,叫MyServletContainerInitialier。在其onStartup方法中我们通过入参ServletContext分别注册了一个Servlet、一个监听器和一个Filter。

public class MyServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        //注册Servlet
        javax.servlet.ServletRegistration.Dynamic servletRegistration = ctx.addServlet("hello", HelloServlet.class);
        servletRegistration.addMapping("/servlet3/hello");
        servletRegistration.setLoadOnStartup(1);
        servletRegistration.setAsyncSupported(true);
        
        //注册监听器
        ctx.addListener(StartupListener.class);
        
        //注册Filter
        javax.servlet.FilterRegistration.Dynamic filterRegistration = ctx.addFilter("hello", HelloFilter.class);
        filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/servlet3/*");
    }

}

它是基于SPI发现的,这需要我们在classpath下面的META-INF/services下新建一个名为javax.servlet.ServletContainerInitializer的文件,然后在里面加上我们自定义的ServletContainerIntializer的全路径名称。如果有多个实现类,每一个实现类写一行。

HandlesTypes

在上面的示例中我们在onStartup的实现中完全没有使用第一个参数classes,它是干什么用的呢?Servlet3允许我们在定义自定义的ServletContainerInitializer的时候通过@HandlesTypes注解指定在自定义的ServletContainerInitializer初始化的时候,也就是调用onStartup时需要自动检测的类型,然后把它们作为onStartup的第一个参数。这在我们搭建框架的时候非常有用,我们可以通过它对外定义一个接口,让有需要通过它来动态注册Servlet对象的人都可以只需要实现我们开放的接口来注册Servlet对象,他们不需要实现完整的ServletContainerInitializer接口,并按照SPI规范在javax.servlet.ServletContainerInitailizer文件中定义。

下面示例中我们通过@HandlesTypes(WebInitializer.class)指定了在容器初始化的时候需要自动检测WebInitializer类型的Class,然后在onStartup方法体中利用反射一一实例化WebInitializer接口实现类,并调用它们的onStartup方法进行Servlet对象的注册。

@HandlesTypes(WebInitializer.class)
public class MyServletContainerInitializer2 implements ServletContainerInitializer {

	@Override
	public void onStartup(Set<Class<?>> classes, ServletContext ctx) 
			throws ServletException {
		// classes就是自动检测到的类路径下的该初始化类上的@HandlesTypes指定的类,
		// 在本示例中就是WebInitializer接口的实现类

		if (classes != null && !classes.isEmpty()) {
			for (Class<?> initializerClass : classes) {
				if (!initializerClass.isInterface() 
						&& !Modifier.isAbstract(initializerClass.getModifiers())) {
					WebInitializer initializer;
					try {
						initializer = (WebInitializer) initializerClass.newInstance();
						initializer.onStartup(ctx);
					} catch (InstantiationException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

}



public interface WebInitializer {

    void onStartup(ServletContext ctx) throws ServletException; 
    
}

(注:本文是基于Servlet3.1所写,由Elim写于2018年6月23日)