代理模式及其在spring與struts2中的體現

代理模式

proxy

 

 

代理模式有三個角色組成:

  1.抽象主題角色:聲明瞭真實主題和代理主題的共同接口。

  2.代理主題角色:內部包含對真實主題的引用,並且提供和真實主題角色相同的接口。

  3.真實主題角色:定義真實的對象。

 

我們先來看傳統方式下一個Proxy的實現實例。

假設我們有一個UserDAO接口及其實現類UserDAOImp:

 

UserDAO.java:

public interface UserDAO {

  public void saveUser(User user);

}

 

UserDAOImp.java:

public class UserDAOImp implements UserDAO{

  public void saveUser(User user) {

   ……

  }

}

 

如果我們希望在UserDAOImp.saveUser方法執行前後追加一些處理過程,如啓動/

提交事務,而不影響外部代碼的調用邏輯,那麼,增加一個Proxy類是個不錯的選擇:

 

UserDAOProxy.java

public class UserDAOProxy implements UserDAO {

 

  private UserDAO userDAO;

 

  public UserDAOProxy(UserDAO userDAO) {

        this.userDAO = userDAO;

  }

 

  public void saveUser(User user) {

    UserTransaction tx = null;

   try {

      tx = (UserTransaction) ( new InitialContext().lookup("java/tx") );

     

      userDAO.saveUser(user);

     

      tx.commit();

 

    } catch (Exception ex) {

     if (null!=tx){

       try {

          tx.rollback();

       }catch(Exception e) {

        }

      }

    }

  }

}

UserDAOProxy同樣是UserDAO接口的實現,對於調用者而言,saveUser方法的使

用完全相同,不同的是內部實現機制已經發生了一些變化――我們在UserDAOProxy中爲

UserDAO.saveUser方法套上了一個JTA事務管理的外殼。

 

上面是靜態Proxy模式的一個典型實現。

現在假設系統中有20個類似的接口,針對每個接口實現一個Proxy,實在是個繁瑣無

味的苦力工程。

動態代理

Dynamic Proxy的出現,爲這個問題提供了一個更加聰明的解決方案。

我們來看看怎樣通過Dynamic Proxy解決上面的問題:

 

public class TxHandler implements InvocationHandler {

 

  private Object originalObject;

 

  public Object bind(Object obj) {

   this.originalObject = obj;

   return Proxy.newProxyInstance(

      obj.getClass().getClassLoader(),

      obj.getClass().getInterfaces(), this);

}

 

public Object invoke(Object proxy, Method  method, Object[] args)

   throws Throwable {

 

    Object result = null;

   if (!method.getName().startsWith("save")) {

      UserTransaction tx = null;

     try {

        tx = (UserTransaction) ( new InitialContext().lookup("java/tx") );

 

        result = method.invoke(originalObject, args);

 

        tx.commit();

 

      } catch (Exception ex) {

       if (null != tx) {

         try {

            tx.rollback();

          } catch (Exception e) {

          }

        }

      }

 

    } else {

      result = method.invoke(originalObject, args);

    }

 

   return result;

  }

 

}

首先注意到,上面這段代碼中,並沒有出現與具體應用層相關的接口或者類引用。也就

是說,這個代理類適用於所有接口的實現。

 

 

其中的關鍵在兩個部分:

         1.

return Proxy.newProxyInstance(

      obj.getClass().getClassLoader(),

      obj.getClass().getInterfaces(), this);

 

java.lang.reflect.Proxy.newProxyInstance方法根據傳入的接口類型

obj.getClass().getInterfaces() 動態構造一個代理類實例返回, 這個代理類是JVM

在內存中動態構造的動態類,它實現了傳入的接口列表中所包含的所有接口。

 

         2

public Object invoke(Object proxy, Method  method, Object[] args)

   throws Throwable {

……

    result = method.invoke(originalObject, args);

   ……

return result;

  }

 

  InvocationHandler.invoke方法將在被代理類的方法被調用之前觸發。通過這個方

法中,我們可以在被代理類方法調用的前後進行一些處理,如代碼中所示,

