您的位置:首页 > 编程语言 > Java开发

Spring 更好地处理 Struts 动作

2007-11-30 16:51 357 查看
您肯定已经听说过控制反转(IOC)设计模式,因为很长一段时间以来一直在流传关于它的信息。如果您在任何功能中使用过Spring框架,那么您就知道其原理的作用。在本文中,我利用这一原理把一个Struts应用程序注入Spring框架,您将亲身体会到IOC模式的强大。

将一个Struts应用程序整合进Spring框架具有多方面的优点。首先,Spring是为解决一些关于JEE的真实世界问题而设计的,比如复杂性、低性能和可测试性,等等。第二,Spring框架包含一个AOP实现,允许您将面向方面技术应用于面向对象的代码。第三,一些人可能会说Spring框架只有处理Struts比Struts处理自己好。但是这是观点问题,我演示三种将Struts应用程序整合到Spring框架的方法后,具体由您自己决定使用哪一种。

我所演示的方法都是执行起来相对简单的,但是它们却具有明显不同的优点。我为每一种方法创建了一个独立而可用的例子,这样您就可以完全理解每种方法。

为什么Spring这么了不起?

Spring的创立者RodJohnson以一种批判的眼光看待Java™企业软件开发,并且提议很多企业难题都能够通过战略地使用IOC模式(也称作依赖注入)来解决。当Rod和一个具有奉献精神的开放源码开发者团队将这个理论应用于实践时,结果就产生了Spring框架。简言之,Spring是一个轻型的容器,利用它可以使用一个外部XML配置文件方便地将对象连接在一起。每个对象都可以通过显示一个JavaBean属性收到一个到依赖对象的引用,留给您的简单任务就只是在一个XML配置文件中把它们连接好。


IOC和Spring

IOC是一种使应用程序逻辑外在化的设计模式,所以它是被注入而不是被写入客户机代码中。将IOC与接口编程应用结合,就像Spring框架那样,产生了一种架构,这种架构能够减少客户机对特定实现逻辑的依赖。请参阅参考资料了解更多关于IOC和Spring的信息。

依赖注入是一个强大的特性,但是Spring框架能够提供更多特性。Spring支持可插拔的事务管理器,可以给您的事务处理提供更广泛的选择范围。它集成了领先的持久性框架,并且提供一个一致的异常层次结构。Spring还提供了一种使用面向方面代码代替正常的面向对象代码的简单机制。

SpringAOP允许您使用拦截器在一个或多个执行点上拦截应用程序逻辑。加强应用程序在拦截器中的日志记录逻辑会产生一个更可读的、实用的代码基础,所以拦截器广泛用于日志记录。您很快就会看到,为了处理横切关注点,SpringAOP发布了它自己的拦截器,您也可以编写您自己的拦截器。

整合Struts和Spring

与Struts相似,Spring可以作为一个MVC实现。这两种框架都具有自己的优点和缺点,尽管大部分人同意Struts在MVC方面仍然是最好的。很多开发团队已经学会在时间紧迫的时候利用Struts作为构造高品质软件的基础。Struts具有如此大的推动力,以至于开发团队宁愿整合Spring框架的特性,而不愿意转换成SpringMVC。没必要进行转换对您来说是一个好消息。Spring架构允许您将Struts作为Web框架连接到基于Spring的业务和持久层。最后的结果就是现在一切条件都具备了。

在接下来的小窍门中,您将会了解到三种将StrutsMVC整合到Spring框架的方法。我将揭示每种方法的缺陷并且对比它们的优点。一旦您了解到所有三种方法的作用,我将会向您展示一个令人兴奋的应用程序,这个程序使用的是这三种方法中我最喜欢的一种。

三个小窍门

接下来的每种整合技术(或者窍门)都有自己的优点和特点。我偏爱其中的一种,但是我知道这三种都能够加深您对Struts和Spring的理解。在处理各种不同情况的时候,这将给您提供一个广阔的选择范围。方法如下:

使用Spring的
ActionSupport
类整合Structs

使用Spring的
DelegatingRequestProcessor
覆盖Struts的
RequestProcessor


