Spring Boot微服务项目实战(第2版)学习笔记-第12章全局异常处理与Retry重试

本章主要介绍Spring Boot全局异常使用、自定义错误页面、全局异常类开发、Retry重试机制的介绍与使用等内容。

1.全局异常介绍

由于Web应用请求处理过程中发生错误是很常见的情况,所以Spring Boot为我们提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。比如现在我们启动spring-boot-book-v2项目(启动项目之前,记得启动Redis服务和ActiveMQ服务),项目启动完成之后,在浏览器中随便输入一个访问地址,比如http://localhost:8080/ayUser/testdddd,由于地址不存在,Spring Boot会跳转到错误界面,如图所示。
在这里插入图片描述
虽然Spring Boot提供了默认的error错误页面映射,但是在实际应用中,所示的错误页面对用户来说并不友好,通常需要我们自己来实现异常提示。

2.Spring Boot全局异常使用

2.1自定义错误页面(前后端分离后,本部分无需实现)

我们知道,Spring Boot的错误提示页面对用户体验并不好,这一节我们来实现自己的错误提示页面。首先,在spring-boot-book-v2项目/src/main/resources/static目录下新建自定义错误页面404.html,具体代码如下:

<! DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
</head>
<body>
<div class="text" style="text-align:center;">
	主人,我累了,让我休息一会!!!
</div>
</body>
</html>

错误页面内容很简单,就是当访问路径不存在时,在页面中间显示一句话:“主人,我累了,让我休息一会!!!”。当然,在真正的项目中,该错误页面样式会更加高大上。

404错误页面开发完成之后,在spring-boot-book-v2项目目录/src/main/java/com.example.demo下新建包error,并在error包下新建ErrorPageConfig配置类,具体代码如下:

@Confiquration
public class ErrorPageConfig{
	@Bean
	public WebServerFactoryCustomize<ConfigurableWebServerFactory>
	webServerFactoryCustomizer(){
		return (container -> {
			ErrorPage error404Page = new errorpage(httpstatus.NOT_FOUND,"/404.html");
		container.addErrorPages(error401Page, error404Page, error500Page);
		});
	}
}
  • WebServerFactoryCustomizer:Spring Boot的自动配置有一个特性就是能够通过代码来修改配置,这样可以很方便地修改配置,而我们只需要实现Spring Boot定义的接口即可实现该功能。这里,需要注册一个实现了WebServerFactoryCustomizer的Bean,在ErrorPageConfig类中,使用匿名类来实现WebServerFactoryCustomizer接口,同时实现该接口唯一的方法customize,并自定义401、404、500等错误页面。

2.2测试

404.html错误页面和ErrorPageConfig类开发完成之后,重启动spring-boot-book-v2项目(项目启动之前,请记得启动Redis缓存服务和ActiveMQ服务,否则项目会报错,之后不再提示),在浏览器访问输入链接http://localhost:8080/ayUser/testdddd,由于该链接不存在,就会出现如图所示的自定义错误页面。

在这里插入图片描述

2.3全局异常类开发

在项目中,我们会遇到各种各样的业务异常,业务异常是指正常的业务处理时,由于某些业务的特殊要求而导致处理不能继续所抛出的异常。我们希望这些业务异常能够统一被处理,而使用Spring Boot进行全局异常处理很方便。首先,在目录src/main/java/com.example.demo.error下统一封装自定义业务异常类BusinessException,该类继承RuntimeException异常类,并提供带有异常信息的构造方法,具体代码如下:

public class BusinessException extends RuntimeException{
	public BusinessException() {}
	public BusinessException(String message) {
		super(message);
	}
}

然后,在目录/src/main/java/com.example.demo.error下新建错误信息类ErrorInfo,该类用于封装错误信息,包括错误码,具体代码如下:

public class ErrorInfo<T> {
	public static final Integer SUCCESS = 200;
	public static final Integer ERROR = 100;
	//错误信息
	private Integer code;
	//错误码
	private String message;
	private String url;
	private T data;
	//忽略set、get方法
}

其次,在目录/src/main/java/com.example.demo.error下新建统一异常处理类GlobalDefaultExceptionHandler,具体代码如下:

@ControllerAdvice(basePackages="com.example.demo",})
public class GlobalDefaultExceptionHandler {
	@ExceptionHandler({BusinessException.class})
	//如果返回的为json数据或其他对象,添加该注解
	@ResponseBody
	public ErrorInfo defaultErrorHandler(HttpServletRequest reg, Exception e) throws Exception {
		ErrorInfo errorInfo = new ErrorInfo();
		errorInfo.setMessage.(e.getMessage());
		errorInfo.setUrl(req.getRequestURI());
		errorInfo.setCode(ErrorInfo.SUCCESS);
		return errorInfo;
	}
}
  • @ControllerAdvice:定义统一的异常处理类,basePackages属性用于定义扫描哪些包,默认可不设置。
  • @ExceptionHandler:用来定义函数针对的异常类型,可以传入多个需要捕获的异常类。
  • @ResponseBody:如果返回的为json数据或其他对象,添加该注解。

最后,在AyUserController类下添加控制层方法findAll,并在方法里抛出BusinessException,该异常会被全局异常类捕获到,具体代码如下:

@Controller
@RequestMapping("/ayUser")
public class AyUserController {
	@Resource
	private AySuerService ayUserService;
	@RequestMapping("/findAll")
	public String findAll(Model model) {
		List<AyUser> ayUser = ayUserService.findAll();
		model.addAttribute("users",ayUser);
		throw new BusinessException("业务异常");
	}
}

2.4测试

代码开发完成之后,重启动spring-boot-book-v2成功之后,在浏览器中输入访问地址:http://localhost:8080/ayUser/findAll,在浏览器中可以看到后端返回的json信息,具体信息如下:

{"code":200,"message":"业务异常","url":"/ayUser/findAll","data":null}

3.Retry重试机制

3.1Retry重试概述

当我们调用一个接口时,由于网络等原因可能会造成第一次失败,再去尝试就成功了,这就是重试机制。重试的解决方案有很多,比如利用try-catch-redo简单重试模式,通过判断返回结果或监听异常来判断是否重试,具体请看如下简单的例子:

public void testRetry() throws Exception{
	boolean result = false;
	try{
		result = load();
		if(!result){
			load();//一次重试
		}
	}catch (Exception e){
		load();//一次重试
	}
}

try-catch-redo重试模式还是有可能出现重试无效的现象,解决这个问题的方法是尝试增加重试次数retrycount和重试间隔周期interval,以达到增加重试有效的可能性。因此我们可以利用try-catch-redo-retry策略重试模式,具体伪代码如下所示:

public void testRetry2() throws Exception{
	boolean result = false;
	try{
		result = load();
		if(!result){
			//延迟3秒,重试三次
			reload(3000L, 3);//延迟多次重试
		}
	}catch (Exception e){
		//延迟3秒,重试三次
		reload(3000L, 3);//延迟多次重试
	}
}

但是这两种策略有一个共同的问题就是:正常逻辑和重试逻辑强耦合。基于这些问题,Spring-Retry规范正常和重试逻辑,Spring-Retry是一个开源工具包,该工具把重试操作模板定制化,可以设置重试策略和回退策略。同时重试执行实例保证线程安全。Spring-Retry重试可以用Java代码方式实现,也可以用注解**@Retryable**方式实现,这里Spring-Retry提倡以注解的方式对方法进行重试。

3.2Retry重试机制使用

使用Spring提供的重试策略之前,首先需要在pom.xml文件中引入所需的依赖,具体代码如下:

<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
</dependency>

依赖添加完成之后,需要在入口类MySpringBootApplication添加注解@EnableRetry开启Retry重试。完整代码如下:

@SpringBootApplication
@ServletComponentScan
@ImportResource(location={"classpath:spring-mvc.xml"})
@EnableAsync
//开启Retry重试
@EnableRetry
public class MySpringBootApplication {
	public static void main(String[] args) {
		SpringApplication.run(MySpringBootApplication.class, args);
	}
}

然后,在AyUserService类下添加新接口findByNameAndPasswordRetry,具体代码如下:

AyUser findByNameAndPasswordRetry(String name, String password);

接口findByNameAndPasswordRetry添加完成之后,在AyUserServiceImpl类下实现接口findByNameAndPasswordRetry,并在方法中故意抛出业务异常BusinessException,具体代码如下:

@Override
@Retryable(value= {BusinessException.class}, maxAttempts =5,
backoff = @Backoff(delay = 5000,multiplier =2))
public AyUser findByNameAndPasswordRetry(String name, String password) {
	System.out.println("findByNameAndPas方法失败重试了!");
	throw new BusinessException();
}
  • @Retryablevalue属性表示当出现哪些异常的时候触发重试,maxAttempts表示最大重试次数,默认为3,delay表示重试的延迟时间,multiplier表示上一次延时时间是这一次的倍数。

最后,在AyUserController类下添加控制层方法findByNameAndPasswordRetry,在该方法中调用服务层AyUserServiceImpl的方法findByNameAndPasswordRetry。具体代码如下:

RequestMapping("/findByNameAndPasswordRetry")
public String findByNameAndPasswordRetry(Model model){
	AyUser ayUser = ayUserService.findByNameAndPasswordRetry("阿毅,"123456");
	model.addAttribute("users", ayUser);
	return "success";
}

3.3测试

代码开发完成之后,重新启动spring-boot-book-v2项目,项目运行成功后,在浏览器中输入访问地址:http://localhost:8080/ayUser/findByNameAndPasswordRetry,由于方法findByNameAndPasswordRetry会抛出BusinessException异常,故Retry重试机制会检测到,进行第2次重试。重试成功,方法执行完成,重试失败,按照配置延迟delay时间,依次进行第3次、第4次、第5次重试,直到重试成功或者达到最大重试次数,重试策略终止。我们可以在IDEA的控制台多次看到如下的打印信息:

[findByNameAndPasswordRetry]方法失败重试了!
[findByNameAndPasswordRetry]方法失败重试了!
[findByNameAndPasswordRetry]方法失败重试了!
[findByNameAndPasswordRetry]方法失败重试了!
[findByNameAndPasswordRetry]方法失败重试了!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值