SpringMVC的异常处理(附代码)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_44296929/article/details/102568498

一、Java中的异常体系:(图片来源于网络)
在这里插入图片描述
1、在这些异常中,RuntimeException及其子孙类异常,在Java语法中并不必须处理,主要原因有:这些异常出现的频率可能非常高,如果一定要处理,例如:try...catch,则几乎所有的代码都需要放在try代码块中,并且,这些异常是可以杜绝的异常,通过严谨的编程,可以保证这些异常绝不会出现!
2、处理异常有两种方式:使用try...catch处理异常,或者使用throw抛出异常对象,并且在方法的声明中使用throws语法声明抛出!
3、通常,异常都是必须处理的,如果没有处理异常,会导致异常不断向上抛,最终java EE中的异常会由Tomcat来处理,会把跟踪日志显示在页面中,这些页面会显得非常的不友好,而且还有被分析出项目的内容的风险,对于后续解决问题也没有任何帮助,所以从原则上来说,必须处理异常!
4、 非RuntimeException是从语法上限制要求处理的,所以每次调用了抛出异常的方法,就必须try…catch或继续声明抛出!(这个就不多说了)
5、RuntimeException及其子孙类异常并不从语法上强制要求处理,但是,一旦出现,会对项目的安全、用户体验等各方面都会产生负面影响。

二、下面我们来说一下SpringMVC中处理异常的方式:

1、处理异常-SimpleMappingExceptionResolver:
SpringMVC提供了统一处理异常的方式:使用SimpleMappingExceptionResolver类,该类通过private Properties exceptionMappings;属性来配置异常种类于转发到的页面的对应关系,即每一种异常都可以有一个与之对应的页面,一旦项目中出现配置过的异常,就会自动转发到对应的页面来提示错误信息!
注:这种方式处理异常非常简单,但是,也有一个较大的问题:无法提示更详细的信息!例如:出现ArrayIndexOutOfBoundsException时,其实,异常还封装了详细的错误信息,即:越界值是多少等。

<!-- 配置统一处理异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="exceptionMappings">
		<props>
			<prop key="java.lang.RuntimeException">runtime</prop>
			<prop key="java.lang.NullPointerException">null</prop>
			<prop key="java.lang.ArrayIndexOutOfBoundsException">index</prop>
		</props>
	</property>
</bean>

通过上面的配置,一旦出现了NullPointerException则会跳转到null.jsp页面。

2、处理异常-@ExceptionHandler注解:
可以在控制器类中定义一个处理异常的方法,在方法之前添加@ExceptionHandler,然后,凡是约定范围内的异常,都可以由该方法来决定如何处理:

@ExceptionHandler
public String handleException(Exception e) {
	System.out.println(
		"ExceptionController.handleException()");
		
	if (e instanceof NullPointerException) {
		return "null";
	} else if (e instanceof ArrayIndexOutOfBoundsException) {
		return "index";
	}
		
	return "runtime";
}

在处理异常时,如果需要转发数据,可以将返回值修改为ModelAndView,或者,在处理异常的方法参数列表中添加HttpServletRequest,但是,却不可以使用ModelMap来封装转发的数据:

@ExceptionHandler
public String handleException(Exception e,
		HttpServletRequest request) {
	System.out.println(
		"ExceptionController.handleException()");
		
	if (e instanceof NullPointerException) {
		return "null";
	} else if (e instanceof ArrayIndexOutOfBoundsException) {
		request.setAttribute("msg", e.getMessage());
		return "index";
	}
		
	return "runtime";
}

例:下面我们举个小例子就明白了:
比如我们想要写一个UserController,在这个Controller中处理用户提交的用户名被占用的时候,抛出该异常。
step1、首先创建一个控制器的基类BaseController。

public abstract class BaseController {
	@ExceptionHandler(RuntimeException.class)
	@ResponseBody
	public ResponseResult<Void> handleException(Exception e){
		//判断类型,并进行处理
		if(e instanceof UsernameConflictException){
			//用户名冲突异常
			return new ResponseResult<Void>(401,e);
		}
	}
}

step2、然后我们创建我们处理业务逻辑的UserController,让UserController继承我们的BaseController。

