统一处理异常 和 统一记录日志

统一处理异常

针对表现层,因为数据层和业务层的数据最后都会汇聚到表现层

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(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com
									</li>
									<li class="nav-item text-white-50">
										牛客科技©2018 All rights reserved
									</li>
									<li class="nav-item text-white-50">ICP14055008-4 &nbsp;&nbsp;&nbsp;&nbsp;
										<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() 在遇到大量并发访问 且 出现异常时,会发生:内存被占满的情况,导致服务挂掉,不可用

异常处理代码

异常处理要求:

  1. 将异常记录到日志中
  2. 给浏览器返回响应: 根据不同的请求类型(普通和异步)返回不同的响应
    • 异步请求:返回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

猜你喜欢

转载自blog.csdn.net/ShirleyZ1007/article/details/136427867