2016-05-19 3 views
3

У меня есть контроллер, содержащий метод show для отображения информации о предоставленном объекте.@ExceptionHandler не перехватывает исключения, которые выбрасываются из Spring Formatters

@Controller 
@RequestMapping("/owners") 
public class OwnersController { 

    @RequestMapping(value = "/{owner}", method = RequestMethod.GET, 
      produces = MediaType.TEXT_HTML_VALUE) 
    public String show(@PathVariable Owner owner, Model model) { 
    // Return view 
    return "owners/show"; 
    } 
} 

Чтобы вызвать эту операцию, я использую URL-адрес http://localhost:8080/owners/1. Как вы могли видеть, я предоставляю идентификатор владельца 1.

Чтобы иметь возможность конвертировать идентификатор 1 в действительный элемент Владелец, необходимо определить Spring Formatter и зарегистрировать его по методу addFormatters от WebMvcConfigurerAdapter.

У меня есть следующие OwnerFormatter:

public class OwnerFormatter implements Formatter<Owner> { 

    private final OwnerService ownerService; 
    private final ConversionService conversionService; 

    public OwnerFormatter(OwnerService ownerService, 
     ConversionService conversionService) { 
    this.ownerService = ownerService; 
    this.conversionService = conversionService; 
    } 

    @Override 
    public Owner parse(String text, Locale locale) throws ParseException { 
    if (text == null || !StringUtils.hasText(text)) { 
     return null; 
    } 
    Long id = conversionService.convert(text, Long.class); 
    Owner owner = ownerService.findOne(id); 
    if(owner == null){ 
     throw new EntityResultNotFoundException(); 
    } 
    return owner; 
    } 

    @Override 
    public String print(Owner owner, Locale locale) { 
    return owner == null ? null : owner.getName(); 
    } 
} 

Как вы могли видеть, Я использовать findOne метод получения Владелец с идентификатором 1. Но, что happends если этот метод возвращает нуль, потому что нет какой-либо есть Владелец с id 1?

Чтобы предотвратить это, я выбрал пользовательское исключение EntityResultNotFoundException. Это исключение содержит следующий код:

public class EntityResultNotFoundException extends RuntimeException { 
    public EntityResultNotFoundException() { 
     super("ERROR: Entity not found"); 
    } 
} 

Я хочу настроить проект, чтобы иметь возможность вернуться errors/404.html, когда это исключение бросков, так, после Spring documentation, у меня есть два варианта:

Вариант а)

Конфигурирование @ControllerAdvice с @ExceptionHandler аннотированный метод, который управляет EntityResultNotFoundException:

@ControllerAdvice 
public class ExceptionHandlerAdvice { 
    @ExceptionHandler(EntityResultNotFoundException.class) 
    public String handleEntityResultNotFoundException(){ 
     return "errors/404"; 
    } 
} 

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

Похоже, что Springformatter Spring, который вызывается до реализации контроллера, не контролируется @ControllerAdvice.

Для меня это не имеет смысла, потому что Spring Formatter был вызван перед вызовом метода контроллера для подготовки необходимых параметров предоставленного @RequestMapping ... поэтому Spring знает, какой метод будет вызываться ... почему Spring Formatter, используемый для подготовки запроса, не контролируется @ControllerAdvice, чтобы поймать все возможные исключения, возникшие во время этого «процесса подготовки»?

ОБНОВЛЕНИЕ: Как сказал Serge Ballesta по его ответу, @ControllerAdvice реализован как совет АОП вокруг методов контроллера. Таким образом, он не имеет возможности перехватить исключение, выходящее за пределы контроллера.

Я отклоняю этот вариант.

UPDATE 2: После некоторых ответов в Spring Framework JIRA, they suggest me использовать общий @ExceptionHandler что перехватывать все исключения, а затем с помощью некоторых условных, чтобы проверить причину и быть в состоянии знать, если причина исключения является исключение, которое я вызываю в Spring Formatter. Я думаю, что это может быть еще одним улучшением Spring MVC, потому что я не могу использовать @ExceptionHandler, чтобы поймать исключения, которые выбрасываются из Spring Formatters.Вы можете проверить доказательство, которое я загрузил here

Я тоже отклоняю эту опцию.

Вариант б)

Включить новый @BeanSimpleMappingExceptionResolver внутри @Configuration класс для отображения исключения с идентификатором вида.

@Configuration 
public class WebMvcConfiguration extends WebMvcConfigurerAdapter { 

    [...] 

    @Bean 
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { 
     SimpleMappingExceptionResolver resolver = 
      new SimpleMappingExceptionResolver(); 
     Properties mappings = new Properties(); 
     mappings.setProperty("EntityResultNotFoundException", "errores/404"); 
     resolver.setExceptionMappings(mappings); 
     return resolver; 
    } 
} 

Однако реализация выше не работает с исключениями, выброшенными на Spring Formatters.

UPDATE: Я отлаживал код Spring, и я нашел две вещи, которые могут быть интересным улучшением Spring Framework.

Прежде всего, DispatcherServlet загружает все зарегистрированные HandlerExceptionResolver от initStrategies и initHandlerExceptionResolvers методов. Этот метод берется все HandlerExceptionResolvers в правильном порядке, но затем, использует следующий код, чтобы заказать их снова:

AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); 

