Struts2源码粗略分析二:初始化详解
核心配置
对于Java的Web应用来说,启动时会根据web.xml文件来装载相关信息,Struts2的核心配置就是一个Filter,这一点与Struts(版本一核心是一个Servlet)有所区别。
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Struts Blank</display-name> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
在Struts2早期版本,org.apache.struts2.dispatcher.FilterDispatcher,但自2.1.3版本开始该类被org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter取代。OK,转入正题,开始分析整个Struts2的启动流程!
StrutsPrepareAndExecuteFilter的生命周期
先回想一下javax.servlet.Filter的生命周期,首先是初始化的init方法,它会随着应用程序启动而启动;然后是用于服务的doFilter方法,每次请求到达都会走入这个方法;最后是用于资源清理的destory,当应用退出时会,该方法会被调用。整个过程就是:init -> doFilter -> destory 过程,对于 StrutsPrepareAndExecuteFilter 来说也是如此。
StrutsPrepareAndExecuteFilter.init方法分析
在本节我们只对初始化部分进行详细分析,分析过程基本如下图所示,按程序执行顺序来进行分析
下面为init方法的实现,请结合图中第一列分析:
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); try { FilterHostConfig config = new FilterHostConfig(filterConfig); init.initLogging(config); Dispatcher dispatcher = init.initDispatcher(config); init.initStaticContentLoader(config, dispatcher); prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); postInit(dispatcher, filterConfig); } finally { init.cleanup(); } }
我们先看看这个方法涉及到的几个类:InitOperations、FilterHostConfig、Dispatcher、PrepareOperations和ExecuteOperations。其中InitOperations是一个用于初始化操作的工具类,而Filter相关信息则是保存在FilterHostConfig中,之后就用生成的FilterHostConfig来创建Dispatcher对象,这个对象比较重要,因为它与xwork结合非常紧密,可以认为是Struts2与xwork结合的核心组件。PrepareOperations和ExecuteOperations在这里没有什么实质性工作,只是对象实例化,它们真正的作用是在应用程序进行服务时才发挥的,我们留在下一节分析。
接下来逐句分析:
InitOperations init = new InitOperations(); FilterHostConfig config = new FilterHostConfig(filterConfig);
以上两句为对象实例化,没什么特别处理,继续。
init.initLogging(config); /** * Initializes the internal Struts logging */ public void initLogging( HostConfig filterConfig ) { String factoryName = filterConfig.getInitParameter("loggerFactory"); if (factoryName != null) { try { Class cls = ClassLoaderUtils.loadClass(factoryName, this.getClass()); LoggerFactory fac = (LoggerFactory) cls.newInstance(); LoggerFactory.setLoggerFactory(fac); } catch ( InstantiationException e ) { System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default"); e.printStackTrace(); } catch ( IllegalAccessException e ) { System.err.println("Unable to access logger factory: " + factoryName + ", using default"); e.printStackTrace(); } catch ( ClassNotFoundException e ) { System.err.println("Unable to locate logger factory class: " + factoryName + ", using default"); e.printStackTrace(); } } }
从方法注释可以看出,是一个日志初始化功能,如果我们在StrutsPrepareAndExecuteFilter配置了一个名为loggerFactory的参数,值为一个实现了com.opensymphony.xwork2.util.logging.LoggerFactory(抽象类)的类,那Struts2就会使用我们提供的这个日志类进行日志输出,这样我们就可以随意使用各种Log第三方jar包了。
接下来就是重点内容了,createDispatcher方法比较简单,利用Filter中配置的相关参数创建一个Dispatcher对象而已,而Dispatcher.init就开始读取struts.xml
Dispatcher dispatcher = init.initDispatcher(config); /** * Creates and initializes the dispatcher */ public Dispatcher initDispatcher( HostConfig filterConfig ) { Dispatcher dispatcher = createDispatcher(filterConfig); dispatcher.init(); return dispatcher; }
[2]部分就是读取struts.xml,正常情况下只能处理默认的xwork.xml配置文件,Struts2重写了这个方法,引入了对struts.xml的支持。
/** * Load configurations, including both XML and zero-configuration strategies, * and update optional settings, including whether to reload configurations and resource files. */ public void init() { if (configurationManager == null) { configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } try { init_DefaultProperties(); // [1] init_TraditionalXmlConfigurations(); // [2] init_LegacyStrutsProperties(); // [3] init_CustomConfigurationProviders(); // [5] init_FilterInitParameters() ; // [6] init_AliasStandardObjects() ; // [7] Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } } catch (Exception ex) { if (LOG.isErrorEnabled()) LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } }
Dispatcher处理之后,就是关于静态内容处理
init.initStaticContentLoader(config, dispatcher);
在定义web.xml时可以指定哪些存于WEB-INF中的内容可以对外访问的,如果有多个包名需用空格分隔。这个功能对于访问资源保护来说相当不错,WEB-INF下内容是禁止访问的,通过这个功能我们就可以访问classes中内容。
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> <init-param> <param-name>packages</param-name> <param-value>demo</param-value> </init-param> </filter>
最后通过URL http://localhost:8080/struts2/static/readme.html 就可以访问到包 demo 中定义的readme.html文件!
下面这句并没有出现在时序图中,感觉与StaticContentLoader类似,就是让StrutsPrepareAndExecuteFilter放弃处理某些URL,这样就可以直接访问web-app下除WEB-INF以外的内容。否则所有内容都得通过action间接访问,即send request -> action -> JSP。
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
cleanup,这个留到下节说吧,Struts1非线程安全,多少跟这个实现有关。终于结尾了~~~