本文纯个人读书笔记,书籍《一步一步学 Spring Boot 2》
如果喜欢,可直接购买书籍。如有侵权,请联系删除
在 Web 应用中,我们经常需要处理错误情况。Spring Boot 默认提供了一个映射 /error,在处理中抛出异常之后,就跳转到这个映射中,并且最终会跳转到一个全局的错误界面。
启动 Spring Boot 应用,随便输入一个地址,就会触发异常,跳转到错误界面。
错误界面:
虽然 Spring Boot 提供了一个默认的错误界面,但是这个界面没有很友好,我们经常会重新开发一个错误界面。
新建错误界面 /resources/static/404.html。
404.html:
错误
一不小心,走丢了哦 !!!
这边就简单的实现一个界面,实际项目中可以根据具体需求进行 404 界面开发。
新建 com.xiaoyue.demo.error.ErrorPageConfig 配置类,配置上面的界面未错误处理界面。
ErrorPageConfig:
@Configuration
public class ErrorPageConfig {
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
factory.addErrorPages(error404Page);
}
};
}
}
这边用的是 Spring Boot 2.0 以上的,所以使用 WebServerFactoryCustomizer,原书使用 EmbeddedServletContainerCustomizer,这个已经被替代了。
也可以继续在 WebServerFactoryCustomizer 中添加 401,,500 等处理界面。
再次启动 Spring Boot 应用,随便输入一个地址,查看错误界面。
实际项目中,处理业务功能时候,由于一些业务的特殊要求,导致处理不能继续而抛出异常。我们希望这些业务异常能够统一处理,因此使用 Spring Boot 进行全局异常处理就变得很方便。
首先,我们需要封装自定义业务异常 com.xiaoyue.demo.exception.BusinessException , 该类继承自 RuntimeException 异常类。
BusinessException:
public class BusinessException extends RuntimeException {
public BusinessException(){
super();
}
public BusinessException(String message){
super(message);
}
}
创建错误信息封装类 com.xiaoyue.demo.error.Errorlnfo。
ErrorInfo:
public class ErrorInfo {
public static final Integer SUCCESS = 200;
public static final Integer ERROR = 100;
//错误信息
private Integer code;
//错误码
private String message;
private String url;
private T data;
}
创建异常处理类 com.xiaoyue.demo.error.GlobalDefaultExceptionHandler 对异常进行处理。
GlobalDefaultExceptionHandler:
@ControllerAdvice(basePackages = {"com.xiaoyue.demo",})
public class GlobalDefaultExceptionHandler {
@ExceptionHandler({BusinessException.class})
//如果返回的为 json 数据或其他对象,就添加该注解
@ResponseBody
public ErrorInfo defaultErrorHandler(HttpServletRequest req ,
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 数据或其他对象,就添加该注解。
在 UserController 中新增方法 testError,抛出 BusinessException 异常。
BusinessException:
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/testError")
public String testError(Model model) {
if (true){
throw new BusinessException("业务处理异常");
}
return "user";
}
}
运行项目,访问 http://localhost:8080/demo/user/testError,查看结果。
实际项目中,处理业务功能时候,由于一些业务的特殊要求,导致处理不能继续而抛出异常。我们希望这些业务异常能够统一处理,因此使用 Spring Boot 进行全局异常处理就变得很方便。
我们调用一个接口的时候,可能由于网络等原因导致失败,这时候需要需要进行重试。简单点就是 try-catch-redo 简单重试模式 ,判断返回结果或监昕异常来判断是否重试。
** try-catch-redo:**
public void testRetry() throws Exception {
boolean result = false;
try {
result = load();
if (!result) {
load(); //一次重试
}
}catch(Exception e){
load(); //一次重试
}
}
try-catch-redo 在失败之后马上发起重试,这种情况下,间隔时间太短,再次重试有人容易失败。这时候,需要考虑增加重试次数或者间隔时间。
try-catch-redo-retry:
public void testRetry() throws Exception {
boolean result = false;
try {
result = load();
if (!result) {
reLload(3000L, 3); //延迟 3 秒,重试 3 次
}
}catch(Exception e){
reLload(3000L, 3); //延迟 3 秒,重试 3 次
}
}
我们自己进行编写重试逻辑,会导致正常逻辑和重试逻辑强耦合,属于入侵式的。Spring-Retry 规范正常逻辑和重试逻辑,将正常逻辑和重试逻辑解藕。
Spring-Retry 是一个开源工具包 , 该工具把重试操作模板定制化,可以设置重试策略和回退策略。同时,重试执行实例保证线程安全。 Spring-Retry 重试可以用 Java 代码方式实现,也可以用注解@Retryable 方式实现,这里 Spring-Retry 提倡以注解的方式。
1.引入依赖
在 pom.xml 文件中添加依赖。
pom.xml:
org.springframework.retry
spring-retry
org.aspectj
aspectjweaver
2.开启重试
在入口类 DemoApplication 中 添加注解 @EnableRetry 开启 Retry 重试。
DemoApplication:
@SpringBootApplication
@ServletComponentScan
@EnableAsync
@ImportResource(locations={"classpath:spring-mvc.xml"})
@EnableRetry
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
在 UserService 中新增测试接口 findByNameAndPasswordRetry 。
UserService:
public interface UserService {
User findByNameAndPasswordRetry(String name, String password);
}
在 UserServiceImpl 中添加实现方法。
UserServiceImpl:
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Resource
private UserRepository userRepository;
@Override
@Retryable(value= {BusinessException.class}, maxAttempts = 5,
backoff = @Backoff(delay = 5000 , multiplier = 2))
public User findByNameAndPasswordRetry(String name, String password) {
System.out.println("findByNameAndPasswordRetry 调用失败,进行重试");
throw new BusinessException() ;
}
}
@Retryable:
value 属性表示当出现哪些异常的时候触发重试。
maxAttempts 表示最大重试次数,默认为 3。
delay 表示重试的延迟时间。
multiplier 表示下一次延时时间是这一次的倍数。
在测试类 DemoApplicationTests 中添加测试代码进行测试。
DemoApplicationTests:
@Test
public void testRetry(){
User user = userService.findByNameAndPasswordRetry("aa", "123456");
logger.info(user.getId() + " " + user.getName());
}