迁移到Struts 2-第二部分

在本系列的第一部分中 ,我们(为Struts开发人员)解释了新的Struts 2(以前称为WebWork)和Struts 1中的高级体系结构,基本请求工作流,配置语义以及操作框架中的差异。应该简化将任何大小的应用程序从Struts迁移到Struts 2的过程。

在本系列的这一部分中,我们将专注于转换动作。 在研究所需的代码更改之前,我们首先需要设置场景。 我们将讨论要转换的示例应用程序,以及在Struts和Struts2版本中使用的通用组件。

从那里,我们将回顾Struts应用程序代码,以查看转换为Struts2代码后的外观。 最后,我们将看一下配置更改。

示例应用

为了简单起见,我们将选择一个大多数人都应该熟悉的示例-网络日志。 尽管简单但可能有些过度使用(也许不如Sun Pet Store那样多),但这是一个示例,不需要解释。

为了更简洁地定义功能,我们将讨论以下用例:

  1. 添加新的博客条目
  2. 查看网志条目
  3. 编辑网志条目
  4. 删除博客条目
  5. 列出所有博客条目

进一步细分,我们要实现的功能是Web应用程序最常见的功能。 它们包括创建,读取,更新和删除-通常称为CRUD。 简化这些步骤将大大提高生产率。

Struts和Struts2应用程序(后端业务服务)之间还将有一个公共组件。 看起来是这样的:

public class BlogService {
private static List<Blog> blogs = new ArrayList<Blog>();
public List<Blog> list() { ... }
public Blog create(Blog blog) { ... }
public void update(Blog blog) { ... }
public void delete(int id) { ... }
public Blog findById(int id) { ... }
}

该对象将在我们的示例中支持用例。 为了简化实现,我们将在Struts和Struts2的操作中实例化此对象。 这将在实际应用程序中提供不必要的耦合,但是对于我们的示例(专注于Web层)而言,这已足够。

SIDEBAR:在第一部分中,我们讨论了Struts2操作中使用的依赖项注入的接口注入样式。 这是用于注入与servlet相关的对象实例的主要样式( HttpServletRequestHttpServletResponsePrincipalProxy等),但这不是唯一的样式。

Struts2使用Spring Framework作为默认容器,并在这样做时使用依赖项注入的setter方法。 通过将setter添加到操作中(如下所示),Struts2框架将从Spring Framework上下文中检索正确的服务,并通过setter将其应用于操作。

public void setBlogService(BlogService service) {
this.blogService = service;
}
与接口注入样式类似,我们需要在动作拦截器堆栈中包含一个拦截器-ActionAutowiringInterceptor拦截器。 现在,在调用动作之前,将由Spring Framework管理的业务对象注入到Struts2动作中。 进一步的配置参数使您可以设置设置程序和业务对象之间的匹配方式(按名称,类型或自动)。

Struts应用程序代码

起点将是Struts实现。 对于每个用例,将有一个动作类,以及一个动作表单的类,该类将在需要它的所有动作中重复使用。 对于我们的应用程序,这可能不是最优雅的解决方案(其他解决方案包括使用动态表单或使用请求分派操作),但这是所有Struts开发人员都应该熟悉的解决方案。 通过转换简单的实现的知识,您将掌握Struts2框架的技能和知识,以迁移更高级的实现。

在第一部分中,我们谈到了Struts和Struts2动作之间的区别。 查看差异的另一种方法是通过UML。 这是Struts动作的一般形式:

该动作表单将用于多个动作,因此让我们先来看一下。

public class BlogForm extends ActionForm {

private String id;
private String title;
private String entry;
private String created;

// public setters and getters for all properties
}

如UML中所示,一个限制是我们的表单扩展了ActionForm类。 第二个限制是字段是String类,因此,getter返回String,而setter接受String。

使用动作表单的动作是查看,创建和更新动作。

查看动作:

public class ViewBlogAction extends Action {

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {

BlogService service = new BlogService();
String id = request.getParameter("id");
request.setAttribute("blog",service.findById(Integer.parseInt(id)));

return (mapping.findForward("success"));
}
}