将Struts
Action
管理委托给Spring框架

装载应用程序环境

无论您使用哪种技术,都需要使用Spring的
ContextLoaderPlugin
为Struts的
ActionServlet
装载Spring应用程序环境。就像添加任何其他插件一样,简单地向您的struts-config.xml文件添加该插件,如下所示:

<plug-inclassName=
"org.springframework.web.struts.ContextLoaderPlugIn">
<set-propertyproperty=
"contextConfigLocation"value="/WEB-INF/beans.xml"/>
</plug-in>

前面已经提到过,在下载部分,您能够找到这三个完全可使用的例子的完整源代码。每个例子都为一个书籍搜索应用程序提供一种不同的Struts和Spring的整合方法。

窍门1.使用Spring的ActionSupport

手动创建一个Spring环境是一种整合Struts和Spring的最直观的方式。为了使它变得更简单,Spring提供了一些帮助。为了方便地获得Spring环境,
org.springframework.web.struts.ActionSupport
类提供了一个
getWebApplicationContext()
方法。您所做的只是从Spring的
ActionSupport
而不是Struts
Action
类扩展您的动作,如清单1所示:

清单1.使用ActionSupport整合Struts

packageca.nexcel.books.actions;
importjava.io.IOException;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.apache.struts.action.ActionError;
importorg.apache.struts.action.ActionErrors;
importorg.apache.struts.action.ActionForm;
importorg.apache.struts.action.ActionForward;
importorg.apache.struts.action.ActionMapping;
importorg.apache.struts.action.DynaActionForm;
importorg.springframework.context.ApplicationContext;
importorg.springframework.web.struts.ActionSupport;
importca.nexcel.books.beans.Book;
importca.nexcel.books.business.BookService;
publicclassSearchSubmitextendsActionSupport{|(1)
publicActionForwardexecute(
ActionMappingmapping,
ActionFormform,
HttpServletRequestrequest,
HttpServletResponseresponse)
throwsIOException,ServletException{
DynaActionFormsearchForm=(DynaActionForm)form;
Stringisbn=(String)searchForm.get("isbn");

//theoldfashionway
//BookServicebookService=newBookServiceImpl();

ApplicationContextctx=
getWebApplicationContext();|(2)
BookServicebookService=
(BookService)ctx.getBean("bookService");|(3)

Bookbook=bookService.read(isbn.trim());
if(null==book){
ActionErrorserrors=newActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,newActionError
("message.notfound"));
saveErrors(request,errors);
returnmapping.findForward("failure");
}
request.setAttribute("book",book);
returnmapping.findForward("success");
}
}

让我们快速思考一下这里到底发生了什么。在(1)处,我通过从Spring的
ActionSupport
类而不是Struts的
Action
类进行扩展,创建了一个新的
Action
。在(2)处,我使用
getWebApplicationContext()
方法获得一个
ApplicationContext
。为了获得业务服务,我使用在(2)处获得的环境在(3)处查找一个Springbean。

这种技术很简单并且易于理解。不幸的是,它将Struts动作与Spring框架耦合在一起。如果您想替换掉Spring,那么您必须重写代码。并且,由于Struts动作不在Spring的控制之下,所以它不能获得SpringAOP的优势。当使用多重独立的Spring环境时,这种技术可能有用,但是在大多数情况下,这种方法不如另外两种方法合适。

窍门2.覆盖RequestProcessor

将Spring从Struts动作中分离是一个更巧妙的设计选择。分离的一种方法是使用
org.springframework.web.struts.DelegatingRequestProcessor
类来覆盖Struts的
RequestProcessor
处理程序,如清单2所示:

清单2.通过Spring的DelegatingRequestProcessor进行整合

<?xmlversion="1.0"encoding="ISO-8859-1"?>
<!DOCTYPEstruts-configPUBLIC
"-//ApacheSoftwareFoundation//DTDStrutsConfiguration1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-beanname="searchForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-propertyname="isbn"type="java.lang.String"/>
</form-bean>

