整合struts,就需要将action的实例交由spring去处理,由此可想,我们需要在项目开始的时候就将action的注入对象加载到spring的工厂里。除了添加struts的依赖,还需要添加struts和spring整合用的插件
1 <dependency>
2 <groupId>org.apache.struts</groupId>
3 <artifactId>struts2-spring-plugin</artifactId>
4 <version>2.3.7</version>
5 </dependency>
6
7 <dependency>
8 <groupId>org.apache.struts</groupId>
9 <artifactId>struts2-core</artifactId>
10 <version>2.3.7</version>
11 </dependency>
除了在测试类中能取得工厂,通过BeanId在工厂中获取实例的情况,其他时候一般是通过自动扫描并注入,因此,在web.xml中,除了struts的过滤器,还需要额外添加spring上下文的监听器,在项目部署的时候就将action添加到工厂
1 <listener>
2 <listener-class>
3 org.springframework.web.context.ContextLoaderListener
4 </listener-class>
5 </listener>
6 <context-param>
7 <param-name>contextConfigLocation</param-name>
8 <param-value>classpath*:beans.xml</param-value>
9 </context-param>
因为需要action被spring进行管理,还需要在struts.xml配置中设置以下内容
1 <constant name="struts.objectFactory"
2 value="org.apache.struts2.spring.StrutsSpringObjectFactory" />
3 <constant name="struts.devMode" value="true" />
4
5 <package name="user" extends="struts-default" namespace="/">
6 <action name="*" class="userAction" method="{1}">
7 <result name="success">/user{1}.jsp</result>
8 </action>
9 </package>
通过spring进行管理之后,action的class配置就无需再写详细的类目录,直接写作注入类的BeanId
1 package org.duyt.action;
2
3 import java.util.List;
4
5 import javax.annotation.Resource;
6
7 import org.duyt.domain.User;
8 import org.duyt.service.IuserService;
9 import org.springframework.context.annotation.Scope;
10 import org.springframework.stereotype.Controller;
11
12 import com.opensymphony.xwork2.ActionSupport;
13 import com.opensymphony.xwork2.ModelDriven;
14
15 @Controller("userAction")
16 //对于action,需要额外指定Scope为prototype,默认是单例模式
17 @Scope("prototype")
18 public class UserAction extends ActionSupport implements ModelDriven<User>{
19
20 private static final long serialVersionUID = 2698940294947436354L;
21
22 private User user;
23 private List<User> userList;
24
25 @Resource
26 private IuserService userService;
27
28
29 public String list() {
30
31 userList = userService.listUser();
32
33 return SUCCESS;
34 }
35
36 public User getModel() {
37 if (user == null) {
38 user = new User();
39 }
40 return user;
41 }
42
43 //get/set方法略
44
45 }
46
上述配置之后,就完成了对struts的整合,但是整合之后,可能会遇到以下的问题:
1:关于声明式事务的切入点表达式。虽然执行对数据库操作的环节是Dao,但是声明却不能在Dao进行事务处理,毕竟一个Dao对数据库的操作不一定能代表一个完整的事务。所以,切入点应该设置在service接口上,一个具体的业务处理应该包含一个完整的事务。拿最经典的银行转账的例子来说,一个账户的金额转出操作和另一个账户的转入操作应该设定在一个service的方法里,当事务对这个方法生效,那么不论转入或者转出任何一个操作出现异常,那么整个操作都会回滚。倘若事务设定在Dao上,那么转入和转出分别为两个方法,其中一个出现异常之后(一般也就是转出出现异常),会出现打钱收不到,钱都不知道去哪儿了的情况,这是我们最不想看到的。可能你会说,把转入和转出设定在Dao的一个方法中不就可以了,对,这样的话是可以解决问题,但是这样会导致层和层之间的分工不明确,相互渗透的情况,Dao只是对数据库的访问,他不应该涉及过分的业务逻辑,就像控制层应该把更多的精力放在跳转或者参数传递上,而不应该和专门控制业务处理的service层抢活干。还有就是在Dao一个方法里进行业务逻辑操作可能会涉及Dao之间的嵌套调用,会出现问题。
2:在页面上使用查询时。可能会出现以下的异常
1 org.hibernate.LazyInitializationException: could not initialize proxy - no Session
2 at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:132)
3 at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:174)
4 at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
5 at org.duyt.domain.Address_$$_javassist_0.toString(Address_$$_javassist_0.java)
6 ...
这是由于hibernate的懒加载机制引起的,由于声明式事务将事务设定在service的接口,导致service方法执行完毕就会关闭session,那么到了页面上需要取得懒加载才能取得的信息时,会导致出现没有session的状态,那么如何解决呢。可以通过一个过滤器,使用Threadlocal在请求之初就开启session,这样,整个流程都可以取得session,请求结束之时再关闭session。新增一个过滤器,将spring工厂中的sessionfactory取得,通过该工厂新建session
1 package org.duyt.filter;
2
3 import java.io.IOException;
4
5 import javax.servlet.Filter;
6 import javax.servlet.FilterChain;
7 import javax.servlet.FilterConfig;
8 import javax.servlet.ServletException;
9 import javax.servlet.ServletRequest;
10 import javax.servlet.ServletResponse;
11
12 import org.hibernate.Session;
13 import org.hibernate.SessionFactory;
14 import org.springframework.web.context.WebApplicationContext;
15 import org.springframework.web.context.support.WebApplicationContextUtils;
16
17 public class OpenSessionInViewFilter implements Filter{
18
19 //spring工厂
20 private static WebApplicationContext wac;
21 //hibernateSessionFactory
22 private static SessionFactory sessionFactory;
23 //线程间共享的session
24 private static ThreadLocal<Session> sessionholder = new ThreadLocal<Session>();
25
26 private static void setSession(Session session){
27 sessionholder.set(session);
28 }
29
30 public static Session getSession(){
31 return sessionholder.get();
32 }
33
34 private static void removeSession(){
35 sessionholder.remove();
36 }
37
38 public void destroy() {
39
40 }
41
42 public void doFilter(ServletRequest req, ServletResponse resp,
43 FilterChain chain) throws IOException, ServletException {
44 try {
45 //执行操作之前先设定session
46 if (sessionFactory != null) {
47 setSession(sessionFactory.openSession());
48 }
49 chain.doFilter(req, resp);
50 } finally{
51 //操作完毕之后在移除session
52 removeSession();
53 }
54 }
55
56 public void init(FilterConfig cfg) throws ServletException {
57 //将spring的工厂加载到属性
58 //这里不要使用new classpathXml.. 去新建工厂,应该使用项目启动时创建的工厂
59 //如下获取
60 wac = WebApplicationContextUtils.getWebApplicationContext(cfg.getServletContext());
61 sessionFactory = (SessionFactory) wac.getBean("sessionFactory");
62 }
63
64 }
65
之后,在Dao中进行数据库操作时就需要使用过滤器创建的session
1 OpenSessionInViewFilter.getSession().XXX
spring本身也提供了一个叫OpenSessionInViewFilter的过滤器,能帮助我们解决懒加载时没有session的问题,看下web.xml
1 <!-- 实现openSessionInView -->
2 <!-- 注意,本过滤器要放在struts过滤器的前面,否则会失效 -->
3 <filter>
4 <filter-name>OpenSessionInViewFilter</filter-name>
5 <!-- 这是自定义的filter -->
6 <!-- <filter-class>org.duyt.filter.OpenSessionInViewFilter</filter-class> -->
7
8 <!-- 这是spring提供的 OpenSessionInViewFilter,使用spring自带的就不用再DAO的实现中使用filter获取session-->
9 <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
10 </filter>
11 <filter-mapping>
12 <filter-name>OpenSessionInViewFilter</filter-name>
13 <url-pattern>/*</url-pattern>
14 </filter-mapping>