(я нашел способ реализовать это в Spring 3.1, это описано во второй части этого ответа)
См. Главу 16.11. Обработка исключений Spring Reference
Есть несколько способов, кроме использования @ExceptionHandler
(см. ответ Гуки )
- Вы можете реализовать HandlerExceptionResolver ( использовать сервлет, а не пакет портлета ) - это своего рода глобальный @ ExceptionHandler
Если у вас нет определенной логики для исключения, а есть только конкретное представление, вы можете использовать SimpleMappingExceptionResolver , который является по крайней мере реализацией HandlerExceptionResolver
, где вы можете указать шаблон имени исключения и представление (jsp), которое отображается при создании исключения. Например:
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
p:defaultErrorView="uncaughtException">
<property name="exceptionMappings">
<props>
<prop key=".DataAccessException">dataAccessFailure</prop>
<prop key=".TypeMismatchException">resourceNotFound</prop>
<prop key=".AccessDeniedException">accessDenied</prop>
</props>
</property>
</bean>
В Spring 3.2 + можно аннотировать класс с помощью @ControllerAdvice
, все методы @ExceptionHandler
в этом классе работают глобально.
В Пружина 3.1 нет @ControllerAdvice
. Но с небольшим взломом можно получить аналогичную функцию.
Ключом является понимание того, как @ExceptionHandler
работает. Весной 3.1 есть класс ExceptionHandlerExceptionResolver
. Этот класс реализует (с помощью своих суперклассов) интерфейс HandlerExceptionResolver
и отвечает за вызов методов @ExceptionHandler
.
Интерфейс HandlerExceptionResolver
имеет только один метод:
ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);`.
Когда запрос обрабатывался методом контроллера Spring 3.x, тогда этот метод (представленный org.springframework.web.method.HandlerMethod
) является параметром handler
.
ExceptionHandlerExceptionResolver
использует handler
(HandlerMethod
) для получения класса Controller и сканирования его на наличие методов, помеченных @ExceptionHandler
. Если один из этих методов соответствует исключению (ex
), то эти методы вызываются для обработки исключения. (иначе null
возвращают, чтобы сигнализировать, что этот обработчик исключений не чувствует ответственности).
Первой идеей было бы реализовать собственный HandlerExceptionResolver
, который ведет себя как ExceptionHandlerExceptionResolver
, но вместо поиска @ExceptionHandler
в классе контроллера он должен искать их в одном специальном компоненте. Недостатком было бы то, что нужно (скопировать (или подкласс ExceptionHandlerExceptionResolver
) и должен) настроить все хорошие конвертеры сообщений, средства разрешения аргументов и обработчики возвращаемых значений вручную (конфигурация реального и только ExceptionHandlerExceptionResolver
выполняется пружина автоматически). Поэтому я пришел с другой идеей:
Реализация простого HandlerExceptionResolver
, который «перенаправляет» исключение в THE (уже настроено) ExceptionHandlerExceptionResolver
, НО с измененным handler
, указывающим на компонент, содержащий глобальные обработчики исключений. (Я называю их глобальными, потому что работа для всех контроллеров.).
И это реализация: 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);
}
}
}
Глобальный обработчик должен реализовать этот интерфейс (чтобы найти и реализовать fakeHanderMethod
, используемый для handler
public interface GlobalMethodExceptionResolverContainer {
void fakeHanderMethod();
}
И пример для глобального обработчика:
@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;
}
}
Кстати: вам не нужно регистрировать GlobalMethodHandlerExeptionResolver
, потому что spring автоматически регистрирует все bean-компоненты, которые реализуют HandlerExceptionResolver
для распознавателей исключений. Так что простого <bean class="GlobalMethodHandlerExeptionResolver"/>
достаточно.