创建动作:

public class SaveBlogEntryAction extends Action {

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {

BlogService service = new BlogService();
BlogForm blogForm = (BlogForm) form;
Blog blog = new Blog();
BeanUtils.copyProperties( blog, blogForm );

service.create( blog );

return (mapping.findForward("success"));
}
}

更新操作:

public class UpdateBlogEntryAction extends Action {

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {

BlogService service = new BlogService();
BlogForm blogForm = (BlogForm) form;

Blog blog = service.findById( Integer.parseInt(blogForm.getId()));
BeanUtils.copyProperties( blog, blogForm );
service.update( blog );
request.setAttribute("blog",blog);

return (mapping.findForward("success"));
}
}

这三个动作均遵循以下模式:

  • 业务对象实例已创建 -如前所述,我们正在采取最直接的途径在操作中使用业务对象。 这意味着将在每个操作中创建一个新实例。
  • 从请求中检索数据 -这是两种形式之一。 在视图操作中,直接从HttpServletRequest对象检索“ id”。 在创建和更新操作中,将使用ActionFormActionFormHttpServletRequest方法非常相似,唯一的区别是字段在对象内分组在一起。
  • 业务对象被称为 -现在该使用业务对象了。 如果参数(在视图操作中)是一个简单的对象,则可以在转换为正确的类型(从字符串到整数)后使用它。 如果对象是更复杂的域对象,则需要使用BeanUtil对象来转换ActionForm
  • 设置返回数据 -如果需要返回数据以便可以将其显示给用户,则需要将其设置为HttpServletRequest对象的属性。
  • 返回一个ActionForward-任何Struts动作的最后一步是查找并返回一个ActionForward对象。

最后两个动作,即remove和list动作,仅稍有不同。 如下所示,删除操作不使用BlogForm类。 通过使用请求属性“ id”(类似于查看操作),它可以使用业务对象执行必要的工作。 正如我们将在后面的配置中看到的那样,它不返回任何数据,因为成功结果被映射为执行list操作,该操作将在删除记录后检索必要的信息(从而使操作的关注点分开) 。

public class RemoveBlogEntryAction extends Action {

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {

BlogService service = new BlogService();
String id = request.getParameter("id");
service.delete(Integer.parseInt(id));

return (mapping.findForward("success"));
}
}

列表操作是不同的,因为它不使用用户的输入。 它仅使用无参数方法调用业务服务,然后向用户返回服务返回的域对象列表。 这里是:

public class ListBlogsAction extends Action {

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {

BlogService service = new BlogService();
request.setAttribute("bloglist",service.list());

return (mapping.findForward("success"));
}
}

转换为Struts2

在Struts2中,有很多方法可以实现上述应用程序。 这些方法从相同的“每类用例”方法(在Struts中使用)到创建类层次结构,甚至对于所有用例都只有一个动作类。 我们将要讨论的方法是我认为是最优化的解决方案-实现CRUD功能的单个类。

此外,我们将列表用例分开。 可以将其合并到同一类中,但是可能会在每个用例(列表用例中的Blog类,以及所有其他用例中的Blog类的列表)中未使用的类属性中造成混淆。

对于Struts2,我们能够在单个UML模型中显示所有动作类。

每个用例都是通过操作方法来实现的。 从上面的UML图中,我们看到在BlogAction上,我们有一种保存,更新和删除方法。 view方法是使用execute方法实现的,该方法是从ActionSupport类继承的。 类似地,在ListBlogAction上 ,使用execute方法实现列表用例。

为简单起见,此图为BlogAction保留了三个接口。 这些都是ServletRequestAware接口,该接口Prepareable模型驱动界面。

第一个接口是ServletRequestAware ,我们将在第I部分中详细介绍。使用该拦截器可以访问操作中的HttpServletRequest对象,这将使我们能够将对象放回到要在JSP中呈现的请求中。