InvocationHandler.invoke方法的參數中傳遞了當前被調用的方法(Method ,以及被

調用方法的參數。

  同時,我們可以通過Method.invoke方法調用被代理類的原始方法實現。這樣,我們

就可以在被代理類的方法調用前後大做文章。

 

  在示例代碼中,我們爲所有名稱以“save”開頭的方法追加了JTA事務管理。

 

SpringAOP實現

其中最典型的就是Spring中的配置化事務管理,先看一個例子

<beans>

  <bean id="dataSource"

   class="org.apache.commons.dbcp.BasicDataSource"

   destroy-method="close">

 

   <property name="driverClassName">

     <value>org.gjt.mm.mysql.Driver</value>

   </property>

 

   <property name="url">

     <value>jdbc:mysql://localhost/sample</value>

   </property>

 

   <property name="username">

     <value>user</value>

   </property>

 

   <property name="password">

     <value>mypass</value>

   </property>

  </bean>

 

  <bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

   <property name="dataSource">

     <ref local="dataSource" />

   </property>

  </bean>

 

  <bean id="userDAO" class="net.xiaxin.dao.UserDAO">

   <property name="dataSource">

     <ref local="dataSource" />

   </property>

  </bean>

 

  <bean id="userDAOProxy"

  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

   <property name="transactionManager">

     <ref bean="transactionManager" />

   </property>

 

   <property name="target">

     <ref local="userDAO" />

   </property>

 

   <property name="transactionAttributes">

     <props>

       <prop key="insert*">PROPAGATION_REQUIRED</prop>

       <prop key="get*">

PROPAGATION_REQUIRED,readOnly

</prop>

     </props>

   </property>

  </bean>

</beans>

 

 

看看例子當中的userDAOProxy,想必大家已經猜測到Spring事務管理機制的實現原理。

  是的,只需通過一個Dynamic Proxy對所有需要事務管理的Bean進行加載,並根據配

置,在invoke方法中對當前調用的方法名進行判定,併爲其加上合適的事務管理代碼,那

麼就實現了Spring式的事務管理。

 

  當然,Spring中的AOP實現更爲複雜和靈活,不過基本原理一致。

 

 

Stuts2中的AOP實現

攔截器是Strut2的一個重要組成部分,對於Strut2框架而言,可以將其理解爲一個空的容器,正是大量的內建攔截器完成該框架的大部分操作。比如,params攔截器將http請求中的參數解析出來,設置成action的屬性;servlet-config直接將http請求中的httpServletRequest實例和httpServletRespon實例傳給actionfileUpload攔截器負責解析請求當中的文件域,並將文件域設置爲action的屬性。。。。。。

對於Strut2的攔截器體系而言,當我們需要某個攔截器的時候,只需要在配置文件中應用該攔截器即可,反之亦然。不管是否應用某個攔截器,對於整個Strut2框架都不會影響,這種設計哲學,是一種可插拔的設計,具有非常好的可擴展性。

Strut2中的攔截器體系是一種AOP設計哲學,它允許開發人員以一種簡單的方式來進行AOP方式的開發。

下面以一個例子來介紹Strut2中的AOP

 

使用攔截器完成權限控制

l         實現攔截器

大部分web應用都涉及權限控制,當瀏覽者需要執行某個操作時,應用需要先檢查瀏覽者是否登錄,以及是否有足夠的權限來執行該操作。

本示例要求用戶登錄,必須爲制定用戶名纔可以查看系統中某個視圖資源,否則直接轉入登錄頁面。對於上面的需求,可以在每個action執行業務邏輯之前,先執行權限檢查邏輯,但這種做法不利於代碼複用。因爲大部分action中檢查權限代碼都大同小異,故將這些代碼邏輯放在攔截器中,將會更加優雅。

檢查用戶是否登錄,通常都是跟蹤用戶的Session來完成的,通過ActionContext即可訪問到Session中的屬性,攔截器的intercep(ActionInvocation invocation)方法的invocation參數可以很輕易的訪問到請求相關的ActionContex實例。

 

//權限檢查攔截器繼承AbstractIntercept

Public class AuthorityInterceptor extends AbstractInterceptor{

 

   //攔截action處理的攔截方法

   Public String intercept(ActionInvocation invocation){

      //取得請求相關的ActionContex實例

       ActionContext ctx = invocation.getInvocationContext();

       Map session = ctx.getSession();

       //取出名爲usersession屬性

       String user = (String)session.get(“User”);

       If(user !=null && user.equals(“scott”) ){

Return invocation.invoke();

}

Ctx.put(“tip”,”您還沒有登錄,請輸入scotttiger登錄”);

Return Action.Login;

}

上面的攔截器代碼非常簡單,先從ActionInvocation 取得用戶的session實例,然後從中取出user屬性,通過判斷屬性值確定用戶是否登錄,從而判斷是否轉入登錄頁面。

 

l         配置攔截器

一旦實現了攔截器,就可以在所有需要實現權限控制的action中複用上面的攔截器。

爲了使用攔截器,首先需要在struts.xml文件中定義該攔截器,定義攔截器的配置片段如下:

<interceptors>

     <interceptor name=”authority” class=”qj.AuthorityInterceptor” />

</interceptors>

定義了攔截器後,可以在action中應用該攔截器,應用攔截器的配置片段如下:

<action name=”viewBook”>

   <result>/WEB-INF/jsp/viewBook.jsp</result>

   <interceptor-ref  name=”defaultStack” />

   <interceptor-ref  name=”authority” />

</action>

這種通過攔截器控制權限的方式,顯然具有更好的代碼複用。

如果爲了簡化struts.xml文件的配置,避免在每個action中重複配置該攔截器,可以將該攔截器配置成一個默認攔截器棧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章