Birden Çok @ControllerAdvice @ExceptionHandlers Önceliğini Ayarlama


83

@ControllerAdviceHer biri içinde bir @ExceptionHandleryöntem bulunan , açıklamalı çoklayıcı sınıflarım var .

Biri, Exceptiondaha spesifik bir işleyici bulunmazsa, bunun kullanılması gerektiği niyetiyle ele alınır.

Ne yazık ki Spring MVC Exception, daha spesifik olanlardan ( IOExceptionörneğin) her zaman en genel durumu ( ) kullanıyor gibi görünüyor .

Spring MVC'nin böyle davranması beklenir mi? Jersey'den bir model taklit etmeye çalışıyorum, bu her bir ExceptionMapperbileşeni (eşdeğer bileşeni) ele aldığı bildirilen türün atılan istisnadan ne kadar uzakta olduğunu belirlemek için ve her zaman en yakın atayı kullanıyor.

Yanıtlar:


127

Spring MVC'nin böyle davranması beklenir mi?

Spring 4.3.7'den itibaren, Spring MVC şu şekilde davranır: HandlerExceptionResolverİşleyici yöntemleri tarafından oluşturulan istisnaları işlemek için örnekleri kullanır .

Varsayılan olarak, web MVC yapılandırması tek bir HandlerExceptionResolverbean kaydeder , a HandlerExceptionResolverComposite,

diğerlerinin listesine delegeler HandlerExceptionResolvers.

Bu diğer çözücüler

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

bu sırayla kayıtlı. Bu sorunun amacı için sadece önemsiyoruz ExceptionHandlerExceptionResolver.

Bir AbstractHandlerMethodExceptionResolveraracılığıyla özel durumları çözer @ExceptionHandleryöntemlerle.

Bağlam başlatma sırasında Spring, algıladığı ControllerAdviceBeanher @ControllerAdviceaçıklamalı sınıf için bir tane oluşturur . ExceptionHandlerExceptionResolverBağlamdan bunlar almak ve bunları kullanarak kullanarak sıralanır AnnotationAwareOrderComparatorhangi

Statik olarak tanımlanmış bir açıklama değerini (varsa) geçersiz kılan Sıralı bir örnek tarafından sağlanan bir sipariş değeriyle OrderComparatorSpring'in Ordered arayüzünü @Orderve @Priorityek açıklamaları destekleyen bir uzantısıdır .

Daha sonra ExceptionHandlerMethodResolver, bu ControllerAdviceBeanörneklerin her biri için bir tane kaydeder (mevcut @ExceptionHandleryöntemleri, işlemesi gereken istisna türleriyle eşleme ). Bunlar nihayet aynı sırayla a'ya eklenir LinkedHashMap(yineleme sırasını korur).

Bir istisna meydana geldiğinde, ExceptionHandlerExceptionResolverbunları yineleyecek ExceptionHandlerMethodResolverve istisnayı işleyebilen ilkini kullanacaktır.

Yani buradaki nokta şudur: eğer @ControllerAdvicebir @ExceptionHandlerfor a sahipseniz, Exceptionbaşka bir @ControllerAdvicesınıftan önce @ExceptionHandlerdaha özel bir istisna ile kayıt olursanız IOException, o ilk çağrılır. Daha önce belirtildiği gibi, kendi alarak bu kaydı sırasını kontrol edebilir @ControllerAdviceaçıklamalı sınıf uygulamak Orderedveya açıklayarak @Orderveya @Priorityve ona uygun bir değer veren.


5
Ayrıca, a içinde birden fazla @ExceptionHandleryöntem olması durumunda @ControllerAdvice, atılan istisnanın en spesifik üst sınıfını işleyen yöntem seçilir.
Vijay Aggarwal

Spring boot 2.3.3'te, bir kontrolör tavsiyesini geçersiz kılan bir alt sınıfta @Order ek açıklaması gerektirmez Bir üst kontrolör tavsiye sınıfından ExceptionHandler yöntemi
Vadiraj Purohit

93

Sotirios Delimanolis cevabında çok yardımcı oldu, daha fazla araştırmada 3.2.4 baharında @ControllerAdvice ek açıklamalarını arayan kodun ayrıca @Order ek açıklamalarının varlığını da kontrol ettiğini ve ControllerAdviceBeans listesini sıraladığını gördük.

@Order bilgi notu olmayan tüm denetleyiciler için ortaya çıkan varsayılan sıra Sıralı # DÜŞÜK_PRECEDENCE, yani en düşük önceliğe sahip olması gereken bir denetleyiciniz varsa TÜM denetleyicilerinizin daha yüksek bir sıraya sahip olması gerekir.

