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的配置才能深谙其中的精妙!