0x00 Struts2的概览
Struts2是一个运行于Web容器的表示层框架,核心作用是帮助我们处理HTTP请求,它的运行环境是Web容器
Struts2通过扩展实现Servlet标准来处理HTTP请求,它的处理流程的代码实现,无论如何封装,都离不开对Servlet标准或者JSP标准所指定的底层API的调用,Struts2只是实现了一个具备通用性的HTTP请求处理机制
首先我们来看一下Struts2和Servlet配置上的区别
web.xml中servlet的配置
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/myservlet</url-pattern>
</servlet-mapping>
struts2中web.xml的配置
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
struts.xml的配置
<struts>
<package name="test" extends="struts-default">
<interceptors>
<interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"></interceptor>
</interceptors>
<action name="hello" class="action.HelloAction" method="execute">
<result name="success">/index.jsp</result>
</action>
</package>
</struts>
可以看到,web.xml中到主要配置从servlet改成了一个filter,这个filter就是struts2主要做到事情,同时,原来到servlet变成了struts.xml
中的action配置,无需配置url的路径,同时增加了result和interceptor
也就是说,原来HTTP请求由servlet来做,由servlet来决定返回,现在由filter来完成这件事情,而filter又抽象出action来处理原来servlet做的事,但是又用result和interceptor进行了简化
0x01 Struts2的程序主线
我们从常用的org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
这个Struts2程序入口开始
IDE点击进入它的代码,根据filter的接口定义,我们需要看init,doFilter,destroy
三个方法,先不管destroy
,毕竟都结束了
public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
this.prepare = new PrepareOperations(dispatcher);
this.execute = new ExecuteOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
this.postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
String uri = RequestUtils.getUri(request);
if (this.excludedPatterns != null && this.prepare.isUrlExcluded(request, this.excludedPatterns)) {
LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", new Object[]{uri});
chain.doFilter(request, response);
} else {
LOG.trace("Checking if {} is a static resource", new Object[]{uri});
boolean handled = this.execute.executeStaticResourceRequest(request, response);
if (!handled) {
LOG.trace("Assuming uri {} as a normal action", new Object[]{uri});
this.prepare.setEncodingAndLocale(request, response);
this.prepare.createActionContext(request, response);
this.prepare.assignDispatcherToThread();
request = this.prepare.wrapRequest(request);
ActionMapping mapping = this.prepare.findActionMapping(request, response, true);
if (mapping == null) {
LOG.trace("Cannot find mapping for {}, passing to other filters", new Object[]{uri});
chain.doFilter(request, response);
} else {
LOG.trace("Found mapping {} for {}", new Object[]{mapping, uri});
this.execute.executeAction(request, response, mapping);
}
}
}
} finally {
this.prepare.cleanupRequest(request);
}
}
}
我们说Struts实际上是一个Filter,所以Filter的生命周期就是Struts2的生命周期,所以我们可以把Struts2的运行逻辑的主线划分为两类
- Struts2的初始化:init方法驱动执行
- Struts2处理HTTP请求:doFilter方法驱动执行
初始化主线
这条主线由Filter的init方法驱动执行,执行完毕后,该线程结束,这条主线本身不参与后面任何HTTP请求的处理
这条主线主要做了以下事情
框架元素的初始化工作:包含了对框架内部的许多内置对象的创建和缓存
控制框架运行的必要条件:初始化过程对框架运行参数和执行模式进行正确性校验
HTTP请求处理主线
这条主线由Filter的doFilter方法负责驱动执行,分成两个阶段
第一阶段:HTTP请求预处理,为真正的业务逻辑执行做必要的数据环境和运行环境的准备
第二阶段:XWork执行业务逻辑,Struts2完成HTTP请求预处理后将HTTP请求中的数据封装成普通的Java对象,程序控制权移交给了XWork,由XWord负责执行具体的业务逻辑,这一阶段完全不依赖于Web容器,是为了消除核心程序对运行环境(Web容器)的依赖
因为有了第二阶段,所以严格意义上的Struts2,实际上由两个不同的框架组成,XWork才是真正实现MVC的框架,将Web容器与MVC实现分离,是Struts2区别于其他Web框架最重要的特性
0x02 Struts2的构成元素
Struts2初始化过程的构成元素
为了更好地管理Struts2中的内置对象,Struts2引入了一个”容器”的概念,实际上Tomcat这些也是这样做的,将所有需要被管理的对象全部置于容器之中
除了容器,Struts2中另一类配置元素PackageConfig,也是Struts2初始化的主要内容之一
用数据结构+算法的角度分析,初始化过程的主要元素就可以分为数据结构的定义和初始化行为的操作两个部分
初始化的数据结构
- Container(com.opensymphony.xwork2.inject):容器定义接口,是Struts2内部进行对象管理的基础构建
- ContainerImpl(com.opensymphony.xwork2.inject):容器的实现类,内部实现了Struts2进行对象生命周期管理和依赖注入的基本功能
- PackageConfig(com.opensymphony.xwork2.config.entities):PackageConfig实体类,其中定义了时间响应模型的完成数据结构
初始化的操作过程
- ConfigurationProvider(com.opensymphony.xwork2.config):配置加载接口的统一接口,Struts2将初始化元素分为Container和PackageConfig两个类,使用多重继承将两类配置加载接口进行统一
- ContainerProvider(com.opensymphony.xwork2.config):Container的配置加载接口,其实现类需要负责初始化容器中的所有对象
- PackageProvider(com.opensymphony.xwork2.config):PackageConfig的配置加载接口,其实现类需要负责初始化用于处理时间请求的配置对象
- ContainerBuilder(com.opensymphony.xwork2.inject):Container的构造器,用于在初始化时构造容器
- PackageConfigBuilder(PackageConfig内部类):PackageConfig的构造类,用于初始化是构造PackageConfig
还有一些其他的辅助元素
- ConfigurationManager(com.opensymphony.xwork2.config):配置行为操作代理类,包含了所有ContainerProvider和PackageProvider的实现以及所有配置的结构化数据(Configuration)
- Configuration(com.opensymphony.xwork2.config):Struts2配置数据的管理类,作为运行时获取配置的基本接口,承载所有配置的结构化数据和操作方法
Struts2HTTP请求预处理阶段的构成元素
在这个阶段,程序的控制权仍然在Struts2中,这个阶段涉及的元素,主要是为了和Web容器打交道,以及为了保持和Web容器解耦,这个阶段做了大量的对象创建和对象转化的工作,用来作为第二阶段的交付物
- Dispatcher(org.apache.struts2.dispatcher):Struts2的核心分发类,是Struts2进行HTTP请求处理的实际执行者,更是将HTTP请求与Web容器进行解耦并进行逻辑处理转发的执行驱动核心
- PrepareOperations(org.apache.struts2.dispatcher.ng):Struts2进行HTTP预处理的操作集合
- ExecuteOperations(org.apache.struts2.dispatcher.ng):Struts2进行HTTP请求逻辑处理的操作集合
XWork2执行业务逻辑
XWork就是一条流水线,它的每个元素,就是流水线中的原材料,XWork框架就像一个完整的事件执行器,进入框架中的事件就如同进入生产线中的原材料,会按照生产线中的定义依次执行并产生结果
XWork中有7大元素
- ActionProxy(com.opensymphony.xwork2):XWork生产线中的执行环境,整个生产线的入口,封装了所有的执行细节
- ActionInvocation(com.opensymphony.xwork2):XWork生产线中的调度者,负责调度整个生产线中各个元素的执行次序
- Interceptor(com.opensymphony.xwork2.interceptor):XWork生产线中的工序序列,可以丰富整个生产线的功能
- Action(com.opensymphony.xwork2):XWork生产线的辅助设备,提供整个生产线运作所必须的数据环境
- ActionContext(com.opensymphony.xwork2):XWork生产线的辅助设备,提供整个生产线工作运行所必需的数据环境
- ValueStack(com.opensymphony.xwork2.util):XWork数据环境中提供表达式运算的工具类,也是XWork中进行数据访问的基础
- Result(com.opensymphony.xwork2):XWork生产线中的末端设备,负责输出生产线的生产结果
它们的调用关系如上图所示
0x03 Struts2的配置元素
Struts2的配置元素的表现形式以XML为核心,而Properties则作为另外一种配置形式起到辅助作用,XML文件的配置元素定义时Properties文件配置元素定义的超集,也就是Properties文件中定义的配置元素,我们都可以在XML中找到相应的配置方式替代,反之不成立
Struts2配置文件的表现形式如下
- web.xml(/WEB-INF/):应用级别配置,Struts2的入口程序定义、运行参数定义
- struts-default.xml(/WEB-INF/lib/struts2-core.jar!struts-default.xml):框架级别配置,包含所有Struts2的基本构成元素定义
- struts.xml(/WEB-INF/classes):应用级别配置,Struts2默认主配置文件,包含所有应用级别对框架级别的默认行为的覆盖定义
- default.properties(/WEB-INF/lib/struts2-core.jar!org.apache.struts2.default.properties):框架级别配置,Struts2默认的框架级别运行参数配置
- struts.properties(/WEB-INF/classes):应用级别配置,包含所有应用级别对框架级别的运行参数的覆盖定义
- struts-plugin.xml(插件所在JAR文件的根目录):应用级别配置,Struts2所支持的插件形式的配置文件,文件结构与struts.xml一致,其定义作为struts.xml的扩展,也可以覆盖框架级别的行为定义
Struts2配置元素定义
我们来看struts-default.xml,因为XML是properties的超集,默认配置几乎覆盖了所有配置元素
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.excludedClasses"
value="
java.lang.Object,
java.lang.Runtime,
java.lang.System,
java.lang.Class,
java.lang.ClassLoader,
java.lang.Shutdown,
java.lang.ProcessBuilder,
sun.misc.Unsafe,
com.opensymphony.xwork2.ActionContext" />
<!-- this must be valid regex, each '.' in package name must be escaped! -->
<!-- it's more flexible but slower than simple string comparison -->
<!-- constant name="struts.excludedPackageNamePatterns" value="^java\.lang\..*,^ognl.*,^(?!javax\.servlet\..+)(javax\..+)" / -->
<!-- this is simpler version of the above used with string comparison -->
<constant name="struts.excludedPackageNames"
value="
ognl.,
java.io.,
java.net.,
java.nio.,
javax.,
freemarker.core.,
freemarker.template.,
freemarker.ext.jsp.,
freemarker.ext.rhino.,
sun.misc.,
sun.reflect.,
javassist.,
org.apache.velocity.,
org.objectweb.asm.,
org.springframework.context.,
com.opensymphony.xwork2.inject.,
com.opensymphony.xwork2.ognl.,
com.opensymphony.xwork2.security.,
com.opensymphony.xwork2.util." />
<bean class="com.opensymphony.xwork2.ObjectFactory" name="struts"/>
<bean type="com.opensymphony.xwork2.factory.ResultFactory" name="struts" class="org.apache.struts2.factory.StrutsResultFactory" />
// ...这里省略了很多bean的定义
<bean type="com.opensymphony.xwork2.config.providers.ValueSubstitutor" class="com.opensymphony.xwork2.config.providers.EnvsValueSubstitutor" scope="singleton"/>
<package name="struts-default" abstract="true" strict-method-invocation="true">
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
// ...这里省略了很多result-type的定义
<result-type name="postback" class="org.apache.struts2.result.PostbackResult" />
</result-types>
<interceptors>
<interceptor name="alias" class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/>
// ...这里省略了很多interceptor的定义
<interceptor name="noop" class="org.apache.struts2.interceptor.NoOpInterceptor" />
<!-- Empty stack - performs no operations -->
<interceptor-stack name="emptyStack">
<interceptor-ref name="noop"/>
</interceptor-stack>
// ...这里省略了很多interceptor-stack定义
<!-- Sample execute and wait stack.
Note: execAndWait should always be the *last* interceptor. -->
<interceptor-stack name="executeAndWaitStack">
<interceptor-ref name="execAndWait">
<param name="excludeMethods">input,back,cancel</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="execAndWait">
<param name="excludeMethods">input,back,cancel</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="defaultStack"/>
<default-class-ref class="com.opensymphony.xwork2.ActionSupport" />
<global-allowed-methods>execute,input,back,cancel,browse,save,delete,list,index</global-allowed-methods>
</package>
</struts>
struts-default.xml中几乎覆盖了所有配置节点,除了include和在struts.xml中配置的我们自己实现的action节点,include类似如下使用
<struts>
<include file="web/struts-config.xml" />
<include file="web/struts-system.xml" />
<include file="web/struts-user.xml" />
</struts>
Struts2的配置元素有以下几种
- include:串联模块化的配置文件,类似对象的引用嵌套,方便配置模块化管理(另一种模块化方式是继承)
- bean:通过type和name属性共同构成一个逻辑主键来共同决定一个class属性,是一个描述接口及其实现类映射关系的节点
- constant:一个典型的键值对类型的配置,定义Struts2运行时参数,这些参数更多的放在properties文件中
- package:复合节点,包含ResultType,Interceptor,InterceptorStack,Action等,一个package节点可以看作一条简单的XWork生产流水线,它包含的节点是构成事件执行序列的主要元素
Struts2配置元素分类
根据上面的介绍,我们可以将除include以外的元素分成两类
- 容器配置元素:bean和constant,一个是构成程序执行的对象,另一个用于指定程序运行的执行参数,都与Struts2自身的运行机制有关
- 事件映射关系元素:package,定义了一种事件请求响应的映射关系,反应Struts2对于外部事件请求时如何进行响应的处理序列
知道了以上内容,我们将在之后的文章中详细展开Struts2的各个部分