JavaWeb后端全局异常处理

JavaWeb使用springmvc进行后端全局异常处理

全局异常处理的目的:

在后端代码校验中,凡是出现用户因前端操作不当而导致的问题,都需要将错误信息返回给前端,供用户查看。
于是,我们在后台,将所有的错误声明成异常,通过创建不同类型的异常,对应不同类型的错误,以此通过我们手动抛出异常,来让springmvc提供的全局异常捕获器来捕获到,并自动将错误信息为我们响应到前端页面。

全局异常处理的步骤

第一步:创建一个新类型的异常

  1. 这个异常需要继承RuntimeException类。
  2. 需要重载这个新的异常类的构造方法,创建一个带参的构造方法,参数为String类型的message
  3. 并在重载的构造方法中,调用父类的有参构造方法。
代码举例:
我们以用户登录的场景为例,创建一个专门用于抛出用户登录时输入信息是否合法的异常类,名为:LoginException,这个自定义的类,是一个在代码运行过程中,才会触发的异常,因为只有在代码运行过程中,用户的请求才能被我们的代码所处理。所以,我们的这个登录异常类,需要继承运行时异常的类RuntimeException。
// 对应上面的 1
public class LoginException extends RuntimeException {

	//这个ID是继承RuntimeException方法,要实现的,对于我们逻辑业务没有影响
	//如果不写会报警告。
  
	private static final long serialVersionUID = 1L;
	
	// 对应上面的 2
	public LoginException(String message) {
	    //对应上面的 3
		super(message);
	}

这里再详细的刨析一下,我们的代码,在底层是如果调用的。上面我们继承了RuntimeException类,这个类中重载了多个构造方法,其目的就是根据不同的参数列表来接收不同情况下的参数,我们现在的业务需求,仅需要向前端发送错误信息即可,没有其他的需求。所以,我们重载的方法对应的是父类RuntimeException类中的参数列表为String message的构造方法。我们传入的message会传递给父类,被父类拿到。具体代码:

    public RuntimeException(String message) {
        super(message);
    }

而在这个类中,可以看到,父类RuntimeException又继续调用了它的父类,为Exception,将我们传递给它的message值又传递给了Exception类中的参数列表为String message的构造方法。具体代码:

    public Exception(String message) {
        super(message);
    }

而Exception类会继续调用它的父类(Exeption又继承了Throwable类),所以,它继续调用Throwable的参数列表为String message的构造方法,将这一路传来的message继续向上传。具体代码:

    public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }

最终,message被赋值给了detailMessage这个实例变量,以备使用。
【在Throwable类中接下去的底层操作,不在本节展开,后续会专门书写文章介绍】

第二步:创建一个全部异常捕获处理器

用于捕获所有代码抛出的异常,并在次处理器中给出解决方案。

这里说是一个处理器,其实就是一个Java类,只不过我们在这里,运用注解的方式,给它赋予神圣的权限,让它从一个普通的Java类,变成一个能够捕获全局异常并给出处理的核心关键类
我们需要做什么呢?

  1. 在这个类上加上@ControllerAdivce注解,来表明它的身份

继续以我们登录的需求为例,我们在这里创建一个名为GlobalExceptionHandler,只所以起这么长的名字,其实完全是为了让它更具有表义性,翻译过来叫做:全局异常处理器。这个的类名,随便怎么叫都可以。

	@ControllerAdvice
	public class GlobalExceptionHandler {
		
	}

第三步:在这个全局异常捕获处理器中,声明我们需要捕获的异常类型及具体解决方案

既然我们创建好了全局异常处理器,那么现在就来的新的问题,作为异常处理的核心关键,它都需要做些什么?
我们现在来思考一下,既然是一个全局异常捕获处理器,它就需要①捕获全局的异常,也就是一旦其他地方的代码抛出了异常,它可以进行捕获。相当于一个警察,看到一个正在偷自行车的小偷,他可以直接上去抓住他,对他进行一系列的处理。那么这个前去抓小偷的过程,就是捕获异常的过程。②捕获到异常后,需要对异常进行处理。至于怎么处理,这就完全根据我们自己的需求来了,比如我们此时的需求就是向前端页面发送回去错误信息即可。这里相当于,警察抓住小偷后,可以对他进行思想教育、或者是直接上铐子带回所里,等候发落。
这里可能有同学会想到,那么一旦出现异常,我们都必须去捕获吗?其实并不是的,我们可以专门指定我们需要捕获的异常类型。这就好比警察也有很多种,有交警、刑警、武警等等,分别对不用的异常情况进行处理,对于交警来说,他更多的是专注于交通类的犯罪情况,而刑警专注于刑事犯罪一类的情况。

言归正传:我们需要做什么?
  1. 在这个类种,创建一个方法,这个方法里写上我们具体如何处理这个异常。通过在方法上,添加@ExceptionHandler(LoginException.class)注解,传入一个我们想要捕获的异常类型。例如,我们的需求是捕获登陆时出现的异常,也就是登陆时一旦出现了错误,我们就抛出一个异常,比如:这个异常可能是由于用户名或者密码错误,抑或是验证码输入有误等等。
    这里@ExceptionHandler()注解是springmvc规定的,我们直接使用即可。那在这个注解中,我们传入了一个参数,这个参数由又我们自己指定的,它表示具体的异常的类型。由于刚才,我们在第一步中,专门创建了一个登录时的异常,LoginException,所以,我在这里把这个异常的类对象传给了它作为参数。一定要注意,是 .class 。
  2. 接下来,就是对异常进行具体的处理,也就是我们要给出的解决方案。
    在完成我们需求案例之前,在这里,罗列出所有异常处理的类型,可以供大家使用。
    在后端,对于错误异常的处理,无非就以下几种情况:
①处理完后,需要发送数据到前端页面给用户显示。

请大家思考一下,我们现在的需求,是否就是如此呢?稍后公布答案。
这里需要特别强调,如果后端要发送数据给前端,那通过的方式就是传输JSON类型的字符串。而如果一旦后端要往前端发送数据(如JSON字符串),则前端一定得有方法去接收我们传过去的数据。通常情况下,是前端发送异步请求到后端,无论是通过调用后端接口还是直接访问指定的url路径,我们在后台直接将值返回给前端异步请求的方法,异步请求的方法中会有专门的回调函数等待着我们返回到前端的值
如果前端没有专门接收传回数据的方法,那么数据就会脱离页面而单独存在,仅仅就是一个字符串。

②不需要发送任何数据,直接跳转页面。

比如,在开发中,一旦用户做出了某些不合法的操作,我们就可以强制跳转页 面,让用户到达我们指定的页面进行访问。

③不仅需要传输数据,又需要跳转页面。

这种情况,多用于前端发送的是同步请求,通过同步请求,后端拿取到传来的参数值,并作出页面跳转的响应,同时还可以将数据封装带回前端。

那么我们的需求是哪种情况呢?

这其实取决于我们前端发送的请求类型,如果是同步请求,那么我们没有办法通过直接返回数据的方式为前端传输数据,因为即使我们可以传输,可是前端页面没有任何的异步回调函数等待着我们的响应。所以,需要用③的方法。
如果是异步请求,那这就相当于,前端通过了异步请求方法来调用我们后端的数据,那肯定是有回调函数可以接收的,那我们就可以使用①的方法。

这里为大家同时列举同步请求和异步请求的两种情况,大家可以在学会后,灵活运用。

同步请求

我们假设,用户登录的时候,填写的是一个form表单,里面有用户名、密码、验证码三项内容。此时,用户一旦点击提交按钮,表单将直接以同步请求的方式,传输到后台的指定controller中。那我们就需要想到,此时我们无法在全局异常处理器中将错误直接返回,而是通过重新跳转回登陆页面,并将错误信息一并返回。
这种需求,在SpringMVC中,我们可以直接使用ModelAndView对象进行处理。直接上代码:

	@ExceptionHandler(LoginException.class)
	@ResponseBody
	public ModelAndView handle(LoginException e) {
		ModelAndView mv = new ModelAndView();
		mv.addObject("error",e.getMessage());
		mv.setViewName("login");
		return mv;
	}

解释一下:
@ResponseBody 标签表示,返回结果不跳转页面。
mv.addObject(“error”,e.getMessage()) 是将错误数据封装成一个key=value的形式,Key为error,Value为我们传给它的message错误信息。
mv.setViewName() 是我们指定的跳转页面的名字,这里由于我设置了视图解析器的前后缀配置。只写了一个login,最终其实返回的页面是user/login.html(这个视个人配置而定)

如果是异步请求

	@ExceptionHandler(LoginException.class)
	@ResponseBody
	public Map<String, String> handle(LoginException e){
		Map<String, String> result = new HashMap<>();
		result.put("error",e.getMessage());
		return result;
	}

我们可以直接创建一个Map集合,还是通过key=value的格式。将Key指定为error,将value指定为我们传给它的message。然后返回这个map集合即可。要注意,还是需要添加@ResponseBody这个注解,来表明不跳转任何页面。而是,直接以JSON格式的字符串输出内容到前端页面。
那此时,前端的回调函数就可以拿到我们这里传过去的数据,其中有key为error,value为message的值。

第四步:在具体的业务代码中,抛出异常

既然我们已经定义好了如何处理异常,那么现在就需要我们在用户输入信息有误的时候,直接手动抛出异常

例如:我们在接收到前端用户传来的数据后,将数据进行核查。无论是与数据库进行交互,获得用户名和密码;还是调用第三方验证码接口来获得正确的验证码。我们最终肯定会得出结论,用户的输入是否是有错的。我们现在来讨论用错的情况。如果,用户的用户名和密码不匹配,那么我们直接在代码中,书写以下代码:

if(用户名或密码错误){
	throw new LoginException("用户名或密码输入错误");
}

通过我们自己手动抛出异样,也就是自己new一个自己定义的LoginException类型的异常,并将错误信息作为参数传给这个构造方法,以此来获得一个异常对象。一旦这里抛出了异常,由于第二、三步中,我们已经声明了全局异常处理器捕获的异常类型,这里肯定可以捕获到我们抛出的异常,此时,我们的异常将被处理器拿到,同时进入到我们指定的处理方法中(也就是会直接返回到前端页面、或者跳转新页面)。而这句代码接下的代码将不会再执行。

验证码错误的话,代码也是一样的,只是错误信息不同:

if(验证码错误){
	throw new LoginException("验证码输入错误");
}

那如果用户名和密码正确、同时验证码也正确,那么就不会进入到if语句中,也就不会抛出异常,则可以继续进行接下面的代码,比如:登陆成功后,跳转到首页,或者其他操作。

发布了10 篇原创文章 · 获赞 1 · 访问量 394

猜你喜欢

转载自blog.csdn.net/m0_46193073/article/details/103979356