接下来Preparable接口,它与PrepareInterceptor结合使用。 当将这两部分一起使用时,提供了一个prepare方法,该方法在execute方法之前被调用。 这允许在操作中进行设置,配置或预填充代码。

在我们的例子中,prepare方法通过检查blogId字段的值来检查这是一个新博客还是一个现有博客。 对于非零值,将检索博客模型并对其进行设置。

接下来是ModelDriven接口。 在上一篇文章中,我们看到了最深刻的区别之一是,在Struts中,动作必须是线程安全的,而在Struts2中则没有这种限制。 每个动作都会在每个请求上实例化并调用。 在Struts2中取消此限制后,动作类可以利用类级别的属性和方法(特别是getter和setter)。 将此功能与拦截器功能结合使用,可以直接在操作上设置HttpServletRequest中的属性。

它是这样的:

  • HTTP请求中的属性名称列表被迭代
  • 在当前动作中搜索当前属性名称的设置器
  • HttpServletRequest检索属性名称的值
  • 该值将转换为操作中设置器的正确类型
  • 然后,通过设置器将转换后的值应用于动作

提供此功能的拦截器是ParametersInterceptor拦截器。

提示:在开发动作时,如果由于某种原因未正确设置值,那么最好的第一步是确保将此拦截器放在应用于该动作的堆栈中。

实际上,这是始终遵循的好策略。 调试问题时,如果似乎有些地方不对或无法按预期方式工作,则很有可能与拦截器有关。 检查正在应用的拦截器以及它们的应用顺序。 拦截器可能会以您意想不到的方式相互干扰。

现在我们已将基于字符串的表单或请求属性应用于操作,下一步是将其应用于域对象或值/传输对象的字段。 这很容易。 实际上,您作为开发人员唯一需要做的就是实现ModelDriven接口(该接口具有单个getModel()方法)并确保将ModelDrivenInterceptor应用于操作。

现在,不是在动作上找到setter,而是首先检索模型并检查模型是否具有与属性名称匹配的setter。 如果在模型对象上没有此类设置器,但在动作上存在,则将在动作上设置该值。 在实践中,我们在BlogAction中看到了这种灵活性-以及Blog模型对象的字段,该操作上有一个setId()方法。 这允许在操作上设置博客的ID,并在准备方法中使用博客的ID来预取正确的Blog实例,然后直接在检索到的Blog实例上设置对象字段的值。

有了这两个功能,将在操作上调用的方法的实现变得很简单-调用特定的业务服务,并将要呈现的数据放置在HttpServletRequest中

public class BlogAction extends ActionSupport
implements ModelDriven, Preparable, ServletRequestAware {

private int blogId;
private Blog blog;
private BlogService service = new BlogService();
private HttpServletRequest request;

public void setServletRequest(HttpServletRequest httpServletRequest) {
this.request = httpServletRequest;
}

public void setId(int blogId) {
this.blogId = blogId;
}

public void prepare() throws Exception {
if( blogId==0 ) {
blog = new Blog();
} else {
blog = service.findById(blogId);
}
}

public Object getModel() {
return blog;
}

public String save() {
service.create(blog);
return SUCCESS;
}
public String update() {
service.update(blog);
request.setAttribute("blog",blog);
return SUCCESS;
}

public String remove() {
service.delete(blogId);
return SUCCESS;
}

public String execute() {
request.setAttribute("blog",blog);
return SUCCESS;
}


}

最后是列表操作。 它还需要访问HttpServletRequest对象以提供要渲染的数据,因此必须实现ServletRequestAware接口。 但是,由于执行该用例不需要任何输入,因此不需要其他接口。 这是实现:

public class ListBlogsAction extends ActionSupport implements ServletRequestAware {

private BlogService service = new BlogService();
private HttpServletRequest request;

public void setServletRequest(HttpServletRequest httpServletRequest) {
this.request = httpServletRequest;
}

public String execute() {
request.setAttribute("bloglist",service.list());
return SUCCESS;
}

}

这样就完成了我们对动作代码的实现。 在本系列的最后一部分,当我们将其与新的Struts2用户界面结合使用时,我们将能够进一步简化操作。

