SpringAOP本質(3)

技術小阿哥發表於2017-11-20
Spring AOP很牛,AOP是OOP的補充,而非競爭者。
前面的例子離實際的應用太遙遠。不足以顯式AOP的力量,現在就用AOP前置通知來檢查使用者的身份,只有通過檢查的才能呼叫業務方法。
 
在沒有使用AOP之前,我們是如何實現的?想想看。
 
1、寫一個安全檢查類,又其他類繼承,並在子類的業務方法中呼叫安全檢查的方法。
比如:Struts1時代就是繼承Action,其類結構如下:
package org.apache.struts.action; 

public class Action { 


    public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, ServletRequest servletRequest, ServletResponse servletResponse) throws java.lang.Exception { /* compiled code */ } 


    public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception { /* compiled code */ } 


    …. 


 
在開發中自己實現的UserAction需要繼承Struts的Action,可以考慮做一個抽象,比如叫做CheckAciton,在其中重寫execute方法,並加入安全檢查機制,並且增加一個抽象請求處理方法 

public ActionForward real(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse)throws java.lang.Exception;

作為業務請求處理的方法,放到重寫的execute方法內部呼叫。
 
public class CheckAction extends Action{ 


    public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception { 

    //todo: 安全檢查邏輯 

    return real(actionMapping,actionForm,httpServletRequest,httpServletResponse); 


    } 


    public abstract ActionForward real(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception; 


}
 
這樣以後業務上的別的Aciton只要繼承了CheckAction,還需要實現real方法,別的方法),即可為該Action加入安全檢查的邏輯。
 
public class DoSomethingAction extends CheckAction{ 


    public abstract ActionForward real(ActionMapping actionMapping, ActionForm actionForm, http.HttpServletRequest httpServletRequest, http.HttpServletResponse httpServletResponse) throws java.lang.Exception{ 

    //todo: 單純處理實際的業務請求 

    return … 

    } 


    …. 

}
 
這樣做也很麻煩,還可以使用動態代理為每個業務介面加上安全檢查的邏輯,但是效能更差,更麻煩。
 
這個還算可行的方案,實現也很容易。但是很死板,如果有多種驗證策略就比較棘手了。
 
沒有對比就顯式不出來Spring AOP的優勢。下面看看Spring的優雅處理:
 
/** 

* 使用者登入資訊載體 

*/
 
public class UserInfo { 

    private String userName; 


    private String password; 


    public UserInfo(String userName, String password) { 

        this.userName = userName; 

        this.password = password; 

    } 

     

    public String getPassword() { 

        return password; 

    } 

    public String getUserName() { 

        return userName; 

    } 

}
 
/** 

* 業務元件:被代理的物件 

*/
 
public class SecureBean { 


    /** 

     * 示範性的業務方法,這個方法將被攔截,加入一些附加邏輯 

     */
 

    public void businessOperate() { 

        System.out.println(“業務方法businessOperate()被呼叫了!”); 

    } 

}
 
/** 

* 安全管理類:檢查使用者登入和管理使用者登出登入的業務邏輯。 

*/
 
public class SecurityManager { 

    //為每一個SecurityManager建立一個本地執行緒變數threadLocal,用來儲存使用者登入資訊UserInfo 

    private static ThreadLocal threadLocal = new ThreadLocal(); 


    /** 

     * 使用者登入方法,允許任何使用者登入。 

     * @param userName 

     * @param password 

     */
 

    public void login(String userName, String password) { 

        // 假定任何的使用者名稱和密碼都可以登入 

        // 將使用者登入資訊封裝為UerInfo物件,儲存在ThreadLocal類的物件threadLocal裡面 

        threadLocal.set(new UserInfo(userName, password)); 

    } 


    public void logout() { 

        // 設定threadLocal物件為null 

        threadLocal.set(null); 

        int x = 0; 

    } 


    public UserInfo getLoggedOnUser() { 

        // 從本地執行緒變數中獲取使用者資訊UerInfo物件 

        return (UserInfo) threadLocal.get(); 

    } 

}
 
