代理模式
代理模式有三個角色組成:
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事務管理。
Spring中AOP實現
其中最典型的就是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實例傳給action;fileUpload攔截器負責解析請求當中的文件域,並將文件域設置爲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(); //取出名爲user的session屬性 String user = (String)session.get(“User”); If(user !=null && user.equals(“scott”) ){ Return invocation.invoke(); } Ctx.put(“tip”,”您還沒有登錄,請輸入scott、tiger登錄”); 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中重複配置該攔截器,可以將該攔截器配置成一個默認攔截器棧。