Chcę utworzyć program obsługi wyjątków, który przechwyci wszystkie kontrolery w moim projekcie. Czy to możliwe? Wygląda na to, że muszę umieścić metodę obsługi w każdym kontrolerze. Dzięki za pomoc. Mam kontroler sprężyny, który wysyła odpowiedź Json. Więc jeśli zdarzy się wyjątek, chcę wysłać odpowiedź o błędzie, którą można kontrolować z jednego miejsca.

20
fastcodejava 19 lipiec 2011, 08:32
1
Co dokładnie ma zrobić przewodnik?
 – 
Bozho
19 lipiec 2011, 09:45
- pytanie zostało zaktualizowane.
 – 
fastcodejava
24 lipiec 2011, 03:05
Widzę. mieliśmy ten sam problem i zdecydowaliśmy się rozszerzyć klasę bazową, która definiuje procedurę obsługi (zgodnie z sugestią gouki)
 – 
Bozho
24 lipiec 2011, 10:06

3 odpowiedzi

Najlepsza odpowiedź

(Znalazłem sposób na zaimplementowanie go w Spring 3.1, jest to opisane w drugiej części tej odpowiedzi)

Zobacz rozdział 16.11 Obsługa wyjątki Spring Reference

Jest więcej sposobów niż użycie @ExceptionHandler (zobacz odpowiedź goukiego)


W Spring 3.2+ można opisać klasę za pomocą @ControllerAdvice, wszystkie @ExceptionHandler metody w tej klasie działają globalnie.


W Wiosnie 3.1 nie ma @ControllerAdvice. Ale przy odrobinie hacka można mieć podobną funkcję.

Kluczem jest zrozumienie sposobu działania @ExceptionHandler. W Spring 3.1 istnieje klasa ExceptionHandlerExceptionResolver. Ta klasa implementuje (z pomocą swoich nadklas) interfejs HandlerExceptionResolver i odpowiada za wywoływanie metod @ExceptionHandler.

Interfejs HandlerExceptionResolver ma tylko jedną metodę:

