网站的异常处理最好是解耦的,并且都放在一个地方集中管理。
比如访问权限不够,跳转到指定页面,比如访问的页面不存在,或者404 500之类的错误。
本文介绍Spring的@ControllerAdvice来对这些异常统一进行处理。
import java.io.IOException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; @ControllerAdvice(annotations = {RestController.class}) public class ExceptionReaper { private static final Logger logger = LogManager.getLogger(ExceptionReaper.class); @ExceptionHandler(value = { IOException.class , RuntimeException.class }) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ModelAndView exception(Exception exception, WebRequest request) { logger.info("Catch an exception", exception); return new ModelAndView("error/errorPage"); } @ExceptionHandler(value = { NoHandlerFoundException.class }) @ResponseStatus(HttpStatus.NOT_FOUND) public ModelAndView noMapping(Exception exception, WebRequest request) { logger.info("No mapping exception", exception); return new ModelAndView("error/notFound"); } }
网上大部分的异常处理大致分为这么几种
1.web.xml配置对404和500之类的错误进行处理,比如访问不存在页面时跳转到一个静态页面,缺点是必须静态页面而且不太好用
<error-page> <error-code>404</error-code> <location>/building.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/error.jsp</location> </error-page>
2.配置exception handler,这样的话每个controller都要配置异常处理,或者在一个super controller里配置异常处理,所有controller继承他,缺点也是显而易见的。
@Controller public class MyController { @ExceptionHandler(RuntimeException.class) @ResponseBody public String runtimeExceptionHandler(RuntimeException runtimeException) { logger.error("error", runtimeException); return "error"; } }
3.在applicationContext里面配置SimpleMappingExceptionResolver
@Bean public SimpleMappingExceptionResolver exceptionResolver() { SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver(); Properties exceptionMappings = new Properties(); exceptionMappings.put("java.lang.Exception", "error/errorPage"); exceptionMappings.put("java.lang.RuntimeException", "error/errorPage"); exceptionResolver.setExceptionMappings(exceptionMappings); Properties statusCodes = new Properties(); statusCodes.put("error/404", "404"); statusCodes.put("error/error", "500"); exceptionResolver.setStatusCodes(statusCodes); return exceptionResolver; }
上面这三种处理方法比较常见,但是我觉得都不如@ControllerAdvice来的方便好用
完全可以把@ExceptionHandler放在@ControllerAdvice统一处理,不用每个controller都配置
import java.io.IOException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; @ControllerAdvice(annotations = {RestController.class}) public class ExceptionReaper { private static final Logger logger = LogManager.getLogger(ExceptionReaper.class); @ExceptionHandler(value = { IOException.class , RuntimeException.class }) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ModelAndView exception(Exception exception, WebRequest request) { logger.info("Catch an exception", exception); return new ModelAndView("error/errorPage"); } @ExceptionHandler(value = { NoHandlerFoundException.class }) @ResponseStatus(HttpStatus.NOT_FOUND) public ModelAndView noMapping(Exception exception, WebRequest request) { logger.info("No mapping exception", exception); return new ModelAndView("error/notFound"); } }
@ControllerAdvice(annotations = {RestController.class}) 配置你需要拦截的类,
@ControllerAdvice(basePackages = "com.demo") 这也可以
然后下面放@ExceptionHandler作为全局的异常处理
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 告诉浏览器这是什么错误类型
最后就可以记录日志,根据需求做一些处理,返回对应的ModelAndView跳转到对应的错误页面
需要注意的是,上面这些配置,只能配置你的程序中抛出的错误
1.如果是用户请求了一个不存在的页面,没有对应的@RequestMapping,此时Spring的DispatcherServlet就会处理掉返回404,不会进入任何一个controller
2.还有比如spring security之类的权限管理模块,如果用户的密码正确,但是该账户的权限组没有权限访问当前页面,此时权限模块会有自己的AccessDeniedHandler处理,也不会进入刚才配置的@ControllerAdvice
所以对于2中的情况,一般通过权限管理模块提供的方法去处理异常
比如spring security中
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/resources/**") .permitAll() .anyRequest() .authenticated() .and() .exceptionHandling() .accessDeniedPage("/error/accessDenied.html") .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); }
对于1中的情况,我们可以配置Spring在没有对应的@RequestMapping时,不要自行处理,让他抛出一个NoHandlerFoundException的异常,从而让我们配置的@ControllerAdvice进行统一处理
如果是xml风格的配置,可以在DispatcherServlet对应的配置文件中配置
如果是之前介绍的class风格的配置,可以这样配置:
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);//for NoHandlerFoundException
import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.request.RequestContextListener; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; public class WebInit implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) throws ServletException { // Create the 'root' Spring application context AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); rootContext.scan("com.demo"); // Manage the lifecycle of the root application context container.addListener(new ContextLoaderListener(rootContext)); // Listener that exposes the request to the current thread container.addListener(new RequestContextListener()); // Create the dispatcher servlet's Spring application context AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext(); // Register and map the dispatcher servlet DispatcherServlet dispatcherServlet = new DispatcherServlet(dispatcherContext); dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);//for NoHandlerFoundException ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", dispatcherServlet); dispatcher.setLoadOnStartup(1); dispatcher.addMapping("/"); } }
网站的异常处理基本都能实现了
以上