配置动作

在我们可以从浏览器调用任何动作之前,我们需要对其进行配置。 这是通过XML配置文件实现的。

对于Struts,我们在WEB-INF目录中使用一个名为“ struts-config.xml”的文件,在该文件中,我们需要配置两个元素-操作表单和操作本身。 Struts2配置(在classes目录中使用名为“ struts.xml”的文件)要复杂一些,因为我们需要配置拦截器和操作。

Struts配置的form-b​​eans节点很容易,具有唯一名称(由开发人员提供)的属性和类型,即ActionForm类的包和名称。

<struts-config>

<form-beans>
<form-bean name="blogForm"
type="com.fdar.articles.infoq.conversion.struts.BlogForm"/>
</form-beans>
...

</struts-config>

我们可以通过三种不同的方式在示例应用程序中配置操作。

1.重定向配置

在此配置中,不使用任何操作类。 而是将请求转发到没有后端处理的JSP上。

在Struts配置中,每个映射都提供一个路径元素,该元素映射到将调用该动作的URL,即路径“ / struts / add”映射到URL“ /struts/add.do”。 还有一个forward属性,该属性提供要转发到的URL-在这种情况下为“ /struts/add.jsp”。

<struts-config>
...

<action-mappings>

<action path="/struts/add" forward="/struts/add.jsp"/>
...

</action-mappings>
</struts-config>

Struts2配置具有更多的结构。

<struts>
<include file="struts-default.xml"/>

<package name="struts2" extends="struts-default" namespace="/struts2">

<action name="add" >
<result>/struts2/add.jsp</result>
</action>
...

</package>
</struts>

您可能注意到的第一件事是,有一个includepackage节点,而不是一个动作映射节点。 Struts2通过允许您将配置细分为任意数量的文件来模块化配置。 每个文件具有完全相同的结构,只是名称不同。

包含节点,使用file属性作为文件名,将外部文件的内容插入当前文件。 节点将操作分组在一起,并且name属性的值必须唯一。

在Struts操作配置中,path属性指定了整个URL。 在Struts2中,URL是包的名称空间属性, 操作节点的名称属性和操作扩展名(默认为“ .action”)的串联。 然后,上述动作将由“ /struts2/add.action”调用。

包节点的最后一个属性是extends属性。 包除了提供名称空间分隔外,还提供结构。 通过扩展另一个软件包,您可以访问其配置-操作,结果,拦截器,异常等。上面的“ struts2”软件包扩展了“ struts-default”软件包(在包含的文件“ struts-default.xml”中定义)-这是主包含文件,应该是所有配置的第一行。 通过为结果类型,拦截器和可以使用的更常见的拦截器堆栈提供所有默认配置,可以节省您的键入时间。

最后一个是结果节点,它只是要转发到的URL的值。 我们剩下的是名称类型属性。 除非您从默认值更改这些设置,否则可以将它们省略掉,从而简化配置。 缺省值将为操作返回的“成功”结果呈现JSP。

2.动作配置

调用动作类以提供后端处理,处理结果在配置中定义,并且用户重定向到相应的视图。

这是重定向配置的下一步。 操作节点上有两个附加属性。 type属性提供操作类的包和名称,scope属性确保将任何表单bean(如果使用)放置在请求范围中。

动作配置使用转发节点代替转发属性。 动作可以返回的每个结果都应该有一个节点。

<struts-config>
...

<action-mappings>

<action path="/struts/list" scope="request"
type="com.fdar.articles.infoq.conversion.struts.ListBlogsAction" >
<forward name="success" path="/struts/list.jsp"/>
</action>
...

</action-mappings>
</struts-config>

在动作配置的情况下,Struts2配置的XML与之前类似。 唯一的区别在于,要调用的动作的包和名称是通过动作节点上的class属性提供的。

<struts>
...

<package name="struts2" extends="struts-default" namespace="/struts2">

<default-interceptor-ref name="defaultStack"/>