Проблема заключается в том, что этот метод делегата на findOrder метод, который пытается получить заказ от HandlerExceptionResolver, что является примером Ordered. Как вы могли видеть, я не определил порядок на моем зарегистрированном @Bean, поэтому, когда вы пытаетесь получить заказ от моего объявленного компонента SimpleMappingExceptionResolver, используется LOWEST_PRECEDENCE. Это приводит к тому, что Spring использует DefaultHandlerExceptionResolver, потому что это первый, который возвращает результат.

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

@Bean 
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { 
     SimpleMappingExceptionResolver resolver = 
      new SimpleMappingExceptionResolver(); 
     Properties mappings = new Properties(); 
     mappings.setProperty("EntityResultNotFoundException", "errores/404"); 
     resolver.setOrder(-1); 
     resolver.setExceptionMappings(mappings); 
     return resolver; 
    } 

Теперь, когда AnnotationAwareOrderComparator сортирует все зарегистрированные HandlerExceptionResolverSimpleMappingExceptionResolver является первым, и он будет использоваться в качестве распознавателя.

Во всяком случае, пока не работает. Я продолжаю отладку, и я видел, что теперь используется doResolveException от SimpleMappingExceptionResolver, чтобы разрешить исключение, поэтому все в порядке. Однако метод findMatchingViewName, который пытается получить отображаемый вид, возвращает null.

Проблема заключается в том, что findMatchingViewName пытается проверить, если полученный матч исключения с некоторыми исключениями, определенных на exceptionMappings в SimpleMappingExceptionResolver, но это только проверку супер классов внутри getDepth метода. Должно быть необходимо проверить исключение причины.

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

public class CauseAdviceSimpleMappingExceptionResolver extends SimpleMappingExceptionResolver{ 

    /** 
    * Find a matching view name in the given exception mappings. 
    * @param exceptionMappings mappings between exception class names and error view names 
    * @param ex the exception that got thrown during handler execution 
    * @return the view name, or {@code null} if none found 
    * @see #setExceptionMappings 
    */ 
    @Override 
    protected String findMatchingViewName(Properties exceptionMappings, Exception ex) { 
     String viewName = null; 
     String dominantMapping = null; 
     int deepest = Integer.MAX_VALUE; 
     for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) { 
      String exceptionMapping = (String) names.nextElement(); 
      int depth = getDepth(exceptionMapping, ex); 
      if (depth >= 0 && (depth < deepest || (depth == deepest && 
        dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) { 
       deepest = depth; 
       dominantMapping = exceptionMapping; 
       viewName = exceptionMappings.getProperty(exceptionMapping); 
      }else if(ex.getCause() instanceof Exception){ 
       return findMatchingViewName(exceptionMappings, (Exception) ex.getCause()); 
      } 
     } 
     if (viewName != null && logger.isDebugEnabled()) { 
      logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() + 
        "], based on exception mapping [" + dominantMapping + "]"); 
     } 
     return viewName; 
    } 

} 

Я думаю, что эта реализация действительно интересно, потому что также используйте класс исключения причины вместо использования только исключений суперклассов. Я собираюсь создать новый Pull-Request в Spring Framework github, включая это улучшение.

С этими 2-мя изменениями (заказ и расширение SimpleMappingExceptionResolver) Я могу поймать исключение, созданное из Spring Formatter, и вернуть пользовательский вид.

+0

Я только что создал следующую проблему в Spring Framework JIRA https://jira.spring.io/browse/SPR-14291 – jcgarcia

+0

не уверен, что это решит вашу проблему, но я думаю, что совет администратора требует, чтобы ваш класс был аннотирован с одним из 4 @Component, Repository, Service, Controller, который будет выполнен. Посмотрите, можете ли вы аннотировать класс ownerformatter. –

ответ

1

Похоже весеннем Formatter, которая вызывается перед тем вызвать реализация контроллера не неоспоримым monitorized по @ControllerAdvice

хороший улов, это именно то, что происходит.

почему Spring Formatter используется для подготовки запроса не неоспоримый monitorized по @ControllerAdvice поймать все возможные исключения, произошедшие во время этого «процесса подготовки»

Поскольку @ControllerAdvice реализуется как АОП советы по методам контроллера. Таким образом, он не имеет возможности перехватить исключение, выходящее за пределы контроллера.

В качестве обходного решения вы можете объявить глобальный HandlerExceptionResolver для обработки вашего настраиваемого исключения.

+0

Привет, Серж, сначала спасибо за ваш ответ. Как я писал по моему вопросу, я также проверил с SimpleMappingExceptionResolver, но без каких-либо результатов ... После этого я отлаживал код Spring, и я нашел что-то, что может быть ошибкой Spring или возможное улучшение. Я собираюсь обновить свой вопрос с помощью решения использовать SimpleMappingExceptionResolver для обнаружения исключений из Spring Formatters. – jcgarcia

+0

Я только что создал следующую ошибку Spring, чтобы иметь возможность создать Pull-Request https://jira.spring.io/browse/SPR-14291 – jcgarcia

+0

Кажется, что '@ ExceptionHandler' не проверяет первопричину. Ничего о '@ ControllerAdvice' и AOP. См. Мое доказательство [здесь] (https://github.com/DISID/disid-proofs/tree/master/spring-boot-exception-handling/ControllerAdvice) – jcgarcia

Смежные вопросы