</form-beans>
<global-forwardstype="org.apache.struts.action.ActionForward">
<forwardname="welcome"path="/welcome.do"/>
<forwardname="searchEntry"path="/searchEntry.do"/>
<forwardname="searchSubmit"path="/searchSubmit.do"/>
</global-forwards>
<action-mappings>
<actionpath="/welcome"forward="/WEB-INF/pages/welcome.htm"/>
<actionpath="/searchEntry"forward="/WEB-INF/pages/search.jsp"/>
<actionpath="/searchSubmit"
type="ca.nexcel.books.actions.SearchSubmit"
input="/searchEntry.do"
validate="true"
name="searchForm">
<forwardname="success"path="/WEB-INF/pages/detail.jsp"/>
<forwardname="failure"path="/WEB-INF/pages/search.jsp"/>
</action>
</action-mappings>
<message-resourcesparameter="ApplicationResources"/>
<controllerprocessorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>|(1)
<plug-inclassName="org.apache.struts.validator.ValidatorPlugIn">
<set-propertyproperty="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
<plug-inclassName="org.springframework.web.struts.ContextLoaderPlugIn">
<set-propertyproperty="csntextConfigLocation"value="/WEB-INF/beans.xml"/>
</plug-in>

</struts-config>


我利用了[code]<controller>
标记来用
DelegatingRequestProcessor
覆盖默认的Struts
RequestProcessor
。下一步是在我的Spring配置文件中注册该动作,如清单3所示:

清单3.在Spring配置文件中注册一个动作
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEbeansPUBLIC"-//SPRING//DTDBEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<beanid="bookService"class="ca.nexcel.books.business.BookServiceImpl"/>
<beanname="/searchSubmit"
class="ca.nexcel.books.actions.SearchSubmit">|(1)
<propertyname="bookService">
<refbean="bookService"/>
</property>
</bean>
</beans>
[/code]
注意:在(1)处,我使用名称属性注册了一个bean,以匹配struts-config
动作映射名称。
SearchSubmit
动作揭示了一个JavaBean属性,允许Spring在运行时填充属性,如清单4
所示:

清单4.具有JavaBean属性的Struts动作

packageca.nexcel.books.actions;
importjava.io.IOException;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.apache.struts.action.Action;
importorg.apache.struts.action.ActionError;
importorg.apache.struts.action.ActionErrors;
importorg.apache.struts.action.ActionForm;
importorg.apache.struts.action.ActionForward;
importorg.apache.struts.action.ActionMapping;
importorg.apache.struts.action.DynaActionForm;
importca.nexcel.books.beans.Book;
importca.nexcel.books.business.BookService;
publicclassSearchSubmitextendsAction{

privateBookServicebookService;
publicBookServicegetBookService(){
returnbookService;
}
publicvoidsetBookService(BookServicebookService){|(1)
this.bookService=bookService;
}
publicActionForwardexecute(
ActionMappingmapping,
ActionFormform,
HttpServletRequestrequest,
HttpServletResponseresponse)
throwsIOException,ServletException{
DynaActionFormsearchForm=(DynaActionForm)form;
Stringisbn=(String)searchForm.get("isbn");

Bookbook=getBookService().read(isbn.trim());|(2)
if(null==book){
ActionErrorserrors=newActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,newActionError("message.notfound"));
saveErrors(request,errors);
returnmapping.findForward("failure");
}
request.setAttribute("book",book);
returnmapping.findForward("success");
}
}

在清单4中,您可以了解到如何创建Struts动作。在(1)处,我创建了一个JavaBean
属性。
DelegatingRequestProcessor
自动地配置这种属性。这种设计使Struts动作并不知道它正被
Spring管理,并且使您能够利用Sping的动作管理框架的所有优点。由于您的Struts动作注意不到Spring的存在,所以您不需要重写您的
Struts代码就可以使用其他控制反转容器来替换掉Spring。

