网站的异常处理最好是解耦的,并且都放在一个地方集中管理。
比如访问权限不够,跳转到指定页面,比如访问的页面不存在,或者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("/");
}
}
网站的异常处理基本都能实现了
以上