本文轉自http://hitmit1314.iteye.com/blog/1315816
大家應該都已經知道Spring 3.1對無web.xml式基於代碼配置的servlet3.0應用。通過spring的api或是網絡上高手們的博文,也一定很快就學會並且加到自己的應用中去了。PS:如果還沒,也可以小小參考一下鄙人的上一篇文章<<探 Spring 3.1之無web.xml式 基於代碼配置的servlet3.0應用>>。
經過一天的深度research, 我了解,理解以及重現了springframework的那一小段代碼。
OK,第一步,入手點,WebApplicationInitializer接口。因為我們只需實現這個接口覆寫它的一個方法,就可以做到配置web.xml同樣的功效。看它的源碼,其實看和不看沒什么兩樣:
- package org.springframework.web;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- public interface WebApplicationInitializer {
- void onStartup(ServletContext servletContext) throws ServletException;
- }
就這么點兒,有效代碼5行,弄地我一頭霧水,就是一個普通接口,聲明了一個方法。連注解都沒有,server是怎么找到實現了它的類的?如果這樣,何不找我定義的其它接口(的實現類完成配置工作)呢。可見現在java的解耦技術,真令人汗顏。
第二步,這個接口旁邊(同包)有個SpringServletContainerInitializer, 看下它是何方神聖吧:
- package org.springframework.web;
- import java.lang.reflect.Modifier;
- import java.util.Collections;
- 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 {
- 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;
- }
- Collections.sort(initializers, new AnnotationAwareOrderComparator());
- servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);
- for (WebApplicationInitializer initializer : 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:
- package com.gxino.imagecapture.cfg;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- public interface WebParameter {
- public void loadInfo(ServletContext servletContext) throws ServletException;
- }
自定義的ServletContainerInitializer,我做得很簡單,直接去執行找到配置類中的loadInfo方法
- package com.gxino.imagecapture.cfg;
- import java.lang.reflect.Modifier;
- import java.util.Set;
- import javax.servlet.ServletContainerInitializer;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.HandlesTypes;
- @HandlesTypes(WebParameter.class)
- public class WebConfiguration implements ServletContainerInitializer {
- @Override
- public void onStartup(Set<Class<?>> webParams, ServletContext servletCtx)
- throws ServletException {
- 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 (Throwable ex) {
- throw new ServletException("Failed to instantiate WebParam class", ex);
- }
- }
- }//loop
- }//Web Params
- }//onStartup
- }
寫個測試Servlet:
- package com.gxino.imagecapture.ctrl;
- import java.io.IOException;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import com.gxino.imagecapture.cfg.WebParameter;
- public class TestServlet extends HttpServlet {
- public void doGet(HttpServletRequest req, HttpServletResponse resp){
- System.out.println("Some client access once");
- try {
- req.getRequestDispatcher("/index.jsp").forward(req, resp);
- } catch (ServletException | IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
實現WebParam配置接口來配置剛才的Servlet:
- package com.gxino.imagecapture.cfg;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRegistration;
- public class ServletParameter implements WebParameter {
- @Override
- public void loadInfo(ServletContext servletContext) throws ServletException {
- ServletRegistration.Dynamic testServlet=servletContext.addServlet("test","com.gxino.imagecapture.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配置合二為一:
- package com.gxino.imagecapture.ctrl;
- import java.io.IOException;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRegistration;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import com.gxino.imagecapture.cfg.WebParameter;
- public class TestServlet extends HttpServlet implements WebParameter{
- @Override
- public void loadInfo(ServletContext servletContext) throws ServletException {
- ServletRegistration.Dynamic testServlet=servletContext.addServlet("test", "com.gxino.imagecapture.ctrl.TestServlet");
- testServlet.setLoadOnStartup(1);
- testServlet.addMapping("/index.html");
- }
- public void doGet(HttpServletRequest req, HttpServletResponse resp){
- System.out.println("Some client access once");
- try {
- req.getRequestDispatcher("/index.jsp").forward(req, resp);
- } catch (ServletException | IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
這回我們看到,配置文件與servlet放到了一起。這樣將回節省大量時間。
以后直接運用Spring Framework的WebApplicationInitializer也知道是怎么一回事兒了。而且可以將Spring 的applicationContext.xml與web.xml融合在一個類中。即注解為@Configuration,並實現WebApplicationInitializer.回頭試試。