DelegatingRequestProcessor
方法的确比第一种方法好,但是仍然存在一些问题。如果您使用一个不同的
RequestProcessor
,则需要手动整合Spring的
DelegatingRequestProcessor
。添加的代码会造成维护的麻烦并且将来会降低您的应用程序的灵活性。此外,还有过一些使用一系列命令来代替
Struts
RequestProcessor
的传闻。这种改变将会对这种解决方法的使用寿命造成负面的影响。

窍门3.将动作管理委托给Spring

一个更好的解决方法是将Strut动作管理委托给Spring。您可以通过在
struts-config
动作映射中注册一个代理来实现。代理负责在Spring环境中查找Struts动作。由于动作在Spring的控制之下,所以它可以填充动作的JavaBean属性,并为应用诸如Spring的AOP拦截器之类的特性带来了可能。

清单5中的
Action
类与清单4中的相同。但是struts-config有一些不同:

清单5.Spring整合的委托方法

<?xmlversion="1.0"encoding="ISO-8859-1"?>
<!DOCTYPEstruts-configPUBLIC
"-//ApacheSoftwareFoundation//DTDStrutsConfiguration1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-beanname="searchForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-propertyname="isbn"type="java.lang.String"/>
</form-bean>

</form-beans>
<global-forwardstype="org.apache.struts.action.ActionForward">
<forwardname="welcome"path="/welcome.do"/>
<forwardname="searchEntry"path="/searchEntry.do"/>
<forwardname="searchSubmit"path="/searchSubmit.do"/>
</global-forwards>
<action-mappings>
<actionpath="/welcome"forward="/WEB-INF/pages/welcome.htm"/>
<actionpath="/searchEntry"forward="/WEB-INF/pages/search.jsp"/>
<actionpath="/searchSubmit"
type="org.springframework.web.struts.DelegatingActionProxy"|(1)
input="/searchEntry.do"
validate="true"
name="searchForm">
<forwardname="success"path="/WEB-INF/pages/detail.jsp"/>
<forwardname="failure"path="/WEB-INF/pages/search.jsp"/>
</action>
</action-mappings>
<message-resourcesparameter="ApplicationResources"/>
<plug-inclassName="org.apache.struts.validator.ValidatorPlugIn">
<set-property
property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
<plug-in
className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-propertyproperty="contextConfigLocation"value="/WEB-INF/beans.xml"/>
</plug-in>

</struts-config>

清单5是一个典型的struts-config.xml文件,只有一个小小的差别。它注册Spring代理类的名称,而不是声明动作的类名,如(1)处所示。DelegatingActionProxy类使用动作映射名称查找Spring环境中的动作。这就是我们使用
ContextLoaderPlugIn
声明的环境。

