0x00 使用S2-S1插件
1.需求
对于Struts1的忠实粉丝,在Struts和webwork合并产生Struts2后,Struts1更新和维护就停止了。版本停留在1.3.10。而曾经保留的Action和ActionForm配置习惯还存在,想要移植也是一个麻烦的事情,所以官方写了一个S2-S1-plugin插件。提供的功能有:
- Can use Struts 1 Actions and ActionForms with no code changes
- Supports Commons Validator-enabled ActionForms
大意是开发者可以使用Struts1中的Action和ActionForm,不需要改变其中的代码。并且支持验证表单。
2.使用
使用教程参考:http://struts.apache.org/docs/struts-1-plugin.html
使用插件所需的包在官方提供的app样例程序showcase中可以找到,并且需要将Struts1使用的包进行导入。 插件源码则可以在
struts-2.3.24-allsrcplugins
文件夹下找到。 写好Action和ActionForm代码后,只需要按照官方教程上写个Struts.xml配置文件即可。
1 | <package name="integration" extends="struts1-default" namespace="/integration"> |
0x01 插件工作原理分析
1.Struts1Action 中间代理
1 | <action name="inputUser" class="org.apache.struts2.s1.Struts1Action"> |
在配置Struts.xml文件中,可以看到是Struts2规范。并将action设置为Struts1Action,而并非我们自定义的处理类integration.action.InputAction。并且自定义类当做className属性传入Struts1Action类中。我们看Struts1Action的主要属性有:
1 | private ActionForm actionForm; |
其中className属性就是上述配置的自定义的Struts1 action类中integration.action.InputAction类,用于处理用户表单数据。该插件相当于封装了一个中间action,转发Struts2的action到Struts1的action进行处理。具体看看 execute()方法:
1 | ActionContext ctx = ActionContext.getContext(); |
在执行过程中,org.apache.struts2.s1.Struts1Action类首先action = (Action) objectFactory.buildBean(className, null);
,这样通过配置的className生成了一个Struts1 Action该类的java对象。
然后在ActionForward forward = action.execute(mapping, actionForm, request, response);
这句代码,将actionForm的数据交给我们配置的Struts1 action进行处理。在这里很好的完成了中间代理连接Struts2和1的任务。 这里有个问题,对于表单和ActionForm属性间的传递是怎样实现的?
在Struts1中,通过ActionServlet中processPopulate(request, response, form, mapping);
进行表单和ActionForm进行对应赋值。而这里则是通过配置的第二个关键技术ScopedModelDrivenInterceptor出路,内部使用OGNL语言进行表单数据处理对应。
2.ScopedModelDrivenInterceptor 拦截器
1 | <interceptor name="userForm" class="com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor"> |
在Struts2中,会使用拦截器对数据进行分析处理,而ScopedModelDrivenInterceptor实现了将ActionForm传入OGNL执行所需要的Context上下文的操作,然后在参数拦截器中将数据对ActionForm进行赋值。
在拦截器执行的intercept方法中,Object model = resolveModel(objectFactory, ctx, cName, scope, modelName);
是关键执行该功能的语句。
1 | protected Object resolveModel(ObjectFactory factory, ActionContext actionContext, String modelClassName, String modelScope, String modelName) throws Exception { |
resolveModel函数首先实例化了ActionForm(这里的modelClassName),然后添加到了ContextMap中。这样会在执行OGNL语句中,会根据上下文进行赋值,顺利将表单中的参数赋值到ActionForm中。而且将model进行了
0x02 Plugin的调试过程
- 对我写下的测试实例进行调试,在ScopedModelDrivenInterceptor拦截器和Struts1Action部分下断点调试。可以看到,首先进入拦截器,将ActionForm加入Context中。
- 调试的时候,看到了OgnlContext的变化。
- 然后将ActionForm加入上下文后,通过参数拦截器进行赋值操作。可以在newStack栈中看到刚才加入的UserForm。
- 最后进入Struts1Action中,执行Struts1的action。