文章目录
统一处理异常
针对表现层,因为数据层和业务层的数据最后都会汇聚到表现层
SpringBoot自动处理异常
错误页面存放文件叫error,错误页面一定要叫状态码,如404,500等(404报错就行,而500还需记录日志)
将error文件放在templates下,会自动生效
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<title>牛客网-500</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header">
<div class="container">
<!-- 导航 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<!-- logo -->
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../../index.html">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link position-relative" href="../letter.html">消息<span class="badge badge-danger">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../register.html">注册</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../login.html">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" href="../profile.html">个人主页</a>
<a class="dropdown-item text-center" href="../setting.html">账号设置</a>
<a class="dropdown-item text-center" href="../login.html">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary">nowcoder</span>
</div>
</li>
</ul>
<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" action="../search.html">
<input class="form-control mr-sm-2" type="search" aria-label="Search" />
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>
</div>
</header>
<!-- 内容 -->
<div class="main">
<div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
<img th:src="@{/img/error.png}" >
</div>
</div>
<!-- 尾部 -->
<footer class="bg-dark">
<div class="container">
<div class="row">
<!-- 二维码 -->
<div class="col-4 qrcode">
<img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
</div>
<!-- 公司信息 -->
<div class="col-8 detail-info">
<div class="row">
<div class="col">
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-light" href="#">关于我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">加入我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">意见反馈</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">企业服务</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">联系我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">免责声明</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">友情链接</a>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav btn-group-vertical company-info">
<li class="nav-item text-white-50">
公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
</li>
<li class="nav-item text-white-50">
联系方式:010-60728802(电话) admin@nowcoder.com
</li>
<li class="nav-item text-white-50">
牛客科技©2018 All rights reserved
</li>
<li class="nav-item text-white-50">
京ICP备14055008号-4
<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
京公网安备 11010502036488号
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</footer>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
</body>
</html>
控制器通知
@ControllerAdvice
标识Controller的配置类
-控制器通知,用于修饰类,表示该类是Controller的全局配置类。配置后,无论哪个Controller出错,都可以统一捕捉到,进行处理
- 在此类中,可以对Controller进行如下三种全局配置:
异常处理方案、绑定数据方案、绑定参数方案。
@ExceptionHandler 异常处理
- 用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常。
@ModelAttribute 绑定数据
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数,给所有Controller用
@DataBinder 绑定参数
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器。
SpringMVC底层有很多参数转换器,会自动判断参数需要哪个转换器,这些转换器可以将html页面中的数据自动匹配Controller层方法的形参,如根据属性名将数据赋值给Controller层某个形参对象
某些数据是特殊的类型,需要特殊的处理,则可用@DataBinder自定义一个参数转换器
捕获异常信息
Java 中捕获异常Exception获取异常信息的方法有 e.getMessage() 和 e.toString() 和e.printStackTrace(),他们的区别如下:
- e.getMessage(): 打印 异常的原因
- e.toString(): 打印 异常类型 和 异常的原因
- e.printStackTrace(): 打印 完整的异常堆栈信息
- e.getExceptionToString(): 打印完整的异常堆栈信息
e.getMessage() 和 e.toString() 方法: 打印的异常信息太少,没有具体的堆栈信息,不利于问题的定位处理!有时还会输出为 null 。
e.printStackTrace() 和 Exceptions.getExceptionToString(e) 方法:都可以打印完整的异常堆栈信息; 二者的区别是: e.printStackTrace() 在遇到大量并发访问 且 出现异常时,会发生:内存被占满的情况,导致服务挂掉,不可用
异常处理代码
异常处理要求:
- 将异常记录到日志中
- 给浏览器返回响应: 根据不同的请求类型(普通和异步)返回不同的响应
- 异步请求:返回JSON字符串
- 普通请求:返回网页,重定向到500页面
// (annotations = Controller.class 限制注解扫描范围,仅扫描带有Controller注解的类
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {
private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
// @ExceptionHandler 异常处理
// - 用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常。
// {}用于指定处理异常的范围
// Exception.class Exception为所有异常的父类,故此处指定的是捕获所有异常
@ExceptionHandler({
Exception.class})
// 公有,无返回值,方法名自定义,常用三种形参,其余的去手册中查
public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
// Controller中发生异常都会调用该方法
// 1. 将异常记录到日志中
logger.error("服务器发生异常: " + e.getMessage());
// StackTrace 是一个数组,数组中的每个元素StackTraceElement,都记录了一条异常信息,他们是有序的
for (StackTraceElement element : e.getStackTrace()) {
logger.error(element.toString());
}
// 2. 给浏览器返回响应
// 普通请求:返回网页,重定向到500
// 异步请求:返回JSON字符串
// 获得 请求的方式
String xRequestedWith = request.getHeader("x-requested-with");
// XMLHttpRequest 代表请求是一个异步请求,因为请求希望返回的数据是xml,只有异步请求会这样希望,普通的都是http
if ("XMLHttpRequest".equals(xRequestedWith)) {
// 2.1 异步请求:响应字符串
// application/json 返回json字符串,浏览器会自动转成js对象
// application/plain 返回普通字符串(可用json格式),需要用parseJSON()方法将字符串转成js对象
response.setContentType("application/plain;charset=utf-8");
// 获取输出流
PrintWriter writer = response.getWriter();
// 输出内容
writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
} else {
// 2.2 普通请求:重定向到500页面,获取项目访问路径,加上/error这一级路径即可
response.sendRedirect(request.getContextPath() + "/error");
}
}
}
// 用于记录完日志之后,手动重定向
@RequestMapping(path = "/error", method = RequestMethod.GET)
public String getErrorPage() {
return "/error/500";
}
统一记录日志
控制器通知,是在控制器发生异常时进行处理,而平时没有异常也会记录日志
而拦截器是针对控制器进行处理,日志不仅仅针对控制器,也针对业务层和数据层
因此,要采用AOP的方式来记录日志
AOP介绍见:
// 需求:对所有的业务组件记录日志,在业务组件一开始记录日志
// 日志格式:用户XXX(ip地址),在XXX时刻,访问了XXX类下XXX方法
@Component
@Aspect
public class ServiceLogAspect {
private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
@Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
// 日志格式:用户XXX(ip地址),在XXX(时间),访问了XXX类下XXX方法
// 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
// 用户ip地址要从request中获取,但方法不可以随意添加Request对象作为参数
// 获取Request对象:利用Request的工具类RequestContextHolder,通过getRequestAttributes()方法可以获得RequestAttributes类型对象
// ServletRequestAttributes是RequestAttributes的子类,拥有更多的方法
// 故将返回类型强转为子类型
// 通过ServletRequestAttributes可以获得Request对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取ip地址
String ip = request.getRemoteHost();
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
// 获取连接点的信息
// joinPoint.getSignature().getDeclaringTypeName() 得到织入目标类名
// joinPoint.getSignature().getName() 得到织入目标方法名
String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
}
}
参考文献
https://blog.csdn.net/qq_43842093/article/details/128057142
ure().getName() 得到织入目标方法名
String target = joinPoint.getSignature().getDeclaringTypeName() + “.” + joinPoint.getSignature().getName();
logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
}
}
# 参考文献
https://blog.csdn.net/qq_43842093/article/details/128057142