用
Spring
更好地处理
Struts
动作三种整合
Struts
应用程序与
Spring
的方式
以及和
hibernate
的整合配置
为什么
Spring
这么了不起?
Spring
的创立者
Rod Johnson
以一种批判的眼光看待
Java™
企业软件开发,并且提议很多企业难题都能够通过战略地使用
IOC
模式(也称作依赖注入)来解决。当
Rod
和一个具有奉献精神的开放源码开发者团队将这个理论应用于实践时,结果就产生了
Spring
框架。简言之,
Spring
是一个轻型的容器,利用它可以使用一个外部
XML
配置文件方便地将对象连接在一起。每个对象都可以通过显示一个
JavaBean
属性收到一个到依赖对象的引用,留给您的简单任务就只是在一个
XML
配置文件中把它们连接好。
依赖注入是一个强大的特性,但是
Spring
框架能够提供更多特性。
Spring
支持可插拔的事务管理器,可以给您的事务处理提供更广泛的选择范围。它集成了领先的持久性框架,并且提供一个一致的异常层次结构。
Spring
还提供了一种使用面向方面代码代替正常的面向对象代码的简单机制。
Spring AOP
允许您使用
拦截器
在一个或多个执行点上拦截应用程序逻辑。加强应用程序在拦截器中的日志记录逻辑会产生一个更可读的、实用的代码基础,所以拦截器广泛用于日志记录。您很快就会看到,为了处理横切关注点,
Spring AOP
发布了它自己的拦截器,您也可以编写您自己的拦截器。
IOC
和
Spring
IOC
是一种使应用程序逻辑外在化的设计模式,所以它是被注入而不是被写入客户机代码中。将
IOC
与接口编程应用结合,就像
Spring
框架那样,产生了一种架构,这种架构能够减少客户机对特定实现逻辑的依赖。请参阅
参考资料
了解更多关于
IOC
和
Spring
的信息。
整合
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
整合窍门中的每一种都被实现成一个完整可用的应用程序
2 hibernate+Spring
整合
hibernate
配置
1
(不需要
hibernate.cfg.xml
)
<beans>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/books</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>snrdcqmq</value>
</property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>com/yang/product/booksonline/dao/Members.hbm.xml</value>
</list>
</property>
</bean>
整合
hibernate
配置
2(
需要
hibernate.cfg.xml)
<
bean
id
=
"sessionFactory"
class
=
"org.springframework.orm.hibernate3.LocalSessionFactoryBean"
>
<
property
name
=
"configLocation"
>
<
value
>
/WEB-INF/classes/hibernate.cfg.xml
</
value
>
</
property
>
</
bean
>