在 Struts 中整合 Spring


Struts要與Spring結合使用,主要的方法就是讓Struts知道Spring的存在,以讓Spring與其管理相關的組件,避免在程式中直接撰寫相關組件的依賴關係建立。

首先要在Struts的struts-config.xml中使用<plug-in>標籤註冊org.springframework.web.struts.ContextLoaderPlugIn:
...
    <plug-in className="org.springframework.web.
                → struts.ContextLoaderPlugIn">
        <set-property property="contextConfigLocation"
value="/WEB-INF/beans-config.xml, /WEB-INF/..."/>
    </plug-in>
...

有幾個方法可以讓您取得Spring所管理的Bean,方法之一是Struts的Action改繼承 org.springframework.web.struts.ActionSupport,這個類別是Struts的Action抽象類別的實作,您 可以使用它的getWebApplicationContext()來取得ApplicationContext的實例,然後進一步取得Spring容器 所管理的Bean實例,例如:
...
public class SomeAction extends ActionSupport {
    public ActionForward execute(ActionMapping mapping,
                                ActionForm form,
                                HttpServletRequest req,
                                HttpServletResponse res)
                                                throws Exception {
        ApplicationContext context =
                                    getWebApplicationContext();

        SomeBean bean = (SomeBean) context.getBean("some");
        ...
        return mapping.findForward("view");
    }
}
...

這種方式符合Struts的使用習慣,透過繼承來實作Action,並可得到Spring管理組件間關係的好處,但壞處就是Spring與Struts的API耦合在一起,而且在Action中包括了取得相依物件的邏輯,這並沒有善用Spring管理依賴注入的好處。

另一個方式是讓Struts的Action直接繼承它自己的抽象Action類,但讓Spring來管理Struts的Action物件,讓Action 物件也成為Spring容器管理下的一個Bean,這麼一來就可以直接使用依賴注入的方式注入相依物件,例如可以重新修改一下第一個 Struts 程式 中的HelloAction類別:
  • HelloAction.java
package onlyfun.caterpillar;

import java.util.*;
import javax.servlet.http.*;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class HelloAction extends Action {
private UserChecker userChecker;

public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String username = request.getParameter("user");
username = this.getUserChecker().check(username);

Map model = new HashMap();
model.put("username", username);
request.setAttribute("userInfo", model);

return mapping.findForward("helloUser");
}

public UserChecker getUserChecker() {
return userChecker;
}

public void setUserChecker(UserChecker userChecker) {
this.userChecker = userChecker;
}
}

其中UserChecker類別的定義如下所示:
  • UserChecker.java
package onlyfun.caterpillar;

public class UserChecker {
public String check(String username) {
if(username != null) {
return username;
}
else {
return "guest";
}
}
}

UserChecker模擬商務層的一個檢查權限的物件,您要在Struts的Action中使用到UserChecker的實例,這可以交由 Spring來為您作依賴注入,您可以將HelloAction與UserChecker的實例都交給Spring容器來管理,例如在Bean定義檔中這 麼定義:
  • beans-config.xml
<?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="userChecker"
class="onlyfun.caterpillar.UserChecker"/>

<bean name="/hello"
class="onlyfun.caterpillar.HelloAction">
<property name="userChecker">
<ref bean="userChecker"/>
</property>
</bean>
</beans>

Action的實例現在已納入Spring的管理,那麼Struts在請求轉發時,要有一個中間代理機制,當請求要轉發至指定的Action之前,先轉發 至代理物件,由代理物件通知Spring以取得Spring所管理的Action實例來處理請求,並將處理結果返回給代理物件,再由代理物件返回給 Struts,這可以在struts-config.xml中使用 org.springframework.web.struts.DelegatingActionProxy的實例來作代理,例如:
  • beans-config.xml
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">
<struts-config>
<action-mappings>
<action
path="/hello"
type="org.springframework.web.
→ struts.DelegatingActionProxy">
<forward
name="helloUser"
path="/WEB-INF/jsp/hello.jsp"/>
</action>
</action-mappings>

<plug-in className="org.springframework.web.
→ struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/beans-config.xml"/>
</plug-in>
</struts-config>

在定義檔中注意使用了<plug-in>標籤加入ContextLoaderPlugIn,並指定了Spring的Bean定義檔之位置與名稱。

注意beans-config.xml中HelloAction的"name"屬性設定為"/hello",則<action>中的 "path"屬性也必須設定為"/hello",DelegatingActionProxy是藉著這個來找到Action實例並進行請求處理的,這個方 法的缺點就是要花功夫在兩個定義檔的名稱比對上,並不是那麼的方便。

您可以使用Spring的org.springframework.web.struts.DelegatingRequestProcessor來取代Struts自己的RequestProcessor,在struts-config.xml中定義:
...
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation"
                  value="/WEB-INF/beans-config.xml"/>
</plug-in>   

<controller processorClass="org.springframework.web.struts.DelegatingRequestProcessor"/> 
...


這一次,可以直接將Struts的Action類別名稱寫在struts-config.xml,看來會比較直覺,例如:
...
<action path="/hello"
            type="onlyfun.caterpillar.HelloAction"/>
...

事實上並不會使用到"type"屬性的設定,撰寫出來只是為了看來比較清楚Action使用了哪一個類別,簡潔的寫法只要這樣就可以了:
...
<action path="/someAction"/>
...

同樣的,當請求轉發時,會由代理物件將請求轉發至Bean定義檔中具有相同名稱(/hello)的Action實例來處理。