Struts Recipes 的合著者 George Franciscus 将介绍另外一个重大的 Struts 整合窍门 —— 此次是将 Struts 应用程序导入 Spring 框架。请跟随 George,他将向您展现如何改变 Struts 动做,使得管理 Struts 动做就像管理 Spring beans 那样。结果是一个加强的 web 框架,这个框架能够方便地利用 Spring AOP 的优点。
您确定已经据说过控制反转 (IOC) 设计模式,由于很长一段时间以来一直在流传关于它的信息。若是您在任何功能中使用过 Spring 框架,那么您就知道其原理的做用。在本文中,我利用这一原理把一个 Struts 应用程序注入 Spring 框架,您将亲身体会到 IOC 模式的强大。
将一个 Struts 应用程序整合进 Spring 框架具备多方面的优势。首先,Spring 是为解决一些关于 JEE 的真实世界问题而设计的,好比复杂性、低性能和可测试性,等等。第二,Spring 框架包含一个 AOP 实现,容许您将面向方面技术应用于面向对象的代码。第三,一些人可能会说 Spring 框架只有处理 Struts 比 Struts 处理本身好。可是这是观点问题,我演示三种将 Struts 应用程序整合到 Spring 框架的方法后,具体由您本身决定使用哪种。
我所演示的方法都是执行起来相对简单的,可是它们却具备明显不一样的优势。我为每一种方法建立了一个独立而可用的例子,这样您就能够彻底理解每种方法。请参阅 下载 部分得到完整例子源代码。请参阅 参考资料,下载 Struts MVC 和 Spring 框架。
为何 Spring 这么了不得?
Spring 的创立者 Rod Johnson 以一种批判的眼光看待 Java™ 企业软件开发,而且提议不少企业难题都可以经过战略地使用 IOC 模式(也称做依赖注入)来解决。当 Rod 和一个具备奉献精神的开放源码开发者团队将这个理论应用于实践时,结果就产生了 Spring 框架。简言之,Spring 是一个轻型的容器,利用它可使用一个外部 XML 配置文件方便地将对象链接在一块儿。每一个对象均可以经过显示一个 JavaBean 属性收到一个到依赖对象的引用,留给您的简单任务就只是在一个 XML 配置文件中把它们链接好。
IOC 和 Spring
IOC 是一种使应用程序逻辑外在化的设计模式,因此它是被注入而不是被写入客户机代码中。将 IOC 与接口编程应用结合,就像 Spring 框架那样,产生了一种架构,这种架构可以减小客户机对特定实现逻辑的依赖。请参阅 参考资料 了解更多关于 IOC 和 Spring 的信息。
依赖注入是一个强大的特性,可是 Spring 框架可以提供更多特性。Spring 支持可插拔的事务管理器,能够给您的事务处理提供更普遍的选择范围。它集成了领先的持久性框架,而且提供一个一致的异常层次结构。Spring 还提供了一种使用面向方面代码代替正常的面向对象代码的简单机制。
Spring AOP 容许您使用拦截器 在一个或多个执行点上拦截应用程序逻辑。增强应用程序在拦截器中的日志记录逻辑会产生一个更可读的、实用的代码基础,因此拦截器普遍用于日志记录。您很快就会看到,为了处理横切关注点,Spring AOP 发布了它本身的拦截器,您也能够编写您本身的拦截器。
整合 Struts 和 Spring
与 Struts 类似,Spring 能够做为一个 MVC 实现。这两种框架都具备本身的优势和缺点,尽管大部分人赞成 Struts 在 MVC 方面仍然是最好的。不少开发团队已经学会在时间紧迫的时候利用 Struts 做为构造高品质软件的基础。Struts 具备如此大的推进力,以致于开发团队宁愿整合 Spring 框架的特性,而不肯意转换成 Spring MVC。不必进行转换对您来讲是一个好消息。Spring 架构容许您将 Struts 做为 Web 框架链接到基于 Spring 的业务和持久层。最后的结果就是如今一切条件都具有了。
在接下来的小窍门中,您将会了解到三种将 Struts MVC 整合到 Spring 框架的方法。我将揭示每种方法的缺陷而且对比它们的优势。 一旦您了解到全部三种方法的做用,我将会向您展现一个使人兴奋的应用程序,这个程序使用的是这三种方法中我最喜欢的一种。
三个小窍门
接下来的每种整合技术(或者窍门)都有本身的优势和特色。我偏心其中的一种,可是我知道这三种都可以加深您对 Struts 和 Spring 的理解。在处理各类不一样状况的时候,这将给您提供一个广阔的选择范围。方法以下:
一、 使用 Spring 的 ActionSupport 类整合 Structs
二、 使用 Spring 的 DelegatingRequestProcessor 覆盖 Struts 的 RequestProcessor
三、 将 Struts Action 管理委托给 Spring 框架
装载应用程序环境
不管您使用哪一种技术,都须要使用 Spring 的 ContextLoaderPlugin 为 Struts 的 ActionServlet 装载 Spring 应用程序环境。就像添加任何其余插件同样,简单地向您的 struts-config.xml 文件添加该插件,以下所示:
<plug-in className=
"org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property=
"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
package ca.nexcel.books.actions;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;
import org.springframework.context.ApplicationContext;
import org.springframework.web.struts.ActionSupport;
import ca.nexcel.books.beans.Book;
import ca.nexcel.books.business.BookService;
public class SearchSubmit extends ActionSupport { |(1)
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
DynaActionForm searchForm = (DynaActionForm) form;
String isbn = (String) searchForm.get("isbn");
//the old fashion way
//BookService bookService = new BookServiceImpl();
ApplicationContext ctx =
getWebApplicationContext(); |(2)
BookService bookService =
(BookService) ctx.getBean("bookService"); |(3)
Book book = bookService.read(isbn.trim());
if (null == book) {
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,new ActionError
("message.notfound"));
saveErrors(request, errors);
return mapping.findForward("failure") ;
}
request.setAttribute("book", book);
return mapping.findForward("success");
}
}
让咱们快速思考一下这里到底发生了什么。在 (1) 处,我经过从 Spring 的 ActionSupport 类而不是 Struts 的 Action 类进行扩展,建立了一个新的 Action。在 (2) 处,我使用 getWebApplicationContext() 方法得到一个 ApplicationContext。为了得到业务服务,我使用在 (2) 处得到的环境在 (3) 处查找一个 Spring bean。
这种技术很简单而且易于理解。不幸的是,它将 Struts 动做与 Spring 框架耦合在一块儿。若是您想替换掉 Spring,那么您必须重写代码。而且,因为 Struts 动做不在 Spring 的控制之下,因此它不能得到 Spring AOP 的优点。当使用多重独立的 Spring 环境时,这种技术可能有用,可是在大多数状况下,这种方法不如另外两种方法合适。
窍门 2. 覆盖 RequestProcessor
将 Spring 从 Struts 动做中分离是一个更巧妙的设计选择。分离的一种方法是使用 org.springframework.web.struts.DelegatingRequestProcessor 类来覆盖 Struts 的 RequestProcessor 处理程序,如清单 2 所示:
清单 2. 经过 Spring 的 DelegatingRequestProcessor 进行整合
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="searchForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="isbn" type="java.lang.String"/>
</form-bean>
</form-beans>
<global-forwards type="org.apache.struts.action.ActionForward">
<forward name="welcome" path="/welcome.do"/>
<forward name="searchEntry" path="/searchEntry.do"/>
<forward name="searchSubmit" path="/searchSubmit.do"/>
</global-forwards>
<action-mappings>
<action path="/welcome" forward="/WEB-INF/pages/welcome.htm"/>
<action path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/>
<action path="/searchSubmit"
type="ca.nexcel.books.actions.SearchSubmit"
input="/searchEntry.do"
validate="true"
name="searchForm">
<forward name="success" path="/WEB-INF/pages/detail.jsp"/>
<forward name="failure" path="/WEB-INF/pages/search.jsp"/>
</action>
</action-mappings>
<message-resources parameter="ApplicationResources"/>
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/> |(1)
<plug-in className="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-property property="csntextConfigLocation" value="/WEB-INF/beans.xml"/>
</plug-in>
</struts-config>
我利用了 <controller> 标记来用 DelegatingRequestProcessor 覆盖默认的 Struts RequestProcessor。下一步是在个人 Spring 配置文件中注册该动做,如清单 3 所示:
清单 3. 在 Spring 配置文件中注册一个动做
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
<bean name="/searchSubmit"
class="ca.nexcel.books.actions.SearchSubmit"> |(1)
<property name="bookService">
<ref bean="bookService"/>
</property>
</bean>
</beans>
注意:在 (1) 处,我使用名称属性注册了一个 bean,以匹配 struts-config 动做映射名称。SearchSubmit 动做揭示了一个 JavaBean 属性,容许 Spring 在运行时填充属性,如清单 4 所示:
清单 4. 具备 JavaBean 属性的 Struts 动做
package ca.nexcel.books.actions;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.DynaActionForm;
import ca.nexcel.books.beans.Book;
import ca.nexcel.books.business.BookService;
public class SearchSubmit extends Action {
private BookService bookService;
public BookService getBookService() {
return bookService;
}
public void setBookService(BookService bookService) { | (1)
this.bookService = bookService;
}
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
DynaActionForm searchForm = (DynaActionForm) form;
String isbn = (String) searchForm.get("isbn");
Book book = getBookService().read(isbn.trim()); |(2)
if (null == book) {
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,new ActionError("message.notfound"));
saveErrors(request, errors);
return mapping.findForward("failure") ;
}
request.setAttribute("book", book);
return mapping.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 整合的委托方法
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="searchForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="isbn" type="java.lang.String"/>
</form-bean>
</form-beans>
<global-forwards type="org.apache.struts.action.ActionForward">
<forward name="welcome" path="/welcome.do"/>
<forward name="searchEntry" path="/searchEntry.do"/>
<forward name="searchSubmit" path="/searchSubmit.do"/>
</global-forwards>
<action-mappings>
<action path="/welcome" forward="/WEB-INF/pages/welcome.htm"/>
<action path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/>
<action path="/searchSubmit"
type="org.springframework.web.struts.DelegatingActionProxy" |(1)
input="/searchEntry.do"
validate="true"
name="searchForm">
<forward name="success" path="/WEB-INF/pages/detail.jsp"/>
<forward name="failure" path="/WEB-INF/pages/search.jsp"/>
</action>
</action-mappings>
<message-resources parameter="ApplicationResources"/>
<plug-in className="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-property property="contextConfigLocation" value="/WEB-INF/beans.xml"/>
</plug-in>
</struts-config>
清单 5 是一个典型的 struts-config.xml 文件,只有一个小小的差异。它注册 Spring 代理类的名称,而不是声明动做的类名,如(1)处所示。DelegatingActionProxy 类使用动做映射名称查找 Spring 环境中的动做。这就是咱们使用 ContextLoaderPlugIn 声明的环境。
将一个 Struts 动做注册为一个 Spring bean 是很是直观的,如清单 6 所示。我利用动做映射使用 <bean> 标记的名称属性(在这个例子中是 "/searchSubmit")简单地建立了一个 bean。这个动做的 JavaBean 属性像任何 Spring bean 同样被填充:
清单 6. 在 Spring 环境中注册一个 Struts 动做
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
<bean name="/searchSubmit"
class="ca.nexcel.books.actions.SearchSubmit">
<property name="bookService">
<ref bean="bookService"/>
</property>
</bean>
</beans>
动做委托的优势
动做委托解决方法是这三种方法中最好的。Struts 动做不了解 Spring,不对代码做任何改变就可用于非 Spring 应用程序中。RequestProcessor 的改变不会影响它,而且它能够利用 Spring AOP 特性的优势。
动做委托的优势不止如此。一旦让 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 动做。为了使用拦截器,您须要作三件事:
1. 建立拦截器。
2. 注册拦截器。
3. 声明在何处拦截代码。
这看起来很是简单的几句话却很是强大。例如,在清单 7 中,我为 Struts 动做建立了一个日志记录拦截器。 这个拦截器在每一个方法调用以前打印一句话:
清单 7. 一个简单的日志记录拦截器
package ca.nexcel.books.interceptors;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class LoggingInterceptor implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("logging before!");
}
}
这个拦截器很是简单。before() 方法在拦截点中每一个方法以前运行。在本例中,它打印出一句话,其实它能够作您想作的任何事。下一步就是在 Spring 配置文件中注册这个拦截器,如清单 8 所示:
清单 8. 在 Spring 配置文件中注册拦截器
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
<bean name="/searchSubmit"
class="ca.nexcel.books.actions.SearchSubmit">
<property name="bookService">
<ref bean="bookService"/>
</property>
</bean>
<!-- Interceptors -->
<bean name="logger"
class="ca.nexcel.books.interceptors.LoggingInterceptor"/> |(1)
<!-- AutoProxies -->
<bean name="loggingAutoProxy"
class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator"> |(2)
<property name="beanNames">
<value>/searchSubmit</valuesgt; |(3)
</property>
<property name="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 整合窍门中的每一种都被实现成一个完整可用的应用程序。请参阅 下载 部分仔细研究它们。
java