Spring controller异常的统一处理

网站的异常处理最好是解耦的,并且都放在一个地方集中管理。

比如访问权限不够,跳转到指定页面,比如访问的页面不存在,或者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("/");
	}

}

网站的异常处理基本都能实现了

以上

猜你喜欢

转载自neverflyaway.iteye.com/blog/2301571