Jetty对ServletContainerInitializer的支持与Spring的应用

Posted 黄智霖-blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetty对ServletContainerInitializer的支持与Spring的应用相关的知识,希望对你有一定的参考价值。

目录

关于ServletContainerInitializer和@HandlesTypes

  ServletContainerInitializer是一个定义在javax.servlet包中的接口,根据servlet规范,框架提供的 ServletContainerInitializer实现必须绑定在 jar包的META-INF/services目录中的一个叫做 javax.servlet.ServletContainerInitializer的文件,在其中指定ServletContainerInitializer的实现。容器启动时会根据规则寻找该文件,找到对应配置的ServletContainerInitializer实现类。并且当应用正在启动的时候,在任何的ServletListener的事件被触发之前,ServletContainerInitializer的onStartup方法将被调用:

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

  注解HandlesTypes用于ServletContainerInitializer,在ServletContainerInitializer实现上的HandlesTypes 注解用于表示它感兴趣的一些类,HandlesTypes的value是一个Class数组,可以表示一个ServletContainerInitializer对多个类感兴趣。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes 
    Class<?>[] value();

  这些感兴趣的类会以Set的结构当做入参传入其onStartup方法中。

Jetty支持

jetty仓库地址:https://github.com/eclipse/jetty.project

  进入org.eclipse.jetty.annotations.AnnotationConfiguration类,找到其configure方法:

public void configure(WebAppContext context) throws Exception
	......
	//创建ServletContainerInitializer
	createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
	if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
			//会通过线程池并发扫描jar包收集class
           scanForAnnotations(context);  
	......
	//上面createServletContainerInitializerAnnotationHandlers会将获取的ServletContainerInitializer和
	//对应HandlesTypes注解配置的类包装成ContainerInitializer,最终存入context中
	//这里取出来遍历调用其resolveClasses方法完成解析
	//这里还涉及到org.eclipse.jetty.classInheritanceMap配置,不是我们的关注点
	List<ContainerInitializer> initializers = 
                   (List<ContainerInitializer>)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
       if (initializers != null && initializers.size()>0)
       
       		//在上面scanForAnnotations方法中完成数据填充
           Map<String, Set<String>> map = ( Map<String, Set<String>>) context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
           for (ContainerInitializer i : initializers)
           			//处理感兴趣的类,主要是根据Class获取对应的实现类名,保存下来
                   i.resolveClasses(context,map);
       

  其中会调用getNonExcludedInitializers()方法获取ServletContainerInitializer列表,如果指定了顺序,还会对ServletContainerInitializer列表进行排序(代码经过了精简和改写,只保留了部分逻辑):

