(同样写于11年12月20日左右的,转导入此)

大家应该都已经知道Spring 3.1对无web.xml式基于代码配置的servlet3.0应用。通过spring的api或是网络上高手们的博文,也一定很快就学会并且加到自己的应用中去了。PS:如果还没,也可以小小参考一下鄙人的上一篇文章<<探 Spring 3.1之无web.xml式 基于代码配置的servlet3.0应用>>。

经过一天的深度research, 我了解,理解以及重现了springframework的那一小段代码。

OK,第一步,入手点,WebApplicationInitializer接口。因为我们只需实现这个接口覆写它的一个方法,就可以做到配置web.xml同样的功效。看它的源码,其实看和不看没什么两样:

Java代码 packageorg.springframework.web; importjavax.servlet.ServletContext; importjavax.servlet.ServletException; publicinterfaceWebApplicationInitializer{ voidonStartup(ServletContextservletContext)throwsServletException; }


就这么点儿,有效代码5行,弄地我一头雾水,就是一个普通接口,声明了一个方法。连注解都没有,server是怎么找到实现了它的类的?如果这样,何不找我定义的其它接口(的实现类完成配置工作)呢。可见现在java的解耦技术,真令人汗颜。


第二步,这个接口旁边(同包)有个SpringServletContainerInitializer, 看下它是何方神圣吧:

