virt-v2v 迁移
在本系列的最后一部分,我们将通过将用户界面从Struts迁移到Struts2,完成应用程序的转换。 在第1部分中,我们研究了Struts和Struts 2应用程序之间的高级架构和基本区别。 在第2部分中,我们开始转换包括操作和配置的实际应用程序。
从高层次上讲,Struts2中的许多标签看起来将与Struts中的标签非常相似。 有用于逻辑的标签,用于显示bean数据的标签,以及有助于表单并将bean数据填充到特定字段中的标签。 只有当您仔细观察时,差异才会变得明显。
差异允许框架提供以下功能:
- JSP标记可作为一流对象用于不同的视图技术 -传统上,标记库仅可用于JSP。 Struts2采取的方法是,所有视图技术(JSP,Velocity,Freemarker等)都应该是一流的公民,并为他们提供使开发Web应用程序更容易的对象。
- 主题 -主题可以在全局或单个基础上提供给标签。
这些功能之所以可用,是因为采用了设计用户界面层的方法。
用户界面架构
大多数读者应该熟悉model-view-controller模式。 它是Struts和Struts2框架的基础。 Struts2以另一种能力结合了这种模式-作为用户界面体系结构的基础。
从高层次看,这是这样的:
![](https://i-blog.csdnimg.cn/blog_migrate/6e1f0a385bf66bf7ef8290557e9f3b30.png)
模型 -模型是从Component基类扩展的。 UI体系结构中的模型还有其他责任。 除了作为独立于实现的数据容器之外,它还通过指定所使用的模板来提供有关如何呈现自身(视图)的信息。
查看 -最终HTML由Freemarker模板生成。 通常,taglib中的Java代码将提供任何必要的逻辑并将HTML代码写入输出流。 通过允许模板语言接管此功能,可以实现额外的灵活性。
提示:Struts2在部署中提供了多个主题,默认情况下,呈现标记视图时使用“ xhtml”主题。 “ xhtml”主题为表单元素提供功能,例如使用表格进行布局,验证消息呈现和标签/值列。 同样,“ css_xhtml”(使用CSS布局而不是表布局),“ simple”(没有附加功能-仅基本HTML标签呈现)和“ ajax”(用于启用Ajax的标签呈现)的主题也可用。 通过为“ theme”属性指定值,可以在视图代码中使用其中任何一个。
此外,开发人员可以创建全新的主题,而无需自定义框架编译。 这不在本文讨论范围之内,但是更多信息可以在Struts2 Wiki上找到。
控制器 -对于希望访问标签库的每种呈现语言,都需要提供一个新的控制器对象。 默认情况下,Struts2提供AbstractDirective (对于Velocity), TagModel (对于Freemarker)和ComponentTagSupport (对于JSP)的实现。 这使标记库功能(以前仅在JSP中可用)成为其他视图技术的一等公民。
目前,所有这些可能看起来有点抽象,但是随着我们开始更详细地讨论标签,实际应用将变得显而易见。 但是现在,该看一些代码了。
转换JSP
让我们从添加JSP开始。 这是Struts版本的样子:
<html>
<head><title>Add Blog Entry</title></head>
<body>
<form action="save.action" method="post">
Title: <input type="text" name="title" /><br/>
Entry: <textarea rows="3" cols="25" name="entry"></textarea> <br/>
<input type="submit" value="Add"/>
</form>
</body>
</html>
您可能已经注意到,这是一个纯HTML页面。 实际上,我们可以将其称为add.html。
这是Struts2版本:
<%@ taglib prefix="s" uri="/WEB-INF/struts-tags.tld" %>
<html>
<head><title>Add Blog Entry</title></head>
<body>
<s:form action="save" method="post" >
<s:textfield label="Title" name="title" />
<s:textarea label="Entry" name="entry" rows="3" cols="25" />
<s:submit value="Add"/>
</s:form>
</body>
</html>
我们正在使用JSP,因此第一行是标记库的定义(我们不会在剩余的代码中包括它)。 文件struts-tags.tld定义了可用的标签,并将其放置在WEB-INF目录中。 该文件最初是从“ struts2-core-2.0.0-SNAPSHOT.jar”文件中提取的。
通过Struts示例,我们看到其余的更改集中在HTML标签周围。 表单标记变为s:form ,文本区域变为s:textarea ,输入标记变为s:textfield或s:submit 。 只要有可能,Struts2标记名称都将与HTML等效名称匹配。 输入标签是不同的-类型不是添加类型属性,而是成为标签本身的名称。
使用Struts2标记,我们可以看到该代码更简洁,更易于阅读。 在s:form标记中,action属性的值变为“ save”,而不是“ save.action”。 标记处理添加扩展以及Web应用程序上下文。 此外,如果需要,还有一个名称空间属性可提供其他路径信息-该标记也将合并到呈现的最终HTML表单标记中。
另一个变化是附加属性标签的存在。 在Struts2标签中,每个表单元素标签的文本都成为标签本身的一部分,而不是从外部指定的。 为什么这么重要? 答案在于标记的呈现。 在本文的开头,我们谈到了UI架构,以及模型如何呈现自身。 如果在Struts2 add.jsp呈现HTML上执行“查看源代码”,您会注意到表单和所有元素都在表格标记内。 标签位于表的一列中,输入或textarea HTML标记位于另一列中。 默认的“ xhtml”主题为我们提供了其他布局格式。
因为这是添加页面,所以最后要提到的一件事。 name属性的值确定将在操作或模型的哪个字段上设置用户输入的值。 因此,如果名称为title ,请确保您具有setTitle (...)方法。
view.jsp说明了另一个常用的标记-呈现数据值的标记。
Struts视图JSP:
<html>
<head><title>View Blog Entry</title></head>
<body>
Id: <c:out value="${requestScope.blog.id}" /><br/>
Title: <c:out value="${requestScope.blog.title}" /><br/>
Entry: <c:out value="${requestScope.blog.entry}" /><br/>
Updated: <fmt:formatDate
value="${requestScope.blog.created.time}" pattern="MM/dd/yyyy"/><br/>
<br/>
<a href="list.action">Back to List</a> |
<a href="edit.action?id=<c:out value="${requestScope.blog.id}"/>">Edit</a>
</body>
</html>
Struts2视图JSP:
<html>
<head><title>View Blog Entry</title></head>
<body>
Id: <s:property value="id" /><br/>
Title: <s:property value="title" /><br/>
Entry: <s:property value="entry" /><br/>
Updated: <s:property value="created.time" /><br/>
<br/>
<s:url id="viewUrl" action="list" />
<s:a href="%{viewUrl}">Back to List</s:a> |
<s:url id="editUrl" action="edit" >
<s:param name="id" value="%{id}" />
</s:url>
<s:a href="%{editUrl}" >Edit</s:a>
</body>
</html>
在JSTL中, c:out标记的value属性提供要渲染的数据,这些数据可以是静态的也可以是动态评估的。 通过使用定界符“ $ {”和“}”来表示表达式。 在JSP视图中,我们要访问放置在HTTP请求范围内的对象实例,这是通过以“ requestScope”开始表达式来实现的。 一旦提供了范围,就可以使用点符号确定通过对象图的路径。
边栏:点符号是一种在对象图中浏览路径的简便方法。 与其使用Java代码“ getPerson()。getAddress()。getPostcode( )”, 不如使用简单得多的“ person.address.postcode ”。
fmt:formatDate标记类似于c:out标记,不同之处在于c:out将以毫秒为单位呈现fmt:formatDate标记以更用户友好的方式显示数据的时间。
SIDEBAR:使用JSTL只是在JSP中提供数据的一种方式。 其他可能性包括直接使用JSP标记(即jsp:useBean和jsp:getProperty )以及使用Struts Taglib标记库。 与Struts2标记库一样,Struts Tablib标记库在HTML标记周围提供了附加的抽象,并提供了附加的映射功能以简化实现。
Struts2的s:property标签与c:out标签非常相似。 它使用一种表达语言来获得一个值,并且还可以通过使用点符号在对象图中搜索来找到该值。 有两个主要区别。 首先是OGNL-对象图导航语言( http://opensymphony.com/ognl)-被用作表达语言。 因此,不仅可以使用点表示法来查找要显示的值,还可以使用高级功能,例如对象方法调用,投影和lambda表达式。 这是一个开源项目,独立于Struts2开发。
第二个区别是不需要范围。 这既是优点也是缺点。 Struts2具有一个Value Stack ,而不是JSP开发人员需要明确指定的不同范围。 值堆栈只是查找值时遍历的不同范围的顺序。 查找值时,如果在一个作用域中未找到该字段的吸气剂,则检查下一个作用域-继续进行直到找到该字段或所有作用域已用尽。 范围的顺序为:
- 临时对象 -这些是在JSP页面中创建的对象(我们将很快看到带有s:url标记的示例),或者它们是由寿命短的标记创建的对象(例如,在采集)。
- 模型对象 -如果正在使用模型对象,则接下来(在操作之前)检查它们。
- 动作对象 -这是刚刚执行的动作。 因此,无需将操作明确放在会话或请求范围内,您仍然可以访问其数据。
- 命名对象 -这些对象包括#application,#session,#request,#attr和#parameters,并引用相应的servlet作用域。
优点很明显-依次检查每个作用域中您要查找的字段。 但是,如果模型对象和动作都具有相同的字段名称(设置为“ id”),则可能会出现问题。 如果您想要模型值,则将返回正确的结果-但是,如果您想要动作值,则将返回模型值,因为模型作用域位于动作作用域之前。 解决方案是提供一个限定符,该限定符确定要在堆栈中的哪个位置检索“ id”字段-在本示例中,“ [1] .id”将从操作中检索值。 可以在http://cwiki.apache.org/WW/ognl-basics.html上在线找到更多信息。
最后一部分是HTML a标签。 根据我们对JSTL的了解,我们看到Struts JSP只是使用标准HTML a标签,以及JSTL c:out标签来提供要编辑的博客的ID。 Struts2的实现稍微复杂一些。 这里又是:
<s:url id="editUrl" action="edit" >
<s:param name="id" value="%{id}" />
</s:url>
<s:a href="%{editUrl}">Edit</s:a>
第一个标记s:url生成我们将要使用的URL。 使用动作(显示)和名称空间(未显示)的属性,它与s:form标记相同。 还有一个id属性。 这很重要,因为s:url标记将生成的URL放在id属性指定的值下的Temporary Object作用域中。 要将名称/值对添加到URL(“?user = bob”部分),请使用s:param标记。
SIDEBAR:为什么我们不能仅在HTML a标签中使用s:property标签? 答案是可以的。 上述方法的好处是生成的URL在整个应用程序中是一致的。
Struts2的一项高级功能是能够更改调用操作的URL。 您可以替换一个新的ActionMapper实现,以代替更标准的URL,例如“ /listUser.action?id=4”,以使URL更加友好,甚至可以使用更多的Ruby On Rail,例如“ / user / 4 / list”。 为了利用这样的更改,让所有代码以标准方式生成URL是有益的。
s:a标记重复了HTML a标记功能,不同之处在于href属性的值可以从Value Stack中获取 。 在我们的例子中,我们要获取生成的编辑URL的值。 您可能已经注意到s:param和s:a标记都使用定界符-“%{”和“}”,而s:property则没有。 当期望标签始终从值堆栈中提取值( s:property标签)时,则不需要定界符。 但是,当值可以是静态文本,值堆栈中的值,已评估的表达式或这些选项中的任何一个( s:param和s:a标记)的组合时,则需要定界符。 但是不用担心-如果您放入了定界符并且不需要它们,Struts2只会删除它们。
到目前为止,我们已经研究了输入表单数据,以及查看在表单中输入的信息。 接下来是编辑表单数据。 这与add.jsp代码非常相似-将显示HTML表单以及HTML输入字段。 不同之处在于,我们将需要一些其他标签来显示当前信息,并且需要一种方法来告知操作我们正在编辑的记录。
这是Struts Edit JSP:
<html>
<head><title>Update Blog Entry</title></head>
<body>
<form action="update.action" method="post">
<input type="hidden" name="id"
value="<c:out value="${requestScope.blog.id}" />" />
Id: <c:out value="${requestScope.blog.id}" /><br/>
Title: <input type="text" name="title"
value="<c:out value="${requestScope.blog.title}" />"/><br/>
Entry: <textarea rows="3" cols="25" name="entry">
<c:out value="${requestScope.blog.entry}" /></textarea><br/>
Updated: <fmt:formatDate
value="${requestScope.blog.created.time}" pattern="MM/dd/yyyy"/><br/>
<input type="submit" value="Update"/>
</form>
</body>
</html>
Struts2 Edit JSP:
<html>
<head><title>Update Blog Entry</title></head>
<body>
<s:form action="update" method="post" >
<s:hidden name="id" value="%{id}" />
<tr>
<td align="right">Id:</td>
<td><s:property value="id" /></td>
</tr>
<s:textfield label="Title" name="title" value="%{title}" />
<s:textarea label="Entry" name="entry" value="%{entry}" rows="3" cols="25" />
<tr>
<td align="right"> Updated:</td>
<td><s:property value="created.time" /></td>
</tr>
<s:submit value="Update"/>
</s:form>
</body>
</html>
这正是我们的期望。 输入HTML标记具有附加的属性值,实际值是c:out标记,显示该字段的当前内容,而textarea HTML标记在其开始和结束标记之间包含c:out标记。 通过隐藏HTML标记将正在编辑的记录上的信息传达给下一个动作。 它还包含一个c:out标记,以提供已编辑记录的“ id”值。
Struts2版本与edit.jsp更加相似。 每个输入标签都有一个附加的value属性,类似于它们HTML对应属性,它们使用定界符(因为值可以是静态的)和Value Stack (如上所述)。 正如我们期望的那样,还有一个s:hidden标记,它复制了隐藏HTML标记。
该JSP的尴尬之处在于,某些字段(标识和更新的字段)需要HTML标记信息才能将其正确呈现到Struts2提供的主题中,而有些则不需要。 有两种解决方案-第一种是使用“简单”主题并在JSP中提供所有布局信息。 另一种方法是为s:input标签创建一个新模板,该模板仅允许查看数据,而不修改数据。 我将其留给读者练习。
最终的JSP是列表JSP。 通过此JSP,我们将引入一类新的提供逻辑的标记库。 这是唯一引入的新元素,我们将其与已经讨论过的数据呈现标记库结合在一起。
列表JSP:
<html>
<head><title>List Blogs</title></head>
<body>
My Blogs:<br/> <c:forEach var="blog" items="${requestScope.bloglist}">
<a href="view.action?id=<c:out value="${blog.id}"/>">
<c:out value="${blog.title}"/></a>
[ Updated <fmt:formatDate value="${blog.created.time}" pattern="MM/dd/yyyy"/> ]
(<a href="remove.action?id=<c:out value="${blog.id}"/> ">remove</a>)
<br/>
</c:forEach>
<a href="add.action">Add a new entry</a>
</body>
</html>
Struts2列表JSP:
<html>
<head><title>List Blogs</title></head>
<body>
My Blogs:<br/>
<s:iterator value="bloglist" >
<a href="view.action?id=<s:property value="id"/>"><s:property value="title"/></a>
[ Updated <s:property value="created.time"/> ]
<s:url id="removeUrl" action="remove" >
<s:param name="id" value="%{id}" />
</s:url>
<s:a href="%{editUrl}" >Edit</s:a>
(<s:a href="%{removeUrl}">remove</s:a>)
<br/>
</s:iterator>
<s:url id="addUrl" action="add" >
<s:a href="%{addUrl}">Add a new entry</s:a>
</body>
</html>
JSTL中的标记是c:forEach标记,它具有两个属性-var属性提供了可以引用集合中当前元素的名称,而items属性则提供了要迭代的集合。 正如我们期望的那样, c:forEach标记中包含的c:out标记均以“ blog”开头,因此它们引用当前元素。
同样,在Struts2实现中,还有as:iterator标记。 此功能与JSTL c:forEach标记完全相同,但是,您可能已经注意到,没有提供属性来引用集合中的当前元素。 原因再次在值堆栈中-当前元素位于“临时对象”范围内的值堆栈的顶部,并且可以通过s:property标记访问而无需特定的对象ID。 如果您觉得提供对象ID更安全,则s:iterator标记上有一个id属性,其作用类似于c:forEach标记上的var属性。
简化动作
在上一篇文章中,我们详细讨论了该操作。 您可能还记得,所有操作方法的最后一步是将Blog实例放置在HttpServletRequest中 。 但是,我们并未在s:property标记中添加“ #request”值。
您应该已经知道为什么不将“ #request”添加到值中(模型和操作位于“ 值堆栈”中 )。 现在,剩下要做的就是通过删除不必要的代码来简化操作类。 具体来说,这意味着我们不再需要将Blog放在HttpServletRequest中 ,并且该动作不再需要实现ServletRequestAware接口。
需要同样改变了ListBlogsAction行动来进行,但是,由于ListBlogsAction不使用模型对象,我们还需要一个getBloglist()方法添加到操作。
完整的源代码可以在本文的附件中找到,并且可以在其中更仔细地检查操作类的更改。
结语
从设计角度和实现角度,我们已经在本文中进行了很多介绍,但是我们还必须采取一些捷径。 特别是,没有足够的时间来专门研究所有可用标签。 Struts2中的标签分为四类:控制标签,数据标签,表单标签和非表单UI标签。 有关所有这些的更多信息,请参见Struts2标签参考页http://struts.apache.org/WW/tag-reference.html 。
除标签外,还有很多我们可以谈论的功能。 这里有几个。 我将留给您研究您发现有趣的内容:
- 验证 -使用Struts2,您可以在类中或通过使用对操作类或模型类起作用的声明性验证框架来提供验证代码。 还提供了基于Ajax的验证。 如要进一步了解,请参阅java.net扎拉尔Siddiqi文章- http://today.java.net/pub/a/today/2006/01/19/webwork-validation.html 。
- 国际化 -像其前身一样,Struts2在动作,验证,消息和标签库中内置了强大的国际化功能。
- Ajax支持 -有一个完整的主题构建以提供Ajax功能,以及返回XML和JSON响应而不是HTML页面片段的方法。
- 结果/结果类型 -自定义结果很容易,并允许您生成图表,图形,JSON,XML或其他您可以想象的结果。
- 注释 -为了简化配置,正在对注释和“约定之上的配置”进行更改。 最终彻底消除对XML配置的需求。
这将我们带到本系列的结尾。 到目前为止,您应该了解Struts2架构(整体架构和用户界面架构)。 能够解释请求处理中的差异; 了解如何配置Struts2应用程序; 并且知道如何将动作和JSP结合起来。
有了Struts的背景知识,以及现在从Struts2上的系列文章中获得的信息,您就应该很好地迁移复杂的应用程序以及从头开始新的Struts2项目。
virt-v2v 迁移