Spring AOP 本質(3)
Spring AOP很牛,AOP是OOP的補充,而非競爭者。
前面的例子離實際的應用太遙遠。不足以顯式AOP的力量,如今就用AOP前置通知來檢查用戶的身份,只有經過檢查的才能調用業務方法。
在沒有使用AOP以前,咱們是如何實現的?想一想看。
一、寫一個安全檢查類,又其餘類繼承,並在子類的業務方法中調用安全檢查的方法。
好比: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的配置才能深諳其中的精妙!