<action name="list"
class="com.fdar.articles.infoq.conversion.struts2.ListBlogsAction">
<result>/struts2/list.jsp</result>
<interceptor-ref name="basicStack"/>
</action>
...

</package>
</struts>

如果要调用execute方法以外的方法(大多数引用BlogAction类的配置就是这种情况 ),则method属性提供方法的名称。 在下面的示例中,将调用update方法。

<action name="update" method="update"
class="com.fdar.articles.infoq.conversion.struts2.BlogAction" >
...
</action>

区别来自default-interceptor-refinterceptor-ref节点。 在第一部分中,我们看到了在调用操作之前请求如何通过一系列拦截器,这些节点配置了拦截器。 default-interceptor-ref节点提供了拦截程序堆栈的名称,以用作程序包的默认程序。 提供interceptor-ref节点时,它将覆盖默认的拦截器( interceptor-ref节点上的name属性可以引用单个拦截器或先前已配置的一堆拦截器)。 此外,可以提供多个拦截器参考节点,并按照列出的顺序进行处理。

3.重定向后配置

当有其他要求刷新结果页不应重新提交表单时,我们使用的最终配置是提交表单。 这称为“重定向后模式”,或者最近称为“闪存范围”。

由于这是一种表单,因此我们需要指定Struts将使用的ActionForm 。 这是通过在操作节点的name属性中提供表单的名称 (上面配置的)来实现的。 唯一需要做的其他更改是在转发节点中将redirect属性设置为true。

<struts-config>
...

<action-mappings>
<action path="/struts/save"
type="com.fdar.articles.infoq.conversion.struts.SaveBlogEntryAction"
name="blogForm" scope="request">
<forward name="success" redirect="true" path="/struts/list.do"/>
</action>
...

</action-mappings>
</struts-config>

Struts2并没有增加现有的配置,而是通过一种新型的结果提供了重定向后功能。 到目前为止,我们已经使用了默认的“调度”结果类型,但是有许多不同的结果类型可用。 此处使用的结果是“重定向”类型。

<struts>
...

<package name="struts2" extends="struts-default" namespace="/struts2">

<action name="save" method="save"
class="com.fdar.articles.infoq.conversion.struts2.BlogAction" >
<result type="redirect">list.action</result>
<interceptor-ref name="defaultStack"/>
</action>
...

</package>
</struts>

再一次,我们使用默认的结果“成功”。

结语

我们已经在本文中进行了很多介绍,但是有些事情我们没有时间做。 为了更好地了解框架和其他实现选项,以下是要查看的简短列表:

  • 配置拦截器和拦截器堆栈 -请查看struts2核心JAR文件中的“ struts-default.xml”文件作为示例。 为跨应用程序功能创建自己的拦截器可以节省大量时间,“ struts-default.xml”中的示例将向您展示如何配置自己的基于应用程序的拦截器堆栈,其中包括新的拦截器。
  • 配置文件中的通配符模式 -以及键入所有内容,在Struts2中可以选择使用通配符模式。 这是Struts实现对Struts2的移植。
  • 使用ParameterAware接口使用UI属性映射 -代替在类上具有模型/传递/值对象或特定属性,您可以配置Struts2将所有请求或表单属性放置到操作中的映射中。 这模拟了Struts的动态表单功能。

您可能会问自己的另一个问题是“这真的可以在相同的UI上工作吗?” -答案是肯定的。 我已经在本文中包含了示例的完整源代码 ,从中您将看到,唯一必要的更改就是修改所调用URL的扩展名(从“ .do”更改为“ .action”)。 另外,Struts标记库(用于表单)可以很容易地代替JSTL使用。 我会将其作为感兴趣的读者的练习。

在本系列的下一篇也是最后一篇文章中,我们将介绍用户界面。 我们将讨论架构。 研究主题和标签; 谈论验证如何适合图片; 并讨论使用UI组件重用代码的方法。 之后,我们将拥有一个完全转换的应用程序。

翻译自: https://www.infoq.com/articles/migrating-struts-2-part2/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值