分以下幾步來理解:
我們知道,在 Web 工程的 /WEB-INF/ 目錄下面,我們有兩個配置文件
Web.xml 和 Struts-config.xml.
一般情況下就只有一個 web.xml 文件,如果系統(tǒng)是用了 struts 技術(shù)的話,那么就會有 struts-config.xml 文件。
在 Web.xml 中有這么一段代碼:
< servlet >
< servlet-name > action </ servlet-name >
< servlet-class > org.apache.struts.action.ActionServlet </ servlet-class >
< init-param >
< param-name > config </ param-name >
< param-value > /WEB-INF/struts-config.xml </ param-value >
</ init-param >
< init-param >
< param-name > debug </ param-name >
< param-value > 3 </ param-value >
</ init-param >
< init-param >
< param-name > detail </ param-name >
< param-value > 3 </ param-value >
</ init-param >
< load-on-startup > 0 </ load-on-startup >
</ servlet >
< servlet >
< servlet-name > startThread </ servlet-name >
< servlet-class > com.cgogo.ypindex.StartThread </ servlet-class >
< init-param >
< param-name > startParam </ param-name >
< param-value > 2 </ param-value >
</ init-param >
< load-on-startup > 1 </ load-on-startup >
</ servlet >
< servlet-mapping >
< servlet-name > action </ servlet-name >
< url-pattern > *.do </ url-pattern >
</ servlet-mapping >
在 web.xml 中會配置 struts 的中央控制器類。 Web.xml 配置文件的初始化是由容器來實現(xiàn)載入和初始化的。如果你使用的是 tomcat 的話,那么就是由 tomcat 在啟動的時候會載入此 web.xml 文件,也就為此 web 應(yīng)用創(chuàng)建了一個 web Context, 此 context 也就是你 Web 應(yīng)用的訪問入口。
載入中央控制器 org.apache.struts.action.ActionServlet ,這是一個 Servlet, 那么,其就要進行 servlet 的初始化操作。此 action 是如何對 Struts 系統(tǒng)進行初始化的呢?
看一下 ActionServlet 的源代碼:
我們知道,在一個 Servlet 中,在其啟動的時候,首先要執(zhí)行 init() 方法,那么我們先來看一下 actionServlet 的 init() 方法的源代碼:
/**
* <p>Initialize this servlet. Most of the processing has been factored into
* support methods so that you can override particular functionality at a
* fairly granular level.</p>
*
* @exception ServletException if we cannot configure ourselves correctly
*/
public void init() throws ServletException {
// Wraps the entire initialization in a try/catch to better handle
// unexpected exceptions and errors to provide better feedback
// to the developer
try {
(1) initInternal();
(2) initOther();
(3) initServlet();
getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
(4) initModuleConfigFactory();
// Initialize modules as needed
ModuleConfig moduleConfig = initModuleConfig("", config);
(5) initModuleMessageResources(moduleConfig);
(6) initModuleDataSources(moduleConfig);
(7) initModulePlugIns(moduleConfig);
moduleConfig.freeze();
(8) 初始化配置參數(shù)
Enumeration names = getServletConfig().getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (!name.startsWith("config/")) {
continue;
}
String prefix = name.substring(6);
moduleConfig = initModuleConfig
(prefix, getServletConfig().getInitParameter(name));
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
}
this.initModulePrefixes(this.getServletContext());
this.destroyConfigDigester();
} catch (UnavailableException ex) {
throw ex;
} catch (Throwable t) {
// The follow error message is not retrieved from internal message
// resources as they may not have been able to have been
// initialized
log.error("Unable to initialize Struts ActionServlet due to an "
+ "unexpected exception or error thrown, so marking the "
+ "servlet as unavailable. Most likely, this is due to an "
+ "incorrect or missing library dependency.", t);
throw new UnavailableException(t.getMessage());
}
}
先來看一下初始化( 1 )的部分 initInternal() 。
initInternal() 就是實現(xiàn)內(nèi)部資源文件的初始化的,也就是轉(zhuǎn)為 Struts 系統(tǒng)本身提供的以下錯誤信息提示等文字的國際化實現(xiàn)的。我們看一下源代碼是如何實現(xiàn)的:
/**
* <p> Initialize our internal MessageResources bundle. </p>
*
* @exception ServletException if we cannot initialize these resources
*/
protected void initInternal() throws ServletException {
// : FIXME : Document UnavailableException
try {
internal = MessageResources.getMessageResources( internalName );
} catch (MissingResourceException e) {
log.error( "Cannot load internal resources from ‘" + internalName + "‘" ,
e);
throw new UnavailableException
( "Cannot load internal resources from ‘" + internalName + "‘" );
}
}
Struts 根據(jù)配置文件的名字得到一個資源文件。
internalName 就是內(nèi)部資源文件的名稱,其在 actionServlet 中定義:
/**
* <p> The Java base name of our internal resources. </p>
* @since Struts 1.1
*/
protected String internalName = "org.apache.struts.action.ActionResources" ;
取得后的對象是一個 MessageResources 的對象,保存在 internal 中,
/**
* <p>The resources object for our internal resources.</p>
*/
protected MessageResources internal = null;
經(jīng)過此初始化后, internal 就不在是 null 了。
至此就實現(xiàn)完成了內(nèi)部資源文件的初始化。如果出現(xiàn)了異常的話,那么系統(tǒng)就捕捉到。
然后,系統(tǒng)就開始初始化其它的配置,即( 2 ) initOther();
/**
* <p>Initialize other global characteristics of the controller servlet.</p>
*
* @exception ServletException if we cannot initialize these resources
*/
protected void initOther() throws ServletException {
String value = null;
value = getServletConfig().getInitParameter("config");
if (value != null) {
config = value;
}
// Backwards compatibility for form beans of Java wrapper classes
// Set to true for strict Struts 1.0 compatibility
value = getServletConfig().getInitParameter("convertNull");
if ("true".equalsIgnoreCase(value)
|| "yes".equalsIgnoreCase(value)
|| "on".equalsIgnoreCase(value)
|| "y".equalsIgnoreCase(value)
|| "1".equalsIgnoreCase(value)) {
convertNull = true;
}
if (convertNull) {
ConvertUtils.deregister();
ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
ConvertUtils.register(new BigIntegerConverter(null), BigInteger.class);
ConvertUtils.register(new BooleanConverter(null), Boolean.class);
ConvertUtils.register(new ByteConverter(null), Byte.class);
ConvertUtils.register(new CharacterConverter(null), Character.class);
ConvertUtils.register(new DoubleConverter(null), Double.class);
ConvertUtils.register(new FloatConverter(null), Float.class);
ConvertUtils.register(new IntegerConverter(null), Integer.class);
ConvertUtils.register(new LongConverter(null), Long.class);
ConvertUtils.register(new ShortConverter(null), Short.class);
}
}
然后就執(zhí)行( 3 ) initServlet();
這個初始化主要是初始化 servlet 的,哪些 servlet 呢?就是我們在 web.xml 中配置的那些需要在 web application 初始化時就栽入系統(tǒng)的 servlet
這是一個復(fù)雜的過程:
我的理解:
這部分代碼就執(zhí)行一次,僅在初始化的時候執(zhí)行一次。
/**
* <p>Initialize the servlet mapping under which our controller servlet
* is being accessed. This will be used in the <code>&html:form></code>
* tag to generate correct destination URLs for form submissions.</p>
*
* @throws ServletException if error happens while scanning web.xml
*/
protected void initServlet() throws ServletException {
// Remember our servlet name
// 這里保存當(dāng)前的servlet名字,保存在actionServlet的servletName屬性中
this.servletName = getServletConfig().getServletName();
// Prepare a Digester to scan the web application deployment descriptor
Digester digester = new Digester();
digester.push(this);
digester.setNamespaceAware(true);
digester.setValidating(false);
// Register our local copy of the DTDs that we can find
for (int i = 0; i < registrations.length; i += 2) {
URL url = this.getClass().getResource(registrations[i+1]);
if (url != null) {
digester.register(registrations[i], url.toString());
}
}
// Configure the processing rules that we need
digester.addCallMethod("web-app/servlet-mapping",
"addServletMapping", 2);
digester.addCallParam("web-app/servlet-mapping/servlet-name", 0);
digester.addCallParam("web-app/servlet-mapping/url-pattern", 1);
// Process the web application deployment descriptor
if (log.isDebugEnabled()) {
log.debug("Scanning web.xml for controller servlet mapping");
}
// 取得當(dāng)前的配置文件
InputStream input =
getServletContext().getResourceAsStream("/WEB-INF/web.xml");
if (input == null) {
log.error(internal.getMessage("configWebXml"));
throw new ServletException(internal.getMessage("configWebXml"));
}
try {
// 解析當(dāng)前配置文件
digester.parse(input);
} catch (IOException e) {
log.error(internal.getMessage("configWebXml"), e);
throw new ServletException(e);
} catch (SAXException e) {
log.error(internal.getMessage("configWebXml"), e);
throw new ServletException(e);
} finally {
try {
// 解析完畢,關(guān)閉輸入
input.close();
} catch (IOException e) {
log.error(internal.getMessage("configWebXml"), e);
/**
如果有異常,當(dāng)前部進行處理,而是留給他的調(diào)用者來處理。其實是當(dāng)前的調(diào)用部分沒有處理的能力。我們可以這樣理解,假設(shè)你想在出現(xiàn)了這類異常異常的地方給用戶一個提示,但是在我們封裝功能實現(xiàn)的時候,我們并不知道誰會來調(diào)用,所以我們只有把異常拋出,讓調(diào)用者自己去處理。
這一點也許不太好理解,不過,如果理解了,可能你就能夠靈活的使用異常了。
*/
throw new ServletException(e);
}
}
// Record a servlet context attribute (if appropriate)
if (log.isDebugEnabled()) {
log.debug("Mapping for servlet ‘" + servletName + "‘ = ‘" +
servletMapping + "‘");
}
if (servletMapping != null) {
getServletContext().setAttribute(Globals.SERVLET_KEY, servletMapping);
}
}
getServletContext().setAttribute( Globals . ACTION_SERVLET_KEY , this );
initModuleConfigFactory();
// Initialize modules as needed
ModuleConfig moduleConfig = initModuleConfig( "" , config );
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
Enumeration names = getServletConfig().getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (!name.startsWith("config/")) {
continue;
}
String prefix = name.substring(6);
moduleConfig = initModuleConfig
(prefix, getServletConfig().getInitParameter(name));
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
}
this .initModulePrefixes( this .getServletContext());
this .destroyConfigDigester();
以上就是Struts的初始化流程。
/**
* <p>Initialize the factory used to create the module configuration.</p>
* @since Struts 1.2
*/
protected void initModuleConfigFactory(){
/**
這個部分的代碼就是取得參數(shù)。
這個參數(shù)你可以自己擴展你的模塊實現(xiàn)工廠。但是一般都沒有自己去做。
所以一般都使用的默認的工廠初始化配置。
*/
String configFactory = getServletConfig().getInitParameter("configFactory");
/**
下面的代碼,只有你做了自己的配置才會有效。否則一般是不執(zhí)行的。
*/
if (configFactory != null) {
/**
設(shè)置此工廠,并把其參數(shù)存入到 ModuleConfigFactory.factoryClass 屬性中。
此部分可以看 ModuleConfigFactory 的代碼。 ModuleConfigFactory 是一個
*/
ModuleConfigFactory.setFactoryClass(configFactory);
}
}
// 調(diào)用的部分
protected String config = "/WEB-INF/struts-config.xml";
------------------------------------------------------------------
ModuleConfig moduleConfig = initModuleConfig("", config);
initModuleMessageResources(moduleConfig);
// 實現(xiàn)的部分:
protected void initModuleMessageResources (ModuleConfig config)
throws ServletException {
/**
在 struts-config.xml 中的資源文件配置,你可能配置了多個資源,所以此處取得是一個數(shù)組
*/
MessageResourcesConfig mrcs[] = config.findMessageResourcesConfigs();
for (int i = 0; i < mrcs.length; i++) {
if ((mrcs[i].getFactory() == null)
|| (mrcs[i].getParameter() == null)) {
continue;
}
if (log.isDebugEnabled()) {
log.debug(
"Initializing module path ‘"
+ config.getPrefix()
+ "‘ message resources from ‘"
+ mrcs[i].getParameter()
+ "‘");
}
/**
protected String factory =
"org.apache.struts.util.PropertyMessageResourcesFactory";
就是返回的這個值,如果你沒有做其他的設(shè)置的話。
一般情況下,我們都用得是默認的
*/
String factory = mrcs[i].getFactory();
/**
此處對每一個資源配置文件都回去創(chuàng)建一個工廠
*/
MessageResourcesFactory.setFactoryClass(factory);
MessageResourcesFactory factoryObject =
MessageResourcesFactory.createFactory();
factoryObject.setConfig(mrcs[i]);
MessageResources resources =
factoryObject.createResources(mrcs[i].getParameter());
resources.setReturnNull(mrcs[i].getNull());
resources.setEscape(mrcs[i].isEscape());
/**
這一部分非常重要。
我們之所以能夠直接調(diào)用,就是因為,初始化后,我們就把此 resources 放到了其對應(yīng)的當(dāng)前應(yīng)用的屬性值里面了。之后我們就可以直接調(diào)用了。
*/
getServletContext().setAttribute(
mrcs[i].getKey() + config.getPrefix(),
resources);
}
}
==========================================================
// 調(diào)用部分
initModuleDataSources(moduleConfig);
// 實現(xiàn)部分:
/**
* <p>Initialize the data sources for the specified module.</p>
*
* @param config ModuleConfig information for this module
*
* @exception ServletException if initialization cannot be performed
* @since Struts 1.1
*/
protected void initModuleDataSources(ModuleConfig config) throws ServletException {
// :FIXME: Document UnavailableException?
if (log.isDebugEnabled()) {
log.debug("Initializing module path ‘" + config.getPrefix() +
"‘ data sources");
}
ServletContextWriter scw =
new ServletContextWriter(getServletContext());
/**
因為你可能配置了多個數(shù)據(jù)源,所以此處返回的是一個數(shù)組
*/
DataSourceConfig dscs[] = config.findDataSourceConfigs();
// 處理沒有配置數(shù)據(jù)源的情況
if (dscs == null) {
dscs = new DataSourceConfig[0];
}
/**
/**
* <p>The JDBC data sources that has been configured for this module,
* if any, keyed by the servlet context attribute under which they are
* stored.</p>
*/
protected FastHashMap dataSources = new FastHashMap();
這是一個加工過的 HashMap ,又不同的工作模式
*/
dataSources.setFast(false);
for (int i = 0; i < dscs.length; i++) {
if (log.isDebugEnabled()) {
log.debug("Initializing module path ‘" + config.getPrefix() +
"‘ data source ‘" + dscs[i].getKey() + "‘");
}
DataSource ds = null;
try {
/**
*/
ds = (DataSource)
RequestUtils.applicationInstance(dscs[i].getType());
BeanUtils.populate(ds, dscs[i].getProperties());
ds.setLogWriter(scw);
} catch (Exception e) {
log.error(internal.getMessage("dataSource.init", dscs[i].getKey()), e);
throw new UnavailableException
(internal.getMessage("dataSource.init", dscs[i].getKey()));
}
/**
這一個部分很重要。
把初始化后的數(shù)據(jù)源放入到 servlet 的屬性中,所以我們才可以通過 struts 的對應(yīng)屬性直接訪問。
protected String key = Globals.DATA_SOURCE_KEY;
所以,我們可以通過 Globals.DATA_SOURCE_KEY 屬性的值來取得其配制后的數(shù)據(jù)源。
如果多個的話,可以通過數(shù)據(jù)源的參數(shù) ID 來配置和調(diào)用。
*/
getServletContext().setAttribute
(dscs[i].getKey() + config.getPrefix(), ds);
dataSources.put(dscs[i].getKey(), ds);
}
dataSources.setFast(true);
}
至此,Struts系統(tǒng)初始化完畢。