|
+--javax.servlet.GenericServlet
|
+--javax.servlet.http.HttpServlet
|
+--org.apache.struts.action.ActionServlet
Struts提供了一個缺省版本的ActionServlet類,你可以繼承這個類,覆蓋其中的一些方法來達到你的特殊處理的需要。ActionServlet繼承與javax.servlet.http.HttpServlet,所以在本質上它和一個普通的servlet沒有區別,你完全可以把它當做一個servlet來看待,只是在其中完成的功能不同罷了。ActionServlet主要完成如下功能:
將一個來自客戶端的URI映射到一個相應的Action類
- 如果是這個Action類是第一次被調用,那麼實例化一個並放入緩存
- 如果在配置文件(struts-config.xml)中指定了相應的ActionForm,那麼從Request中抓取數據填充FormBean
- 調用這個Action類的perform()方法,傳入ActionMapping的一個引用,對應的ActionForm、以及由容器傳給ActionServlet的HttpServletRequest、HttpServletResponse對象。
確省版本的ActionServlet會從配置文件web.xml中讀取如下初始化參數:
- application
應用使用的資源包(resources bundle)的基類 - factory
用於創建應用的MessageResources對象的MessageResourcesFactory的類名。確省是org.apache.struts.util.PropertyMessageResourcesFactory。 - config
Struts的配置文件,確省是/WEB-INF/struts-config.xml。注意這兒是與應用Context關聯的相對路徑。 - content
定義了確省的內容類型和編碼格式,它會被自動地被設置到每個response中,如果JSP/Servlet中沒有明確的設置。確省是text/html。 - debug
調試信息的級別。默認爲0,比當前級別高的調試信息會被log到日誌文件中。 - detail
與debug的作用類似,只是這個detail是initMapping()時專用的。調試信息會被打印到System.out,而不是日誌文件。 - formBean
ActionFormBean的實現類,確省爲org.apache.struts.action.ActionFormBean - forward
應用中使用的ActionForward類,確省是org.apache.struts.action.ActionForward。 - locale
指定了確省使用的Locale對象。設爲true,當得到一個session時,會自動在session中存儲一個以Action.LOCALE_KEY標示的Locale對象,如果session中還沒有與Action.LOCALE_KEY綁定的Locale對象。 - mapping
應用中使用的ActionMapping類,確省是org.apache.struts.action.ActionMapping。 - multipartClass
文件上傳使用的MutipartRequestHandler的實現類。確省爲org.apache.struts.upload.DiskMultipartRequestHandler - nocache
如果設爲true,那麼ActionServlet會自動在每個到客戶端的響應中添加nocache的HTML頭,這樣客戶端就不會對應用中的頁面進行緩存。確省爲false - null
如果設置爲true,那麼應用在得到一個未定義的message資源時,會返回null,而不是返回一個錯誤信息。確省是true。 - maxFileSize
文件上傳的大小上限,確省爲250M - bufferSize
文件上傳時的緩衝區的大小,確省爲4M - tempDir
設置用於上傳時的臨時目錄。工作目錄會作爲一個Servlet環境(Context)的屬性提供。 - validate
Are we using the new configuration file format?確省爲true。 - validating
在解析配置XML文件是是否進行有效性的驗證。確省爲true
ActionServlet中應用了命令設計模式。
一個Servlet在由容器生成時,首先會調用init()方法進行初始化,在接到一個HTTP請求時,調用相應的方法進行處理;比如GET請求調用doGet()方法,POST請求調用doPost()方法。所以首先看看ActionServlet的init()方法,你就會很清楚爲什麼ActionServlet可以完成這些功能了。
init()
在它的init()方法中,ActionServlet依次調用如下protected的方法完成初始化:
- initActions() - 大家可能還曾有這個疑問:Struts爲什麼可以找到一個請求URI對應的action類呢?答案就在這兒,ActionServlet有一個actions屬性,類型爲org.apache.struts.util.FastHashMap,用於存儲以類的全名爲key的已實例化的Action類。在init()時首先調用的就是initActions()方法,在這個方法中只是簡單的清除map中的所有的名值對,
synchronized (actions) {
actions.setFast(false);
actions.clear();
actions.setFast(true);
}
首先把actions設爲slow模式,這時對FastHashMap的訪問是線程同步的,然後清除actions中的所有的已存在的名/值對,最後再把actions的模式設爲fast。由於FastHashMap是struts在java.util.HashMap的基礎上的一個擴展類,是爲了適應多線程、並且對HashMap的訪問大部分是隻讀的特殊環境的需要。大家知道java.util.HashMap是非線程安全的,所以HashMap一般適用於單線程環境下。org.apache.struts.FastHashMap就是繼承於java.util.HashMap,在其中添加多線程的支持產生的。在fast模式下的工作方式是這樣的:讀取是非線程同步的;寫入時首先克隆當前map,然後在這個克隆上做寫入操做,完成後用這個修改後的克隆版本替換原來的map。那麼在什麼時候會把Actions類添加到這個map中呢?我們已經提到了struts是動態的生成Action類的實例的,在每次ActionServlet接收到一個GET或POST的HTTP請求時,會在這個map中查找對應的Action類的實例,如果不存在,那麼就實例化一個,並放入map中。可見這個actions屬性起到了對Action類實例的緩存的作用。 - initInternal() - 初始化ActionServlet內部使用的資源包MessageResources,使用MessageResources.getMessageResources(internalName)得到 internalName爲"org.apache.struts.action.ActionResources"對應的ActionResources.properties文件。這個資源包主要用於ActionServlet處理過程中的用到的提示信息,這兒不展開討論。
- initDebug() - 從web.xml中讀取本應用的debug級別參數getServletConfig().getInitParameter("debug"),然後賦給debug屬性。
- initApplication()- 初始化應用資源包,並放置入ServletContext中。
String factory =getServletConfig().getInitParameter(“factory”);
String oldFacory = MessageResourcesFactory.getFactoryClass();
if (factory !=null)
MessageResourcesFactory.setFactoryClass(factory);
String value = getServletConfig().getInitParameter("application");
MessageResourcesFactory factoryObject =
MessageResourcesFactory.createFactory();
application = factoryObject.createResources(value);
MessageResourcesFactory.setFactory(oldFactory);
getServletContext().setAttribute(Action.MESSAGES_KEY, application);
說明:文中引用的代碼片斷可能會省略了一些例外檢查等非主線的內容,敬請注意。
首先從配置文件中讀取factory參數,如果這個參數不爲空,那麼就在MessageResourcesFactory中使用這個指定的Factory類;否則,使用默認的工廠類org.apche.struts.util.PropertyMessageResourceFactory。然後調用MessageResourcesFactory的靜態createFactory()方法,生成一個具體的MessageResourceFactory對象(注意:MessageResourcesFactory是抽象類)。這樣就可以調用這個具體的MessageResourceFactory的createResource()方法得到配置文件(web.xml)中定義的資源文件了。
上面的application對象類型爲MessageResources。在web.xml中在配置ActionServlet時可以指定一個特定的工廠類。不能直接MessageResourcesFactory的createResources()方法,因爲這個方法是abstract的。創建factoryObject的過程如下:
MessageResourceFactory factoryObject=
MessageResourcesFactory.createFactory();
application = factoryObject.createResources(value);
<li>initMapping() - 爲應用初始化mapping信息ActionServlet有一個protected的屬性:mapping,封裝了一個ActionMapping的對象集合,以便於管理、查找ActionMapping。mappings是org.apache.struts.action.ActionMappings類的實例。主要有兩個方法:addMapping(ActionMapping mapping)和findMapping(String path)。ActionMapping也是使用上面提到的org.apache.struts.util.FastHashMap類來存儲所有的ActionMapping對象。
mappings.setServlet(this);
……
// Initialize the name of our ActionFormBean implementation class
value = getServletConfig().getInitParameter("formBean");
if (value != null)
formBeanClass = value;
// Initialize the name of our ActionForward implementation class
value = getServletConfig().getInitParameter("forward");
if (value != null)
forwardClass = value;
// Initialize the name of our ActionMapping implementation class
value = getServletConfig().getInitParameter("mapping");
if (value != null)
mappingClass = value;
在initMapping()中,首先鏈接mappings對象到本servlet實例。其實這句話的作用很簡單,在ActionMappings中會有一個ActionServlet類型的屬性,這個屬性就界定了這個ActionMappings對象所屬的ActionServlet。Struts的實現比較靈活,其中的ActionFormBean、ActionForward、ActionMapping類你完全可以使用自己實現的子類,來定製Struts的工作方式。上面的代碼就從配置文件(web.xml)中讀取formBean、forward、mapping參數,這些參數就是你定製的ActionFormBean、ActionForward、ActionMapping類名。
// Initialize the context-relative path to our configuration resources
value = getServletConfig().getInitParameter("config");
if (value != null)
config = value;
// Acquire an input stream to our configuration resource
InputStream input = getServletContext().getResourceAsStream(config);
Digester digester = null;
digester = initDigester(detail);
try {
formBeans.setFast(false);
forwards.setFast(false);
mappings.setFast(false);
digester.parse(input);
mappings.setFast(true);
forwards.setFast(true);
formBeans.setFast(true);
} catch (SAXException e) {
throw new ServletException
(internal.getMessage("configParse", config), e);
} finally {
input.close();
}
從web.xml讀取Struts的配置文件的位置。使用org.apache.struts.digester.Digester解析config參數標示的配置文件,通常爲“/WEB-INF/struts-config.xml”,解析出所有的data-source、form-bean、action-mapping、forward。從上面的程序片斷看到,Digester僅僅調用了一個parse()方法,那麼,Digester是怎樣把解析struts-config.xml文件並把解析的結果form-bean等信息存儲到屬性變量formBeans等中的呢?你可以注意到在調用digester.parse(InputStream)之前,首先調用了initDigester()方法:
Digester digester = new Digester();
digester.push(this);
digester.addObjectCreate("struts-config/action-mappings/action",
mappingClass, "className");
digester.addSetProperties("struts-config/action-mappings/action");
digester.addSetNext("struts-config/action-mappings/action",
"addMapping",
"org.apache.struts.action.ActionMapping");
digester.addSetProperty
("struts-config/action-mappings/action/set-property",
"property", "value");
在這個方法中首先生成一個Digester對象,然後設置解析的規則和回調,如果你對XML、SAX不是很熟,這兒不必糾纏太深。要注意的是addSetNext()方法,設置了每一個要解析元素的Set Next回調方法,而這個方法就是由digester解析器的父提供的。上面的片斷中的“addMapping”就是ActionServlet本身定義的一個方法,將由Digester回調。Digester就是籍此把解析出的每一個FormBean、ActionForward、ActionMapping等存儲到屬性變量formBeans、forwards、mappings等中的。 - initUpload() - 初始化有關Upload的一些參數,比如:bufferSize、tempDir。
- initDataSource() -取出在initMapping()中從配置文件中讀取的每一個DataSource,設置LogWriter,如果爲GenericDataSource的實例,則打開數據源。然後,把每個dataSource放入Context中。
dataSource.setLogWriter(scw);
((GenericDataSource)dataSource).open();
getServletContext().setAttribute(key,dataSource); - initOther() - 設置其它尚未初始化的的參數(content、locale、nocache),併發布formBeans、forwards、mappings到Context:
getServletContext().setAttribute(Action.FORM_BEANS_KEY, formBeans);
getServletContext().setAttribute(Action.FORWARDS_KEY, forwards);
getServletContext().setAttribute(Action.MAPPINGS_KEY, mappings); - initServlet() - 初始化Controller Servlet的Servlet Mapping。這兒也使用了Digester工具,掃描web.xml所有的<web-app/servlet-mapping>,尋找servlet-name與當前Servlet相同的mapping,置入Context。代碼如下;
Digester digester = new Digester();
digester.push(this);
digester.setDebug(debug);
digester.setValidating(validating);
digester.addCallMethod(“web-app/servlet-mapping”,“addServletMapping”, 2);
digester.addCallParm(“web-app/servlet-mapping/servlet-name”, 0);
digester.addCallParm(“web-app/servlet-mapping/url-pattern”, 1);
InputStream is = getServletContext().getResourceAsStream(“/WEB-INF/web.xml”);
digester.parse(is);
getServletContext().setAttribute(Action.SERVLET_KEY,servletMapping);