Как выкинуть исключение из области действия вызывающего абонента? - PullRequest
5 голосов
/ 08 апреля 2009

Я хотел бы создать подпрограмму, которая выполняет некоторые записи, выполняет некоторые другие действия и затем генерирует исключение. Я бы хотел, чтобы эта процедура вызывалась из разных мест. Однако создание исключений в этой подпрограмме означает, что они будут иметь эту подпрограмму в своей трассировке стека. Я бы предпочел, чтобы трассировка стека не сообщала об этой служебной программе. Есть ли способ сделать это, не создавая исключение в вызывающей стороне и передавая его в служебную процедуру?

public static void die(String message) throws MyException {
  log(message);
  ...
  throw new MyException();
}

Для программистов, говорящих на двух языках: Perl / Java: как мне carp в Java?

Ответы [ 9 ]

11 голосов
/ 08 апреля 2009

Вы можете установить трассировку стека для любого исключения, которое хотите выдать:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CarpTest {
    public static void main(String[] args) {
        new CarpTest().run();
    }

    public void run() {
        methodThatCarps();
    }

    private void methodThatCarps() {
        carp("Message");
    }

    private void carp(String message) {
        RuntimeException e = new RuntimeException(message);
        e.fillInStackTrace();
        List<StackTraceElement> stack = new ArrayList<StackTraceElement>(Arrays.asList(e.getStackTrace()));
        stack.remove(0);
        e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()]));
        throw e;
    }
}

Это напечатает следующую трассировку стека во время выполнения:

Exception in thread "main" java.lang.RuntimeException: Message
    at CarpTest.methodThatCarps(CarpTest.java:18)
    at CarpTest.run(CarpTest.java:14)
    at CarpTest.main(CarpTest.java:10)

Обратите внимание, что, как вы хотите, метод "карп" не появляется в трассировке стека. Однако манипулирование трассировкой стека следует выполнять только с осторожностью.

4 голосов
/ 08 апреля 2009

Если вы хотите использовать Исключение для управления потоком и что происходит после этого, рекомендуется переопределить метод fillInStackTrace ():

public Throwable fillInStackTrace() {
   return this;
}

В результате вы получите исключение без трассировки стека и с уменьшенными накладными расходами (заполнение трассировки стека занимает время).

2 голосов
/ 08 апреля 2009

Нет способа удалить функцию выброса из трассировки стека. Вся цель трассировки стека состоит в том, чтобы записать путь исключения, так что разрешение функции отказаться от этой цели.

Единственный способ изменить это, если вы вернули исключение, а не выбросили его. Но это заставляет вас зависеть от вызывающего абонента, который знает, чтобы выдать исключение.

throw die("someReason).fillInStackTrace();

Модифицированная функция

public static Exception die(String message) {  
  log(message);  
  ...  
  return new MyException();
}

РЕДАКТИРОВАТЬ

Добавлен вызов fillInStackTrace (), чтобы гарантировать, что стек сброшен до точки выброса.

http://java.sun.com/j2se/1.3/docs/api/java/lang/Throwable.html#Throwable()

1 голос
/ 08 апреля 2009

Исходя из того, что ordnungswidrig сказал об установке трассировки стека, и того, что неизвестно (Google) о переопределении fillInStackTrace (), я создал CarpException, который выполняет именно то, что я хочу. Обратите внимание, что я обнаружил, что мне пришлось удалить четыре кадра трассировки стека вместо одного, так как я собирал кадры из Throwable и Exception.

public class CarpException extends Exception {
  @Override
  public Throwable fillInStackTrace() {
    super.fillInStackTrace();
    StackTraceElement[] origStackTrace = getStackTrace();
    StackTraceElement[] newStackTrace = new StackTraceElement[origStackTrace.length - 4];
    System.arraycopy(origStackTrace, 4, newStackTrace, 0, origStackTrace.length - 4);
    setStackTrace(newStackTrace);
    return this;
  }
}
1 голос
/ 08 апреля 2009

Может быть, вам стоит рассмотреть проблему с другой стороны. Вместо того, чтобы изменять трассировку стека, почему бы просто не использовать метод генератора исключений (die в вашем примере) вернуть исключение, а не выбросить его? Тогда ваш звонок throw die();.

Например:

// revised die() method:
public static MyException die(String message){
  log(message);
  //...
  return new MyException();
}


// calling code:
throw die("a-whoopsie daisy!");

Теперь, конечно, throw die() может показаться немного неэстетичным, так что вы можете переименовать die() в newException() или что-то в этом роде. Но требование, чтобы метод обработки исключений не отображался в трассировке стека, выполнено - die() (или newException()) возвращается до того, как возникнет исключение, и, следовательно, не является частью стека, подлежащего трассировке.

Редактировать: Мой плохой. Я потратил так много времени на работу с C #, что забыл, что в Java трассировки стека исключений генерируются при создании экземпляра, а в C # / .NET трассировки стека исключений генерируются во время выполнения.

Так что этот трюк будет работать в C #, но не в Java.

1 голос
/ 08 апреля 2009

Ммм ... вы можете создать подкласс исключений и переопределить все методы в нем, а также обернуть исходное исключение. Внутренне создайте новую трассировку стека с помощью метода getStackTrace () из заключенного в исключение исключения. Я не смотрел на источник исключения, но вам, возможно, даже не придется переопределять такое количество методов.

0 голосов
/ 08 апреля 2009

Вы бросаете трассировку стека, чтобы иметь возможность ее анализировать? В этом случае вы можете вызвать метод getStackTrace () для Exception, который возвращает StackTraceElement []. Там вы можете отфильтровать ненужные элементы (например, метод die).

0 голосов
/ 08 апреля 2009

Вы можете рассмотреть возможность получения вашим методом Logger в качестве параметра метода. Это позволит вам управлять выводом журнала в зависимости от вызывающего класса.

Я бы посоветовал не хотеть, чтобы ваше исключение исключало эту часть трассировки стека. Когда вы уходите, и какой-то новый человек начинает поддерживать ваш код, он не оценит эту нестандартную обработку ошибок.

0 голосов
/ 08 апреля 2009

Нет, не могу сделать ... Я пытался сделать что-то подобное некоторое время назад (я пытался перехватить трассировку стека для записи вызовов методов до того, как существовал AOP).

Трассировка стека заполняется при создании исключения, и это делается изначально. Для того, над чем я работал, я в итоге прочитал трассировку стека и посмотрел на второй элемент, но это вам здесь не поможет ...

...