import java.lang.reflect.Method; 
import org.springframework.aop.MethodBeforeAdvice; 

/** 

* 前置通知類 

*/
 
public class SecurityAdvice implements MethodBeforeAdvice { 


    private SecurityManager securityManager; 


    public SecurityAdvice() { 

        this.securityManager = new SecurityManager(); 

    } 


    /** 

     * 前置通知的介面方法實現。僅允許robh使用者登入,強制設定的。 

     */
 

    public void before(Method method, Object[] args, Object target) 

            throws Throwable { 

        UserInfo user = securityManager.getLoggedOnUser(); 


        if (user == null) { 

            System.out.println(“沒有使用者憑證資訊!,本前置通知僅僅允許robh使用者登入,不信你試試看!”); 

            throw new SecurityException( 

                    “你必須在呼叫此方法” + method.getName() + “前進行登入:”); 

        } else if (“robh”.equals(user.getUserName())) { 

            System.out.println(“使用者robh成功登入:OK!”); 

        } else { 

            System.out.println(“非法使用者”+user.getUserName()+“,請使用robh登入,使用者呼叫的方法是:” + method.getName()); 

            throw new SecurityException(“使用者” + user.getUserName() 

                    + ” 不允許呼叫” + method.getName() + “方法!”); 

        } 

    } 

}


 
import org.springframework.aop.framework.ProxyFactory; 

/** 

* 測試類,客戶端 

*/
 
public class SecurityExample { 


    public static void main(String[] args) { 

        //得到一個 security manager 

        SecurityManager mgr = new SecurityManager(); 


        //獲取一個SecureBean的代理物件 

        SecureBean bean = getSecureBean(); 


        //嘗試用robh登入 

        mgr.login(“robh”“pwd”);   //檢查登入情況 

        bean.businessOperate();     //業務方法呼叫 

        mgr.logout();               //登出登入 


        //嘗試用janm登入 

        try { 

            mgr.login(“janm”“pwd”);       //檢查登入情況 

            bean.businessOperate();         //業務方法呼叫 

        } catch (SecurityException ex) { 

            System.out.println(“發生了異常: “ + ex.getMessage()); 

        } finally { 

            mgr.logout();                   //登出登入 

        } 


        // 嘗試不使用任何使用者名稱身份呼叫業務方法 

        try { 

            bean.businessOperate();         //業務方法呼叫 

        } catch (SecurityException ex) { 

            System.out.println(“發生了異常: “ + ex.getMessage()); 

        } 


    } 


    /** 

     * 獲取SecureBean的代理物件 

     * 

     * @return SecureBean的代理 

     */
 

    private static SecureBean getSecureBean() { 

        //建立一個目標物件 

        SecureBean target = new SecureBean(); 


        //建立一個通知 

        SecurityAdvice advice = new SecurityAdvice(); 


        //獲取代理物件 

        ProxyFactory factory = new ProxyFactory(); 

        factory.setTarget(target); 

        factory.addAdvice(advice); 

        SecureBean proxy = (SecureBean) factory.getProxy(); 


        return proxy; 

    } 

}
 
執行結果:
– Using JDK 1.4 collections 

使用者robh成功登入:OK! 

業務方法businessOperate()被呼叫了! 

非法使用者janm,請使用robh登入,使用者呼叫的方法是:businessOperate 

發生了異常: 使用者janm 不允許呼叫businessOperate方法! 

沒有使用者憑證資訊!,本前置通知僅僅允許robh使用者登入,不信你試試看! 

發生了異常: 你必須在呼叫此方法businessOperate前進行登入: 


Process finished with exit code 0
 
觀察執行結果,精確實現了驗證的要求。
 
這裡從底層觀察Spring AOP的應用,實際應用中最好還是通過xml配置耦合程式碼。只有明白了AOP其中奧祕,使用Spring的配置才能深諳其中的精妙!
本文轉自 leizhimin 51CTO部落格,原文連結:http://blog.51cto.com/lavasoft/75291,如需轉載請自行聯絡原作者


相關文章