ModelAndView resolveException(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler,
                              Exception ex);`.

Gdy żądanie było obsługiwane przez metodę kontrolera Spring 3.x, ta metoda (reprezentowana przez org.springframework.web.method.HandlerMethod) jest parametrem handler.

ExceptionHandlerExceptionResolver używa handler (HandlerMethod), aby uzyskać klasę Controller i przeskanować ją w poszukiwaniu metod oznaczonych @ExceptionHandler. Jeśli jedna z tych metod pasuje do wyjątku (ex), wówczas te metody są wywoływane w celu obsługi wyjątku. (w przeciwnym razie null zostanie zwrócony, aby zasygnalizować, że ten mechanizm rozpoznawania wyjątków nie czuje się odpowiedzialny).

Pierwszym pomysłem byłoby zaimplementowanie własnego HandlerExceptionResolver, które zachowuje się jak ExceptionHandlerExceptionResolver, ale zamiast szukać @ExceptionHandler w klasie kontrolera, powinno szukać ich w jednym specjalnym ziarnku. Wadą byłoby to, że trzeba (skopiować (lub podklasę ExceptionHandlerExceptionResolver) i trzeba) skonfigurować wszystkie ładne konwertery komunikatów, programy rozpoznawania argumentów i obsługi zwracanych wartości (konfiguracja rzeczywistej i tylko ExceptionHandlerExceptionResolver odbywa się automatycznie przez sprężynę). Wpadłem więc na inny pomysł:

Zaimplementuj prosty HandlerExceptionResolver, który "przesyła" wyjątek do (już skonfigurowanego) ExceptionHandlerExceptionResolver, ALE ze zmodyfikowanym handler, który wskazuje na ziarno zawierające globalne procedury obsługi wyjątków (nazywam je globalne, ponieważ wykonują pracę dla wszystkich kontrolerów).

A oto implementacja: GlobalMethodHandlerExeptionResolver

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;


public class GlobalMethodHandlerExeptionResolver
             implements HandlerExceptionResolver, Ordered {

    @Override
    public int getOrder() {
        return -1; //
    }

    private ExceptionHandlerExceptionResolver realExceptionResolver;

    private List<GlobalMethodExceptionResolverContainer> containers;

    @Autowired
    public GlobalMethodHandlerExeptionResolver(
            ExceptionHandlerExceptionResolver realExceptionResolver,
            List<GlobalMethodExceptionResolverContainer> containers) {
        this.realExceptionResolver = realExceptionResolver;
        this.containers = containers;
    }

    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {              
        for (GlobalMethodExceptionResolverContainer container : this.containers) {    
            ModelAndView result = this.realExceptionResolver.resolveException(
                    request,
                    response,
                    handlerMethodPointingGlobalExceptionContainerBean(container),
                    ex);
            if (result != null)
                return result;
        }
        // we feel not responsible
        return null;
    }


    protected HandlerMethod handlerMethodPointingGlobalExceptionContainerBean(
                               GlobalMethodExceptionResolverContainer container) {
        try {
            return new HandlerMethod(container,
                                     GlobalMethodExceptionResolverContainer.class.
                                          getMethod("fakeHanderMethod"));            
        } catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException(e);
        }            
    }
}

Globalny Handler musi zaimplementować ten interfejs (w celu znalezienia i zaimplementowania fakeHanderMethod używanego dla handler

public interface GlobalMethodExceptionResolverContainer {
    void fakeHanderMethod();
}

I przykład dla globalnego Handlera:

@Component
public class JsonGlobalExceptionResolver
             implements GlobalMethodExceptionResolverContainer {

    @Override
    public void fakeHanderMethod() {
    }


    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ValidationErrorDto handleMethodArgumentNotValidException(
                MethodArgumentNotValidException validationException,
                Locale locale) {

         ...
         /* map validationException.getBindingResult().getFieldErrors()
          * to ValidationErrorDto (custom class) */
         return validationErrorDto;
    }
}

BTW: Nie musisz rejestrować GlobalMethodHandlerExeptionResolver, ponieważ Spring automatycznie rejestruje wszystkie ziarna, które implementują HandlerExceptionResolver dla rozwiązywania wyjątków. Więc wystarczy prosty <bean class="GlobalMethodHandlerExeptionResolver"/>.

25
Alexander Ziubin 5 grudzień 2019, 15:20
Dobra odpowiedź. Czy można to zrobić za pomocą adnotacji? Mamy kontrolery, które zwracają odpowiedź Json, a nie widok.
 – 
fastcodejava
20 lipiec 2011, 06:01
1
@fastcodejava: Domyślam się, że nie ma standardowych adnotacji. W każdym razie możesz zbudować swój własny.
 – 
Ralph
20 lipiec 2011, 10:07
2
Rozdział „Obsługa wyjątków” znajduje się w 17.11 w najnowszym podręczniku Springa. Zobacz static.springsource .org/wiosna/docs/3.2.x/…
 – 
Robert M.
18 styczeń 2013, 13:15
@fastcodejava: Jeśli nadal jesteś zainteresowany: znalazłem sposób, jak zrobić @ExceptionHandler metody odpowiedzialne za kontrolery. - zobacz moją rozszerzoną odpowiedź.
 – 
Ralph
25 sierpień 2013, 21:49
Skorzystałem z @ControllerAdvice. Obsługuje wyjątek i zwraca wyznaczoną stronę błędu, ale problem polega na tym, że nic nie jest zapisywane w dziennikach. Czy możesz mi powiedzieć, jak włączyć logowanie w tym celu?
 – 
Nikhil
11 czerwiec 2015, 18:14

Od wersji Spring 3.2 możesz używać @ControllerAdvice adnotacja. Możesz zadeklarować @ExceptionHandler w ramach klasy @ControllerAdvice w takim przypadku obsługuje wyjątki od metod @RequestMapping ze wszystkich kontrolerów.

@ControllerAdvice
public class MyGlobalExceptionHandler {

    @ExceptionHandler(value=IOException.class)
    public @ResponseBody String iOExceptionHandler(Exception ex){
        //
        //
    }

    // other exception handler methods
    // ...

}
15
Vahe Harutyunyan 18 kwiecień 2013, 10:54

Klasa abstrakcyjna, w której zdefiniujesz obsługę wyjątków. A następnie spraw, aby twoje kontrolery go odziedziczyły.

9
gouki 19 lipiec 2011, 09:26
1
Definiowanie ExceptionHandler w klasie abstrakcyjnej nie działa w moim przypadku (wiosna 4.1.4).
 – 
Klapsa2503
9 marzec 2018, 13:41