public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context) throws Exception
	ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
	//通过ServiceLoader加载并实例化ServletContainerInitializer
	ServiceLoader<ServletContainerInitializer> loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
	//获取排序配置(org.eclipse.jetty.containerInitializerOrder)
	ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
	//迭代
	for (ServletContainerInitializer sci:loadedInitializers)
		//存入 sciResourceMap      
		sciResourceMap.put(sci, sciResource);
	
	//假设根据containerInitializerOrder排序
	nonExcludedInitializers.addAll(sciResourceMap.keySet());
	//
	Collections.sort(nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
	//返回ServletContainerInitializer实现集合
	return nonExcludedInitializers;

  在通过getNonExcludedInitializers方法获取到ServletContainerInitializer的集合之后,会将其当做入参传入createServletContainerInitializerAnnotationHandlers方法,完成HandlesTypes注解的解析和ServletContainerInitializer的包装工作:

public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis) throws Exception 
	//ServletContainerInitializer会被包装为ContainerInitializer
	List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
	//将initializers 存入context,后续在ServletContainerInitializersStarter取出来使用
	context.setAttribute(CONTAINER_INITIALIZERS, initializers);
	for (ServletContainerInitializer service : scis)
        
			//获取HandlesTypes注解
            HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class);
            ContainerInitializer initializer = null;
            if (annotation != null)
                
				//获取HandlesTypes配置的感兴趣的类
                Class<?>[] classes = annotation.value();
                if (classes != null)
                
					//包装成ContainerInitializer
                    initializer = new ContainerInitializer(service, classes);
                    if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
                    
                    	ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ClassInheritanceMap();
                        context.setAttribute(CLASS_INHERITANCE_MAP, map);
                        //后续会在AnnotationConfiguration.configure方法中解析
                        _classInheritanceHandler = new ClassInheritanceHandler(map);
                    
                
                else
                
                    initializer = new ContainerInitializer(service, null);
                    if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass());
                
            
            else
            
                initializer = new ContainerInitializer(service, null);
            
            
            initializers.add(initializer);
        
	ServletContainerInitializersStarter starter =(ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
	if (starter != null)
		throw new IllegalStateException("ServletContainerInitializersStarter already exists");
	//创建ServletContainerInitializersStarter
	starter = new ServletContainerInitializersStarter(context);
	context.setAttribute(CONTAINER_INITIALIZER_STARTER, starter);
	//添加ServletContainerInitializersStarter
	context.addBean(starter, true);

  可以看到ServletContainerInitializer被包装成了ContainerInitializer ,并且存入了context中,key为org.eclipse.jetty.containerInitializers。之后在AnnotationConfiguration.configure方法的最后会取出ContainerInitializer列表,然后遍历调用resolveClasses方法处理感兴趣的类,这里只需要关注会将这些类名存入Set中。
  同时还创建了ServletContainerInitializersStarter交给context管理,这个starter继承了AbstractLifeCycle,具备生命周期,那么我们就直接到ServletContainerInitializersStarter的doStart方法中:

public void doStart()
    
    	//从context中根据org.eclipse.jetty.containerInitializers参数将ContainerInitializer列表取出来
        List<ContainerInitializer> initializers = (List<ContainerInitializer>)_context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
        if (initializers == null)
            return;
        
        for (ContainerInitializer i : initializers)
        
            try
            
				//遍历调用callStartup
                i.callStartup(_context);
            
            catch (Exception e)
            
                LOG.warn(e);
                throw new RuntimeException(e);
            
        
    

  逻辑很简单,从context中把之前在AnnotationConfiguration中存入的ContainerInitializer列表取出来,然后遍历调用其callStartup方法。来看看ContainerInitializer.callStartup方法的实现:

public void callStartup(WebAppContext context)
    throws Exception
    	
    	//target就是ServletContainerInitializer
        if (_target != null)
        
            Set<Class<?>> classes = new HashSet<Class<?>>();

            ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(context.getClassLoader());
            try
            
                for (String s : _applicableTypeNames)
                	//_applicableTypeNames就是感兴趣的类名列表
                	//在前面调用resolveClasses方法根据_interestedTypes(HandlesTypes注解配置)得到
                    classes.add(Loader.loadClass(context.getClass(), s));

                context.getServletContext().setExtendedListenerTypes(true);
                if (LOG.isDebugEnabled())
                
                    long start = System.nanoTime();
                    //调用ServletContainerInitializer的onStartup方法,将感兴趣的类列表classes传入
                    _target.onStartup(classes, context.getServletContext());
                
                else
                    _target.onStartup(classes, context.getServletContext());
            
            finally
             
                context.getServletContext().setExtendedListenerTypes(false);
                Thread.currentThread().setContextClassLoader(oldLoader);
            
        
    

  到这里就实现了前面描述的关于ServletContainerInitializer和@HandlesTypes的使用规范。

Spring对ServletContainerInitializer的应用

  spring-web中有一个类SpringServletContainerInitializer,它实现了ServletContainerInitializer接口,并且按照规范,将其配置在jar包下面的META-INF/services/javax.servlet.ServletContainerInitializer文件中:

  我们来看看SpringServletContainerInitializer类的实现:

//对WebApplicationInitializer接口感兴趣
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer 
	@Override
	public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException 
		//webAppInitializerClasses就是WebApplicationInitializer接口的实现类
		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 
						//实例化WebApplicationInitializer
						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;
		
		//根据Order注解对initializers进行排序
		AnnotationAwareOrderComparator.sort(initializers);
		servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);

		for (WebApplicationInitializer initializer : initializers) 
			//遍历执行onStartup方法
			initializer.onStartup(servletContext);
		
	


  编程式的servlet、filter等的集成都依赖WebApplicationInitializer实现,通过它可以实现一个web.xml的"替代"方案,而WebApplicationInitializer由依赖SpringServletContainerInitializer,SpringServletContainerInitializer又依赖servlet容器对ServletContainerInitializer相关规范的支持。

以上是关于Jetty对ServletContainerInitializer的支持与Spring的应用的主要内容,如果未能解决你的问题,请参考以下文章

jetty源代码剖析

Jetty 11 未检测到 Jakarta Servlet

Jetty vs. Tomcat

jetty;tomcat;热部署

jetty xml解析

如何配置嵌入式 Jetty 以处理 OPTIONS 预检请求?