1. 声明式异常处理
-
背景:编程式异常处理导致在业务代码中掺杂着大量的try-catch代码。并且代码冗余度极高。
-
声明式异常处理的步骤:
① 新建一个类,使用 @RestControllerAdvice 修饰
② 新建多个方法,每一个方法专门处理某一类异常信息,使用 @ExceptionHandler 修饰
java
//@ControllerAdvice 返回的是逻辑视图
@RestControllerAdvice //返回的是(json)字符串
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException e){
System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
return e.getMessage() ;
}
@ExceptionHandler(SQLException.class)
public String handleSQLException(SQLException e){
System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
return e.getMessage() ;
}
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
return e.getMessage() ;
}
}
2. 拦截器 - Interceptor
-
和过滤器的区别: 过滤器需要依赖于javaee环境;
-
基本使用: ① 新建类,实现 HandlerInterceptor接口
② 实现其中的三个方法: preHandle() , postHandle() , afterCompletion()
③ 配置(注册)拦截器,拦截所有
java
//@ControllerAdvice 返回的是逻辑视图
@RestControllerAdvice //返回的是(json)字符串
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException e){
System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
return e.getMessage() ;
}
@ExceptionHandler(SQLException.class)
public String handleSQLException(SQLException e){
System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
return e.getMessage() ;
}
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
System.out.println("我捕获到了一个运行时异常:"+e.getMessage());
return e.getMessage() ;
}
}
xml
<context:component-scan base-package="com.bottom"/>
<!-- 注册拦截器拦截所有 -->
<mvc:interceptors>
<bean class="com.atguigu.springmvc.interceptor.FirstInterceptor"/>
</mvc:interceptors>
- 拦截器的拦截规则:
① 拦截所有:
mvc:interceptors </mvc:interceptors>
② 设置拦截路径:
xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello01/*"/> 拦截hello01下面的所有的请求路径(单层)
<mvc:mapping path="/hello01/**"/> 拦截hello01下面的所有的请求路径(多层)
<mvc:exclude-mapping path="/hello01/h03"/> 排除指定规则的路径
<bean class="com.bottom.springmvc.interceptor.FirstInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
- 拦截器栈 ① 配置:
xml
<mvc:interceptors>
<bean class="com.bottom.springmvc.interceptor.InterceptorA"/>
<bean class="com.bottom.springmvc.interceptor.InterceptorB"/>
<bean class="com.bottom.springmvc.interceptor.InterceptorC"/>
</mvc:interceptors>
建立 InterceptorA类
java
public class InterceptorA implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("A - preHandle ... ");
return true ;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("A - postHandler ... ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("A - afterCompletion ... ");
}
}
建立 InterceptorB类
java
public class InterceptorB implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("B - preHandle ... ");
return true ;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("B - postHandler ... ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("B - afterCompletion ... ");
}
}
建立InterceptorC类
java
public class InterceptorC implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("C - preHandle ... ");
return true ;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("C - postHandler ... ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("C - afterCompletion ... ");
}
}
② 执行顺序:
-
A - preHandle ...
-
B - preHandle ...
-
C - preHandle ...
-
Hello01 - h02()...
-
C - postHandler ...
-
B - postHandler ...
-
A - postHandler ...
-
C - afterCompletion ...
-
B - afterCompletion ...
-
A - afterCompletion ...
- 拦截器栈相关的源码阅读
① 阅读DispatcherServlet的doDispatch方法:
java
protected void doDispatch(){
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
//如果这里成立,则第1081行就不执行了
return;
}
//它是Hello01Controller的h02方法的调用
mv = ha.handle(request,response); //此处是第1081行
}
② 阅读mappedHandler.applyPreHandle():
java
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
通过上述代码的得出结论:
-
如果preHandle没有放行,则执行afterCompletion(跳过postHandle)
-
preHandle是正序执行的
③ 再次回到doDispatch方法:
java
protected void doDispatch(){
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
//如果这里成立,则第1081行就不执行了
return;
}
//它是Hello01Controller的h02方法的调用
mv = ha.handle(request,response); //此处是第1081行
//1088行
//这句代码的作用是调用当前拦截器链的所有postHandle方法
mappedHandler.applyPostHandle();
}
④ 查看mappedHandler.applyPostHandle():
java
void applyPostHandle(){
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
通过上述代码得知:拦截器链的postHandle方法是倒序执行的
⑤ 再次回到doDispatch方法:
java
protected void doDispatch(){
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
//如果这里成立,则第1081行就不执行了
return;
}
//它是Hello01Controller的h02方法的调用
mv = ha.handle(request,response); //此处是第1081行
//1088行
//这句代码的作用是调用当前拦截器链的所有postHandle方法
mappedHandler.applyPostHandle();
//1098行
processDispatchResult(); //处理分发结果
}
查看processDispatchResult():
java
private void processDispatchResult(){
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
查看mappedHandler.triggerAfterCompletion()方法:
java
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.afterCompletion(request, response, this.handler, ex);
}
}
通过上述这个方法的代码得知:拦截器链的afterCompletion的调用也是倒序的
3. 数据校验
-
为什么需要数据校验?为什么需要双校验?
在数据操作之前,需要保证数据的合理性,所以需要数据校验;
双校验指的是客户端和服务器端都要进行数据校验。特殊的场景下,客户端会使用一些程序伪装成浏览器请求,这样的话客户端校验就绕开了,因此服务器端也必须校验。
-
服务器端如何进行数据校验
- 添加依赖: hibernate-validator
xml
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.2.5.Final</version>
</dependency>
- 在entity上使用JSR303相关的校验注解
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@NotNull
private Integer id ;
@Length(min = 5 , max = 20)
private String name ;
@Min(18)
@Max(99)
private Integer age ;
@Email
private String email ;
}
- 在控制器方法上使用@Validated进行数据校验。如果校验有问题,则错误信息会存放在BindingResult对象中
java
public Object add(@Validated User user , BindingResult bindingResult){
System.out.println("user = " + user);
if(bindingResult.hasErrors()){
//如果数据校验有错误,则错误信息会存放在bindingResult对象中
return bindingResult.getFieldError().toString();
}
return "succ" ;
}