@Controller
@RequestMapping("/user")
public class UserController extends BaseController{
	@Autowired
	private IUserService userService;
	
	//调用业务层进行注册(reg为接口)
	userService.reg(user);
	//执行返回
	return new ResponseResult<Void>();
}

step3、reg接口代码:下面我只举一个异常的例子,实际开发中肯定不止这一种异常。

public interface IUserService {
	User reg(User user)throws UsernameConflictException;
}

看下具体的业务层实现类:抛出了异常,并定义了异常信息:throw new UsernameConflictException("");

@Service("userService")
public class UserServiceImpl implements IUserService{
	@Autowired
	private UserMapper userMapper;
	
	public User reg(User user) throws UsernameConflictException,InsertDataException{
		// 根据尝试注册的用户名查询用户数据
		User data=findUserByUsername(user.getUsername());
		// 判断是否查询到数据
		if(data!=null){
			// 是:查询到数据,即用户名被占用,则抛出UsernameConflictException异常
			throw new UsernameConflictException("用户名("+user.getUsername()+")已被占用");
		}else{
			// 否:没有查询到数据,即用户名没有被占用,则执行插入用户数据,获取返回值
			User result=insert(user);
			// 执行返回
			return result;
		}
	}

step4、与页面中的ajax请求相对应:如果返回的状态码是401,则会提示错误信息。

扫描二维码关注公众号,回复: 7571773 查看本文章
<script type="text/javascript">
	$("#btn-reg").click(function(){
		var url="../user/handle_reg.do";
		var data=$("#reg-form").serialize();
		$.ajax({
			"url":url,
			"data":data,
			"type":"POST",
			"dataType":"json",
			"success":function(json){
				if(json.state==401){
					//用户名已被占用
					alert("注册失败"+json.message);
				}
			}
		});
	});
</script>

三、思考:

1、是否可以在BaseController类中添加3个处理异常的方法,分别单独处理NullPointerExceptionArrayIndexOutOfBoundsExceptionRuntimeException异常?
答案:可以!允许使用多个不同的方法来处理异常!

2、假设处理异常的方法在A控制器中,而B控制器中处理请求时出现异常,会被处理吗?
答案:不可以!通常,可以创建一个BaseController,作为当前项目的控制器类的基类,然后,把处理异常的代码编写在这个类中即可!

3、关于@ExceptionHandler,还可以用于限制其对应的方法处理的异常的种类!
例如:@ExceptionHandler(IndexOutOfBoundsException.class);

以上代码表示接下来的方法只处理IndexOutOfBoundsException及其子孙类异常,而其它的异常并不处理!

四、总结:

1、处理异常的本质并不可以撤销异常,无法让已经出现的问题还原到没有问题,所以,处理异常的本质应该是尽量的补救已出现的问题,并且,尝试通过提示信息等方式告之使用者,尽量执行正确的操作,避免后续再次出现同样的问题!
2、对于开发者而言,应该处理每一个可能出现的异常,因为,如果没有处理,就会继续抛出,最终被Tomcat捕获,而Tomcat处理的方式是把异常的跟踪信息显示在页面上,是极为不合适的!
3、在SpringMVC中,处理异常有2中方式,第一种是通过SimpleMappingExceptionResolver设置异常与转发目标的对应关系,第2种是使用@ExceptionHandler为处理异常的方法进行注解,推荐使用第2种方式。
4、通常,会把处理异常的方法写在项目的控制器类的基类中,即写在BaseController中,在使用@ExceptionHandler时,可以明确的指定所处理的异常类型,例如:@ExceptionHandler(NullPointerException),且,在控制器类中,可以编写多个处理异常的方法。
5、关于处理异常的方法,应该是public方法,返回值类型与处理请求的方式相同,可以是StringModelAndView或其他允许类型,方法的名称可以自定义,参数列表中必须包含Exception类型的参数,还允许HttpServletRequestHttpServletResponse,不允许使用ModelMap

上面就是Spring MVC处理异常的方式,如果文章对你有帮助,请点个赞支持一下,谢谢。

猜你喜欢

转载自blog.csdn.net/weixin_44296929/article/details/102568498