问题: 我在我的应用程序中调用了外部方法并且想捕获它可能抛出的异常我能捕获javalangException吗?
答案: 通过一个给定的方法去处理所有运行时和检测异常对于预防外部错误是不充分的
你可以去读目前 JavaWorld文章 Java Tip : When Catching Exception Dont Cast Your Net Too Wide这篇文章警告了捕获javalangException和javalangThroable是不好的捕获你能指定的异常对于代码的可维护性是十分重要的然而这个规则依赖于特殊的环境如果你不打算你的程序崩溃并且保留你的数据结构的安全异常那么你必须捕获被抛出的真正的异常
举个例子想象你有一个加载了这个接口的服务器应用
public interface IFoo
{
/**
* This method cant throw any checked exceptionsor can it?
*/
void bar ();
} // End of interface
对于给出参数的理由是让我们通知你这样的服务在什么地方并且不同的IFoo实现能够从外部资源加载上你写如下代码
try
{
IFoo foo = // get an IFoo implementation
foobar ();
}
catch (RuntimeException ioe)
{
// Handle ioe
}
catch (Error e)
{
// Handle or rethrow e
}
并且你在这个里处理了所有可能的异常你不需要在这里加上任何捕获javaioIOException的异常因为IFoo实现没有从IFoobar()中抛出它对吗?(事实上如果你加上了捕获javaioIOException异常块编译器可能会把它作为不可到达的异常而丢弃)
错误在我写的EvilFoo类中bar()方法证明了将抛出你传递给类构造器的任何异常
public void bar ()
{
EvilThrowthrowThrowable (m_throwthis);
}
运行Main方法
public class Main
{
public static void main (final String[] args)
{
// This try/catch block appears to intercept all exceptions that
// IFoobar() can throw; however this is not true
try
{
IFoo foo = new EvilFoo (new javaioIOException (SURPRISE!));
foobar ();
}
catch (RuntimeException ioe)
{
// Ignore ioe
}
catch (Error e)
{
// Ignore e
}
}
} // End of class
你将看到从bar()方法抛出的javaioIOException异常实例并且没有任何捕获块
>java cp classes Main
Exception in thread main javaioIOException: SURPRISE!
at Mainmain(Mainjava:)
在这里发生了什么?
主要的观察是通常针对检测异常的Java规则仅仅在编译的时候被执行在运行的时候一个JVM不能保证被一个方法抛出的异常是否和在这个方法中声明的抛出异常相匹配因为调用方法的职责是捕获和处理所有从调用方法抛出的异常任何没有被调用方法声明的异常将不予理睬并且拒绝调用栈
如果正常行为是编译器执行那么我怎么创建EvilFoo的?至少有两个方法可以去创建抛出没有声明的异常的Java方法
Threadstop(Throwable)在一些时候不被赞成使用但是它仍然被使用并且传递一个Throwable给被调用的Thread
分别编译你能在编译EvilFoo时候不去编译真正声明bar()方法抛出检测异常的IFoo临时版本
我用后一种选择我编译开始定义的EvilThrow类
public abstract class EvilThrow
{
public static void throwThrowable (Throwable throwable)
throws Throwable
{
throw throwable;
}
}
接下来我用Byte Code Engineering Library(BCEL)的JasminVisitor分解结果在汇编代码中删除throwThrowable()方法Throwable的声明并且用Jasmin assembler 编译新的版本
如果你编写捕获异常的构造器那么它应该总是捕获javalangThrowable而不仅仅只捕获javalangException这个规则适合你开发管理运行时的应用程序和必须执行可能包含错误甚至恶意代码的外部组件你要确保捕获Throwable并且过滤掉错误信息
下面示例说明了如果你没有遵循这个建议将发生什么
Example: Breaking SwingUtilitiesinvokeAndWait()
javaxswingSwingUtilitiesinvokeAndWait()是在AWT上执行一个线程的有用方法当一个应用程序线程必须更新图形用户接口并且服从所有Swing线程规则的时候这个方法将被调用一个没有捕获Runnablerun()抛出的异常将被捕获并且被封装在一个InvocationTragetException中重新抛出
Sun的JSE假设这样一个未捕获的异常仅仅是javalangException的子类这里是一个SwingUtilitiesinvokeAndWait()调用javaawteventInvocationEvent的一个分析
public void dispatch() {
if (catchExceptions) {
try {
runnablerun();
}
catch (Exception e) {
exception = e;
}
}
else {
runnablerun();
}
if (notifier != null) {
synchronized (notifier) {
notifiernotifyAll();
}
}
}
这段代码的问题是如果runnablerun()抛出一个Throwable捕获块又没有并且notifiernotifyAll()从来不会被执行然后调用应用线程将等待在javaawtEventQueueinvokeAndWait()里的一个非公共锁对象(lockwait()将从未执行)
public static void invokeAndWait(Runnable runnable)
throws InterruptedException InvocationTargetException {
class AWTInvocationLock {}
Object lock = new AWTInvocationLock();
InvocationEvent event =new InvocationEvent(ToolkitgetDefaultToolkit() runnable lock
true);
synchronized (lock) {
ToolkitgetEventQueue()postEvent(event);
lockwait();
}
Exception eventException = eventgetException();
if (eventException != null) {
throw new InvocationTargetException(eventException);
}
}
让EvilFoo实现Runnable接口
public void run ()
{
bar ();
}
然后在Main中调用它
SwingUtilitiesinvokeAndWait (new EvilFoo (new Throwable (SURPRISE!)));
正如你看到的未受信任代码使你的代码进入你没有准备处理的执行路径中的异常被保护起来