将一个Struts动作注册为一个Springbean是非常直观的,如清单6所示。我利用动作映射使用
<bean>
标记的名称属性(在这个例子中是"
/searchSubmit
")简单地创建了一个bean。这个动作的JavaBean属性像任何Springbean一样被填充:

清单6.在Spring环境中注册一个Struts动作

<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEbeansPUBLIC"-//SPRING//DTDBEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<beanid="bookService"class="ca.nexcel.books.business.BookServiceImpl"/>
<beanname="/searchSubmit"
class="ca.nexcel.books.actions.SearchSubmit">
<propertyname="bookService">
<refbean="bookService"/>
</property>
</bean>
</beans>

动作委托的优点

动作委托解决方法是这三种方法中最好的。Struts动作不了解Spring,不对代码作任何改变就可用于非Spring应用程序中。
RequestProcessor
的改变不会影响它,并且它可以利用SpringAOP特性的优点。

动作委托的优点不止如此。一旦让Spring控制您的Struts动作,您就可以使用Spring给动作补充更强的活力。例如,没有Spring的话,所有的Struts动作都必须是线程安全的。如果您设置
<bean>
标记的singleton属性为“false”,那么不管用何种方法,您的应用程序都将在每一个请求上有一个新生成的动作对象。您可能不需要这种特性,但是把它放在您的工具箱中也很好。您也可以利用Spring的生命周期方法。例如,当实例化Struts动作时,
<bean>
标记的init-method属性被用于运行一个方法。类似地,在从容器中删除bean之前,destroy-method属性执行一个方法。这些方法是管理昂贵对象的好办法,它们以一种与Servlet生命周期相同的方式进行管理。

拦截Struts

前面提到过,通过将Struts动作委托给Spring框架而整合Struts和Spring的一个主要的优点是:您可以将Spring的AOP拦截器应用于您的Struts动作。通过将Spring拦截器应用于Struts动作,您可以用最小的代价处理横切关注点。

虽然Spring提供很多内置拦截器,但是我将向您展示如何创建自己的拦截器并把它应用于一个Struts动作。为了使用拦截器,您需要做三件事:

创建拦截器。

注册拦截器。

声明在何处拦截代码。

这看起来非常简单的几句话却非常强大。例如,在清单7中,我为Struts动作创建了一个日志记录拦截器。这个拦截器在每个方法调用之前打印一句话:

清单7.一个简单的日志记录拦截器

packageca.nexcel.books.interceptors;
importorg.springframework.aop.MethodBeforeAdvice;
importjava.lang.reflect.Method;
publicclassLoggingInterceptorimplementsMethodBeforeAdvice{
publicvoidbefore(Methodmethod,Object[]objects,Objecto)throwsThrowable{
System.out.println("loggingbefore!");
}
}

这个拦截器非常简单。
before()
方法在拦截点中每个方法之前运行。在本例中,它打印出一句话,其实它可以做您想做的任何事。下一步就是在Spring配置文件中注册这个拦截器,如清单8所示:

清单8.在Spring配置文件中注册拦截器

<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEbeansPUBLIC"-//SPRING//DTDBEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<beanid="bookService"class="ca.nexcel.books.business.BookServiceImpl"/>
<beanname="/searchSubmit"
class="ca.nexcel.books.actions.SearchSubmit">
<propertyname="bookService">
<refbean="bookService"/>
</property>
</bean>
<!--Interceptors-->
<beanname="logger"
class="ca.nexcel.books.interceptors.LoggingInterceptor"/>|(1)
<!--AutoProxies-->
<beanname="loggingAutoProxy"
class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">|(2)
<propertyname="beanNames">
<value>/searchSubmit</valuesgt;|(3)
</property>
<propertyname="interceptorNames">
<list>
<value>logger</value>|(4)
</list>
</property>
</bean>
</beans>

您可能已经注意到了,清单8扩展了清单6中所示的应用程序以包含一个拦截器。具体细节如下:

在(1)处,我注册了这个拦截器。

在(2)处,我创建了一个bean名称自动代理,它描述如何应用拦截器。还有其他的方法定义拦截点,但是这种方法常见而简便。

在(3)处,我将Struts动作注册为将被拦截的bean。如果您想要拦截其他的Struts动作,则只需要在"beanNames"下面创建附加的
<value>
标记。

在(4)处,当拦截发生时,我执行了在(1)处创建的拦截器bean的名称。这里列出的所有拦截器都应用于“beanNames”。

就是这样。就像这个例子所展示的,将您的Struts动作置于Spring框架的控制之下,为处理您的Struts应用程序提供了一系列全新的选择。在本例中,使用动作委托可以轻松地利用Spring拦截器提高Struts应用程序中的日志记录能力。

结束语

在本文中,您已经学习了将Struts动作整合到Spring框架中的三种窍门。使用Spring的
ActionSupport
来整合Struts(第一种窍门中就是这样做的)简单而快捷,但是会将Struts动作与Spring框架耦合在一起。如果您需要将应用程序移植到一个不同的框架,则需要重写代码。第二种解决方法通过委托
RequestProcessor
巧妙地解开代码的耦合,但是它的可扩展性不强,并且当Struts的
RequestProcessor
变成一系列命令时,这种方法就持续不了很长时间。第三种方法是这三种方法中最好的:将Struts动作委托给Spring框架可以使代码解耦,从而使您可以在您的Struts应用程序中利用Spring的特性(比如日志记录拦截器)。

三种Struts-Spring整合窍门中的每一种都被实现成一个完整可用的应用程序。请参阅下载部分仔细研究它们。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