Burada, bir UserProfileException veya RuntimeException oluştuğunda uygun yanıtlar sunabilen ControllerAdvice ve Order ek açıklamaları ile iki istisna işleyici sınıfına sahip olmayı gösteren bir örnek verilmiştir.

class UserProfileException extends RuntimeException {
}

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
class UserProfileExceptionHandler {
    @ExceptionHandler(UserProfileException)
    @ResponseBody
    ResponseEntity<ErrorResponse> handleUserProfileException() {
        ....
    }
}

@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
class DefaultExceptionHandler {

    @ExceptionHandler(RuntimeException)
    @ResponseBody
    ResponseEntity<ErrorResponse> handleRuntimeException() {
        ....
    }
}
  • Bkz. ControllerAdviceBean # initOrderFromBeanType ()
  • Bkz. ControllerAdviceBean # findAnnotatedBeans ()
  • Bkz. ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()

Zevk almak!


21

İstisna işleyicilerin sırası, @Orderaçıklama kullanılarak değiştirilebilir .

Örneğin:

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomExceptionHandler {

    //...

}

@Order'ın değeri herhangi bir tam sayı olabilir.


5

Belgelerde şunu da buldum:

https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.html#getExceptionHandlerMethod-org.springframework. web.method.HandlerMethod-java.lang.Exception-

ExceptionHandlerMethod

korumalı ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, Exception istisna)

Verilen istisna için bir @ExceptionHandler yöntemi bulun. Varsayılan uygulama, önce denetleyicinin sınıf hiyerarşisindeki yöntemleri arar ve bulunmazsa, bazı @ControllerAdvice Yay yönetimli çekirdekler algılandığını varsayarak ek @ExceptionHandler yöntemlerini aramaya devam eder . Parametreler: handlerMethod - istisnanın ortaya çıktığı yöntem (boş olabilir) istisna - yükseltilmiş istisna Döndürür: istisnayı işlemek için bir yöntem veya null

Bu, bu sorunu çözmek istiyorsanız, bu istisnaları atan denetleyiciye özel istisna işleyicinizi eklemeniz gerektiği anlamına gelir. ANd, Global varsayılan istisna işleyiciyi kullanan bir ve yalnızca ControllerAdvice'i tanımlamak için

Bu, süreci basitleştirir ve sorunu çözmek için Sipariş ek açıklamasına ihtiyacımız yoktur.


2

Benzer bir durum , İlkbahar blogundaki Küresel İstisna Yönetimi başlıklı bölümde yer alan mükemmel " Bahar MVC'sinde İstisna Yönetimi " yazısında birleşti . Senaryoları, istisna sınıfında kayıtlı ResponseStatus ek açıklamalarını kontrol etmeyi ve varsa, çerçevenin bunları işlemesine izin vermek için istisnayı yeniden atmayı içerir. Bu genel taktiği kullanabilirsiniz - orada daha uygun bir işleyici olup olmadığını belirlemeye çalışın ve yeniden fırlatın.

Alternatif olarak, bunun yerine bakabileceğiniz bazı başka istisna yönetimi stratejileri vardır.


1

İşlenecek Önemli Sınıf:

**@Order(Ordered.HIGHEST_PRECEDENCE)**
public class FunctionalResponseEntityExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(FunctionalResponseEntityExceptionHandler.class);

    @ExceptionHandler(EntityNotFoundException.class)
    public final ResponseEntity<Object> handleFunctionalExceptions(EntityNotFoundException ex, WebRequest request)
    {
        logger.error(ex.getMessage() + " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.getMessage(),
                request.getDescription(false),HttpStatus.NOT_FOUND.toString());
        return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}

Düşük Öncelikli Diğer İstisnalar

@ControllerAdvice
    public class GlobalResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
    {
    private final Logger logger = LoggerFactory.getLogger(GlobalResponseEntityExceptionHandler.class);
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request)
    {
        logger.error(ex.getMessage()+ " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.toString(),
                request.getDescription(false),HttpStatus.INTERNAL_SERVER_ERROR.toString());
    }
    }

0

aşağıdaki gibi bir sayı değeri de kullanabilirsiniz

@Order(value = 100)

Daha düşük değerler daha yüksek önceliğe sahiptir. Varsayılan değer * {@code Ordered.LOWEST_PRECEDENCE} şeklindedir ve en düşük önceliği gösterir (diğer * belirtilen sipariş değerlerinden herhangi birine kaybetme)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.