我们夏季学期的作业,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类的所有方法,因此才能解决问题。

少有的一篇与Linux无关的文章啊。话说我们已经进入了夏季学期,要求使用 Struts 2 + Spring + Hibernate 完成一个Web应用程序。我现在在积极学习中。配置Struts 2绝对是一件看RP的事情,配置反复出现问题。最早出现的错误就是 404 ,网页没找到。后来我不知道做了什么事,就解决了。。而现在的HTTP Status 404 - No result defined for action XxxAction and result yyy错误,就比较经典了。连老师看了半天,也丝毫没有解决办法。Google也毫无结果。不过后来倒是给了我启发。

我发现BuildPath里同样的包我其实加了两遍,WebContent/WEB-INF/lib里放入指定的jar包后,事实上就已经被添加进Eclipse了。我开始时没有发现,再添加了一遍,这样就添加了两遍。

后来将这些额外添加的包去掉,问题解决。

一直不知道Java中Finally的作用,因为总感觉没有什么用,事实上,当初学Javascript时也有同样的疑问,后来Google下,才明白过来。

	public void writeFile(String filePath, String fileName, String args) throws IOException
{
    FileWriter fw = new FileWriter(filePath + fileName);
    try 
    {
        fw.write(args);
    }
    catch (IOException e)
    {
    //1
        fw.close();
        throw e;
    }
    //2
    fw.close();
}
 
这段代码创建了一个FileWriter object,并调用 write 方法。在退出该方法之前,您必须关闭FileWriter object,以避免资源漏洞。为了完成这一任务,我们在 //2 处调用 close,它是该方法的最后一条语句。但是,如果 try 块中发生一个异常会怎么样呢?在这种情况下,//2 处的 close 调用永远不会发生。因此,您必须捕获这个异常,并在重新发出这个异常之前在 //1 处插入对 close 的另一个调用。这样就可以确保在退出该方法之前关闭FileWriter object。这样编写代码既麻烦又易于出错,但在没有 finally 的情况下这是必不可少的。有了 finally,前面的代码就可以重写为以下的形式:finally 关键字是对 Java 异常处理模型的最佳补充。 finally 结构使代码总会执行,而不管有无异常发生。使用 finally 可以维护对象的内部状态,并可以清理非内存资源。如果没有 finally,您的代码就会很费解。例如,下面的代码说明,在不使用 finally 的情况下您如何编写代码来释放非内存资源:
 
	public void writeFile(String filePath, String fileName, String args) throws IOException
{
    FileWriter fw = new FileWriter(filePath + fileName);
    try 
    {
        fw.write(args);
    }
    catch (IOException e)
    {
        throw e;
    }
    finally 
    {
        fw.close();
    }
}
 
finally 块确保 close 方法总被执行,而不管 try 块内是否发出异常(或是try块中有突如其来的return)。因此,可以确保在退出该方法之前总会调用 close 方法。这样您就可以确信FileWriter object被关闭并且您没有泄漏资源。
 
参考文献:
tech.ccidnet.com/art/3737/20060510/549039_1.html
hi.baidu.com/laoyouji2008/blog/item/d96467092d28f02f6b60fbb6.html
 
对于一些无可奈何的二元操作(神马是二元操作?二元操作是指当调用一个函数后必须成对调用另一个函数,这两个函数总是成对使用,否则可能会导致严重错误。比如文件的打开和关闭,加锁和释放锁。二元操作本身不是难点,但是人们非常容易忘记调用第二个函数,从而造成隐患,这在一个代码块中有多个如return,break,continue之类跳转语句时极易发生,以前有本书希望我们永远在代码块中只设定一个出口,但是这么做难度很大),由于Java的析构函数总不是很给力(无法确定何时调用,是否会调用,不能用它来包装第二个操作,这和C++完全不一样),我们完全可以使用Finally来完成第二个操作,这样就不会遗忘了。我们只要让第一个操作的那个代码块被try包围,第二个操作写在之后的finally中,catch可以不需要,这样无论接下来有多少个可能导致跳出代码块的语句,第二次操作总会被执行,毋须任何担忧。