我们夏季学期的作业,JavaEE的项目正在进行。在我的反复诱导下,Deltamaster 同学终于动用了AOP,不过很快就出了问题。他用AOP对所有Action类的execute方法进行增强(“advice”),但问题是,增强之后,View中的错误信息就无法输出了。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<s:property value="errorMessage" />

由于AOP是我怂恿的,所以问题还是要由我来解决。Deltamaster如果有什么问题,那我肯定不会先去Google,因为我用Google就是他教的。所以首先,我仔细对比了启用AOP与不启用AOP两种情况下Action的表现,发现完全一致,确定Action没有问题。

由于AOP中我们用的是级别最高的around,有拦截并修改函数返回值的能力。我多次测试了around函数中proceed()函数的返回值在函数中各个点的位置,也没有发现问题。也就是说,around本身的代码也没有错误,但不排除是SpringFramework AOP的Bug使得Controller无法获取到action的返回值。不过,我暂时不会往Bug这块地方想。

如果Controller确实没有获取到action的返回值,也就是,jsp文件没有被调用。我随后在jsp后面加了一句输出语句,然后运行。输出成功了!这就表示该jsp文件确实被调用了,同时也证明了Controller确实获取到了action的返回值,前面AOP有Bug的推论不成立。总之从action到view,中间所有过程均正常。

那么就是errorMessage没有被获得了,在jsp中写errorMessage,实质将调用action类的getErrorMessage方法,我在这个方法中加了句输出语句。测试时这句话并没有输出,也就是说,getErrorMessage没有被调用。但为什么不被调用呢?我在action类中自己写了点get方法,测试发现都没有输出。(此处省略1000+字,关于我是如何验证是否是AOP的Bug造成getErrorMessage失效,以及是否是传回对象的类型无法匹配等等各种怀疑)最后,索性直接写

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<s:property value="class" />

输出了,而且发现是个proxy类,我随即将class改成了class.superclass.name,也就是输出本类的父类的名字,结果是,java.lang.reflect.Proxy,原因终于被发现了,作为一个Java的动态代理类,怎么可能有getErrorMessage方法。我们知道,SpringFramework AOP实现的核心方法是Java动态代理(基于接口的代理)或CGLIB(基于类的代理),这里似乎用的是前者。Proxy类对象代替Action类调用了View,结果当View要求调用者的getErrorMessage方法时,Proxy类没有这样的方法,所以导致出错!

随后我仔细查看了Proxy类的文档,全是类方法,没有任何能获得被代理对象的实例方法。无奈下,直接Google了,很快就找到了解答,很幸运,是中文解答:http://jeooo-li.iteye.com/blog/436931。“只要在Spring的配置文件applicationContext中的<aop:aspectj-autoproxy/>改为<aop:aspectj-autoproxy proxy-target-class="true"/>就可以了。”,经过测试,这个解答是正确的,问题解决。

经过初步测试,发现proxy-target-class="true" 一旦加上后,SpringFramework AOP将使用CGLIB方法而不是动态代理,生成Action类的子类,这样即可以满足AOP增强处理的要求,又完美继承了Action类的所有方法,因此才能解决问题。