Java代码 packageorg.springframework.web; importjava.lang.reflect.Modifier; importjava.util.Collections; importjava.util.LinkedList; importjava.util.List; importjava.util.ServiceLoader; importjava.util.Set; importjavax.servlet.ServletContainerInitializer; importjavax.servlet.ServletContext; importjavax.servlet.ServletException; importjavax.servlet.annotation.HandlesTypes; importorg.springframework.core.annotation.AnnotationAwareOrderComparator; @HandlesTypes(WebApplicationInitializer.class) publicclassSpringServletContainerInitializerimplementsServletContainerInitializer{ publicvoidonStartup(Set<Class<?>>webAppInitializerClasses,ServletContextservletContext) throwsServletException{ List<WebApplicationInitializer>initializers=newLinkedList<WebApplicationInitializer>(); if(webAppInitializerClasses!=null){ for(Class<?>waiClass:webAppInitializerClasses){ //Bedefensive:Someservletcontainersprovideuswithinvalidclasses, //nomatterwhat@HandlesTypessays... if(!waiClass.isInterface()&&!Modifier.isAbstract(waiClass.getModifiers())&&WebApplicationInitializer.class.isAssignableFrom(waiClass)){ try{ initializers.add((WebApplicationInitializer)waiClass.newInstance()); } catch(Throwableex){ thrownewServletException("FailedtoinstantiateWebApplicationInitializerclass",ex); } } } } if(initializers.isEmpty()){ servletContext.log("NoSpringWebApplicationInitializertypesdetectedonclasspath"); return; } Collections.sort(initializers,newAnnotationAwareOrderComparator()); servletContext.log("SpringWebApplicationInitializersdetectedonclasspath:"+initializers); for(WebApplicationInitializerinitializer:initializers){ initializer.onStartup(servletContext); } } }



以上的有效代码28行。刚看时也很迷茫,其实慢慢就理解了。拟个伪代码吧,方便大家理解:
1,定义一个类SpringServletContainerInitializer,并标明该类要操作的一个类WebApplicationInitializer
2, 该类会行使ServletContainerInitializer接口的一个行为onStartup,从而将一个集合中的初始化设置 全部配置到ServletContext的实例中。
3,具体的onStartup方法中,建立合格配置列表,
4,如果确定集合中有配置,逐一检查配置是否是合格配置,具体判断依据:这个类不是接口,不是抽象类,而且是所要操作的那个接口的一个实现类。满足此依据,合格。将合格的配置类实例化放入合格配置列表。过程中有错要通知控制台。
5,如若执行完步骤4,发现没有合格配置,在ServletContext记录该结果,并结束onStartup行为。
6,将找到配置按一定排列方式(AnnotationAwareOrder)排序。
7,在ServletContext中记录找到结果。
8,逐一执行配置。 即驱动每一个WebApplicationInitializer的实现类行使其onStartup行为。

第三步很明显了,去research 接口ServletContainerInitializer和注解HandleType。在这里:http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html

该接口允许一个库或运行时,(运行时应该指server)声明为一个web程序的启动状态,并执行任何所需的程序中注册的servlet,filter,listener来响应它......
其它也就不用看了,可以想象得到支持Servlet3机制的服务器,会找到这样接口的实现类,执行onStartup行为。至于如何找,无非也是这样一系列的反射机制的应用。自己做一个试试吧:
自定义的WebApplicationInitializer:

Java代码 packagecom.xxxxx.p_w_picpathcapture.cfg; importjavax.servlet.ServletContext; importjavax.servlet.ServletException; publicinterfaceWebParameter{ publicvoidloadInfo(ServletContextservletContext)throwsServletException; }



自定义的ServletContainerInitializer,我做得很简单,直接去执行找到配置类中的loadInfo方法

Java代码 packagecom.xxxxx.p_w_picpathcapture.cfg; importjava.lang.reflect.Modifier; importjava.util.Set; importjavax.servlet.ServletContainerInitializer; importjavax.servlet.ServletContext; importjavax.servlet.ServletException; importjavax.servlet.annotation.HandlesTypes; @HandlesTypes(WebParameter.class) publicclassWebConfigurationimplementsServletContainerInitializer{ @Override publicvoidonStartup(Set<Class<?>>webParams,ServletContextservletCtx) throwsServletException{ if(webParams!=null){ for(Class<?>paramClass:webParams){ if(!paramClass.isInterface()&&!Modifier.isAbstract(paramClass.getModifiers())&& WebParameter.class.isAssignableFrom(paramClass)){ try{ ((WebParameter)paramClass.newInstance()).loadInfo(servletCtx); } catch(Throwableex){ thrownewServletException("FailedtoinstantiateWebParamclass",ex); } } }//loop }//WebParams }//onStartup }


写个测试Servlet:

Java代码 packagecom.xxxxx.p_w_picpathcapture.ctrl; importjava.io.IOException; importjavax.servlet.ServletException; importjavax.servlet.http.HttpServlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importcom.gxino.p_w_picpathcapture.cfg.WebParameter; publicclassTestServletextendsHttpServlet{ publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp){ System.out.println("Someclientaccessonce"); try{ req.getRequestDispatcher("/index.jsp").forward(req,resp); }catch(ServletException|IOExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } } }



实现WebParam配置接口来配置刚才的Servlet:

Java代码 packagecom.xxxxx.p_w_picpathcapture.cfg; importjavax.servlet.ServletContext; importjavax.servlet.ServletException; importjavax.servlet.ServletRegistration; publicclassServletParameterimplementsWebParameter{ @Override publicvoidloadInfo(ServletContextservletContext)throwsServletException{ ServletRegistration.DynamictestServlet=servletContext.addServlet("test","com.gxino.p_w_picpathcapture.ctrl.TestServlet"); testServlet.setLoadOnStartup(1); testServlet.addMapping("/index.html"); } }


启动服务器,访问http://localhost:xxxx/xxxxx/index.html

失败。Debug. 发现没有走这些代码。应该还差关键环节。看来还得知道Servlet3中是怎么找ServletContainerInitializer的。再回刚才ServletContainerInitializer的api有这样一句:该接口的实现必须声明一个JAR资源放到程序中的META-INF/services下,并且记有该接口那个实现类的全路径,才会被运行时(server)的查找机制或是其它特定机制找到。那篇api需要仔细阅读啊。


到org.springframework.web-3.0.1.RELEASE.jar中能找到META-INF/services下的javax.servlet.ServletContainerInitializer文件,内容为org.springframework.web.SpringServletContainerInitializer同样,我们专门作这样一个包,在mkdir好的META-INF/services下vi 一个文件命名为javax.servlet.ServletContainerInitializer,内容为自定的那个WebConfiguration的全路径类名。 然后在META-INF的parent路径下运行jar cvf test.jar META-INF。一切完毕,将其放到WEB-INF/lib下。启动。

这回大功告成。

访问http://localhost:xxxx/xxxxx/index.html。页面跳到了index.jsp下。
并且控制台打出: Some client access once

再使个劲,将Servlet和Servlet配置合二为一:

Java代码 packagecom.xxxxx.p_w_picpathcapture.ctrl; importjava.io.IOException; importjavax.servlet.ServletContext; importjavax.servlet.ServletException; importjavax.servlet.ServletRegistration; importjavax.servlet.http.HttpServlet; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importcom.xxxxx.p_w_picpathcapture.cfg.WebParameter; publicclassTestServletextendsHttpServletimplementsWebParameter{ @Override publicvoidloadInfo(ServletContextservletContext)throwsServletException{ ServletRegistration.DynamictestServlet=servletContext.addServlet("test","com.gxino.p_w_picpathcapture.ctrl.TestServlet"); testServlet.setLoadOnStartup(1); testServlet.addMapping("/index.html"); } publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp){ System.out.println("Someclientaccessonce"); try{ req.getRequestDispatcher("/index.jsp").forward(req,resp); }catch(ServletException|IOExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } } }



这回我们看到,配置文件与servlet放到了一起。这样将回节省大量时间。

以后直接运用Spring Framework的WebApplicationInitializer也知道是怎么一回事儿了。而且可以将Spring 的applicationContext.xml与web.xml融合在一个类中。即注解为@Configuration,并实现WebApplicationInitializer.回头试试。