Spring的AOP动态调用的反思
以往做项目的时候,用到了SPRING的AOP来做权限拦截。但项目里用的是Strut1,用的最多的Action是DispatchAction。所配置好一切以后,竟然发现不能有效的拦截DispatchAction里的各个方法。通过对DispatchAction源码的查看,发现原来DispatchAction是通过动态调用来执行各个不同的方法的~
代码如下:
public abstract class DispatchAction extends BaseAction/* */ {/* 98 */ protected static Log log = LogFactory.getLog(DispatchAction.class);/* */ protected Class clazz;/* */ protected HashMap methods;/* */ protected Class[] types;/* */ /* */ public DispatchAction()/* */ {/* 105 */ this.clazz = super.getClass();/* */ /* 113 */ this.methods = new HashMap();/* */ /* 119 */ this.types = new Class[] { ActionMapping.class, ActionForm.class, HttpServletRequest.class, HttpServletResponse.class };/* */ }/* */ /* */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)/* */ throws Exception/* */ { //首先执行这里,程序的入口/* 145 */ if (isCancelled(request)) {/* 146 */ ActionForward af = cancelled(mapping, form, request, response);/* */ /* 148 */ if (af != null) {/* 149 */ return af;/* */ }/* */ /* */ }/* */ /* 154 */ String parameter = getParameter(mapping, form, request, response); //获取strut配置文件中的parameter的参数--通常为method/* */ /* 157 */ String name = getMethodName(mapping, form, request, response, parameter);/* */ /* 161 */ if (("execute".equals(name)) || ("perform".equals(name))) {/* 162 */ String message = messages.getMessage("dispatch.recursive", mapping.getPath());/* */ /* 165 */ log.error(message);/* 166 */ throw new ServletException(message);/* */ }/* */ /* 170 */ return dispatchMethod(mapping, form, request, response, name); //根据method动态调用方法/* */ }/* */ /* */ protected ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)/* */ throws Exception/* */ {/* 191 */ String message = messages.getMessage("dispatch.parameter", mapping.getPath(), mapping.getParameter());/* */ /* 195 */ log.error(message);/* */ /* 197 */ throw new ServletException(message);/* */ }/* */ /* */ protected ActionForward cancelled(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)/* */ throws Exception/* */ {/* 219 */ return null;/* */ }/* */ /* */ protected ActionForward dispatchMethod(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String name)/* */ throws Exception/* */ {/* 244 */ if (name == null) {/* 245 */ return unspecified(mapping, form, request, response);/* */ }/* */ /* 249 */ Method method = null;/* */ try/* */ {/* 252 */ method = getMethod(name);/* */ } catch (NoSuchMethodException e) {/* 254 */ String message = messages.getMessage("dispatch.method", mapping.getPath(), name);/* */ /* 257 */ log.error(message, e);/* */ /* 259 */ String userMsg = messages.getMessage("dispatch.method.user", mapping.getPath());/* */ /* 261 */ throw new NoSuchMethodException(userMsg);/* */ }/* */ /* 264 */ ActionForward forward = null;/* */ try/* */ {/* 267 */ Object[] args = { mapping, form, request, response };/* */ /* 269 */ forward = (ActionForward)method.invoke(this, args); //这里就是通过动态的调用,来执行各个方法的/* */ } catch (ClassCastException e) {/* 271 */ String message = messages.getMessage("dispatch.return", mapping.getPath(), name);/* */ /* 274 */ log.error(message, e);/* 275 */ throw e;/* */ } catch (IllegalAccessException e) {/* 277 */ String message = messages.getMessage("dispatch.error", mapping.getPath(), name);/* */ /* 280 */ log.error(message, e);/* 281 */ throw e;/* */ }/* */ catch (InvocationTargetException e)/* */ {/* 285 */ Throwable t = e.getTargetException();/* */ /* 287 */ if (t instanceof Exception) {/* 288 */ throw ((Exception)t);/* */ }/* 290 */ String message = messages.getMessage("dispatch.error", mapping.getPath(), name);/* */ /* 294 */ log.error(message, e);/* 295 */ throw new ServletException(t);/* */ }/* */ /* 300 */ return forward;/* */ }/* */ /* */ protected String getParameter(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)/* */ throws Exception/* */ {/* 318 */ String parameter = mapping.getParameter();/* */ /* 320 */ if (parameter == null) {/* 321 */ String message = messages.getMessage("dispatch.handler", mapping.getPath());/* */ /* 324 */ log.error(message);/* */ /* 326 */ throw new ServletException(message);/* */ }/* */ /* 330 */ return parameter;/* */ }/* */ /* */ protected Method getMethod(String name)/* */ throws NoSuchMethodException/* */ {/* 344 */ synchronized (this.methods) {/* 345 */ Method method = (Method)this.methods.get(name);/* */ /* 347 */ if (method == null) {/* 348 */ method = this.clazz.getMethod(name, this.types);/* 349 */ this.methods.put(name, method);/* */ }/* */ /* 352 */ return method;/* */ }/* */ }/* */ /* */ protected String getMethodName(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String parameter)/* */ throws Exception/* */ {/* 374 */ return request.getParameter(parameter);/* */ }/* */ }于是乎对spring的AOP进行测试,想看看到底是不是因为动态调用方法,所以才导致aop无法对DispatchAction 进行有效的拦截。
测试代码如下:
首先定义一个切入点
@Component("aspectDemo")@Aspectpublic class AspectDemo {// 定义切入点表达式private final static String EXP = "execution(* cn.gs..*.*(..))";@Around(EXP)public Object around(ProceedingJoinPoint point) throws Throwable {System.out.println("Around Before " + point.getSignature().getName());// 调用目标对象的方法并获取返回值Object o = point.proceed(point.getArgs());System.out.println("Around After " + point.getSignature().getName());return o;}}接着是拦截类的方法
@Service("dynamicProxy")public class DynamicProxy {public Object withMethodToExecute(String name) throws Exception { // 调用了withMethodToExecuteMethod method = getMethod(name);Object obj = method.invoke(this, name); // 此处动态调用了method1return obj;}public Object withMethodToExecute2(String name) throws Exception { // 调用了withMethodToExecutemethod1(""); //直接调用method1return null;}public String method1(String methodName) {System.out.println(" execute method1");return methodName;}public Method getMethod(String name) throws NoSuchMethodException {Method method = this.getClass().getMethod(name, String.class);return method;}}spring配置文件的设置,开启了自动扫描和动态代理
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <aop:aspectj-autoproxy/><context:component-scan base-package="cn.gs"/> </beans>
Junit的测试类如下
public class ManTest {static DynamicProxy dynamicProxy;@BeforeClasspublic static void setUpBeforeClass() throws Exception {try {ApplicationContext act = new ClassPathXmlApplicationContext("beans.xml");dynamicProxy = (DynamicProxy) act.getBean("dynamicProxy");} catch (RuntimeException e) {e.printStackTrace();}}@Testpublic void testSay() throws Exception {dynamicProxy.withMethodToExecute("method1"); //动态调用method1System.out.println("============================="); dynamicProxy.withMethodToExecute2("method1"); //直接调用method1System.out.println("ok"); }}打印结果
Around Before withMethodToExecute
execute method1
Around After withMethodToExecute
=============================
Around Before withMethodToExecute2
execute method1
Around After withMethodToExecute2
ok
结果发现无法是动态调用method1方法还是直接调用,都无法打印两遍,也就是说我所希望的打印结果应该是这样的
Around Before withMethodToExecute
Around Before method1
execute method1
Around After method1
Around After withMethodToExecute
=============================
Around Before withMethodToExecute2
Around Before method1
execute method1
Around After method1
Around After withMethodToExecute2
ok
事实证明,AOP是无法对方法里面的方法进行递归的拦截。也就是如果一个方法里有多个符合AOP所要拦截的方法的话,那么是无法拦截到里面的方法的。这应该跟AOP的代理实现有关系。
**头一次发帖!有不对的地方请指正。欢迎拍砖。 1 楼 kingwon 2010-02-27 其实有更深层次的原因:
AspectJ增强编译的字节码时,是不会修改你的方法实现的,凡是调用this.xxx的仍然是原来的方法;反射亦然
也有一个规避的方法:凡是用到this的地方,用从spring得到的对象代替
2 楼 sword.cai 2010-02-28 要想用到spring增强,必须用spring ioc里的proxy bean引用
而this引用到了具体实现proxy.target里具体bean了,所以得不到增强
3 楼 sundoctor 2010-03-01 为什么要递归拦截呢,我个人觉得权限不应该在action上拦截,应该拦截到业务层上就可以了。 4 楼 linsongbin1 2010-03-02 在service上进行拦截合理。 5 楼 YiSingQ 2010-03-02 AOP拦截不应该设在Action上,应该在业务逻辑层的方法上。 6 楼 redhat 2010-03-02 最简单就是做个web filter或者listener。当然在使用struts时可以使用intercepter,这个要看版本是否支持。 7 楼 flynofry 2010-03-03 这点在《spring揭秘》一书中提到了,里面有讲到解决的办法。