整合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上,那么轉入和轉出分別為兩個方法,其中一個出現異常之后(一般也就是轉出出現異常),會出現打錢收不到,錢都不知道去哪兒了的情況,這是我們最不想看到的??赡苣銜f,把轉入和轉出設定在Dao的一個方法中不就可以了,對,這樣的話是可以解決問題,但是這樣會導致層和層之間的分工不明確,相互滲透的情況,Dao只是對數據庫的訪問,他不應該涉及過分的業務邏輯,就像控制層應該把更多的精力放在跳轉或者參數傳遞上,而不應該和專門控制業務處理的service層搶活干。還有就是在Dao一個方法里進行業務邏輯操作可能會涉及Dao之間的嵌套調用,會出現問題。
2:在頁面上使用查詢時??赡軙霈F以下的異常
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>