前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring之拦截器

Spring之拦截器

作者头像
Java架构师必看
发布2022-07-06 15:39:13
4480
发布2022-07-06 15:39:13
举报
文章被收录于专栏:Java架构师必看

大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说Spring之拦截器,希望能够帮助大家进步!!!

一、Spring拦截器简介

Spring拦截器是一种基于AOP的技术,本质也是使用一种代理技术,它主要作用于接口请求中的控制器,也就是Controller。因此它可以用于对接口进行权限验证控制。

下面我们看一个简单的拦截器例子

创建一个DemoInterceptor类实现HandlerInterceptor接口,重写preHandle(),postHandle(),afterCompletion() 三个方法,如下代码,我们就创建了一个Spring的拦截器。

代码语言:javascript
复制
public class DemoInterceptor implements HandlerInterceptor { 
   

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
   
		System.out.println("preHandle......");
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
   
		System.out.println("postHandle......");
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
   
		System.out.println("afterCompletion......");
	}
}

创建拦截器之后,我们还需要将其注册到Spring程序中,以便启用它。

注册拦截器

创建一个Spring配置类实现WebMvcConfigurer接口,并重写addInterceptors()方法,用于将拦截器添加到程序中。

代码语言:javascript
复制
此代码由Java架构师必看网-架构君整理
@Configuration
public class MvcConfig implements WebMvcConfigurer { 
   

   @Override
   public void addInterceptors(InterceptorRegistry registry) { 
   
      registry.addInterceptor(new DemoInterceptor());
   }
}

创建一个接口用于查看拦截器的执行效果

代码语言:javascript
复制
@RestController
@RequestMapping("/interceptor")
public class DemoController { 
   


   @GetMapping("/demo")
   public String demoAction() { 
   
      System.out.println("interceptor-demo......");
      return "success";
   }

}

测试接口

如控制台打印,preHandle()方法在Controller方法执行之前执行,postHandle()与afterCompletion()方法都在打印语句之后,那它的执行顺序是什么样的呢?

二、Spring拦截器执行源码解析

主要执行代码在DispatcherServlet类中,其中有个**doDispatch()**的方法他就是做handler执行的,也就是执行接口处理逻辑,其中一系列的拦截器执行顺序写在此处。

代码语言:javascript
复制
此代码由Java架构师必看网-架构君整理
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 
   
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try { 
   
      ModelAndView mv = null;
      Exception dispatchException = null;

      try { 
   
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // 获取指定的控制器处理器
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) { 
   
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) { 
   
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 
   
               return;
            }
         }
		 // 此处首先调用拦截器的preHandle()方法
         if (!mappedHandler.applyPreHandle(processedRequest, response)) { 
   
            return;
         }

         // 执行Controller的处理器
		 // 并返回ModelAndView对象
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) { 
   
            return;
         }

         applyDefaultViewName(processedRequest, mv);
		 // 此处调用postHandle()方法
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) { 
   
         dispatchException = ex;
      }
      catch (Throwable err) { 
   
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      // 正常执行完流程后通过该方法触发afterCompletion()方法
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) { 
   
	  // 程序发生异常,也会触发afterCompetion()方法
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) { 
   
      // 程序发生异常,也会触发afterCompetion()方法
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally { 
   
      if (asyncManager.isConcurrentHandlingStarted()) { 
   
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) { 
   
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else { 
   
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) { 
   
            cleanupMultipart(processedRequest);
         }
      }
   }
}

接下来看一下**processDispatchResult()**方法

代码语言:javascript
复制
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception { 
   

	boolean errorView = false;

	if (exception != null) { 
   
		if (exception instanceof ModelAndViewDefiningException) { 
   
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else { 
   
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}

	// Did the handler return a view to render?
	if (mv != null && !mv.wasCleared()) { 
   
		render(mv, request, response);
		if (errorView) { 
   
			WebUtils.clearErrorRequestAttributes(request);
		}
	}
	else { 
   
		if (logger.isTraceEnabled()) { 
   
			logger.trace("No view rendering, null ModelAndView returned.");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 
   
		// Concurrent handling started during a forward
		return;
	}
        // 如下代码则进行触发afterCompletion()方法 
	if (mappedHandler != null) { 
   
		// Exception (if any) is already handled..
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
ler.triggerAfterCompletion(request, response, null);
	}
}

从上述源码中我们会发现,当Controller处理器中发生异常返回时,代码将进入catch代码块,同样也会执行atfterCompletion()。

测试发生异常的执行结果

代码语言:javascript
复制
@RestController
@RequestMapping("/interceptor")
public class DemoController { 
   

   @GetMapping("/demo")
   public String demoAction() { 
   
      System.out.println("interceptor-demo......");
      System.out.println(1/0);
      return "success";
   }
}

执行结果如下:

如图我们可以看到没有执行postHandle() 方法

总结:preHandle() 在处理器执行之前执行,postHandle() 在处理器执行之后执行,当处理器发生异常返回时,postHandle() 将不会执行,而afterCompletion() 则一定会执行,相当于try catch finally中的finally。

三、Spring拦截器应用案例

本节通过使用Spring拦截器实现一个简单的接口数据验证功能,功能实现只需要通过在接口方法的参数上添加 @NotNull 或者实体类中的字段上添加 @NotNull 即可验证该字段是否为空。

创建一个 @NotNull 注解

代码语言:javascript
复制
@Target({ 
   ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull { 
   
}

创建一个参数校验拦截器

拦截器中我们注入了一个 LocalVariableTableParameterNameDiscoverer 对象,它的作用是可以获取处理器上的方法参数名,以此用于校验请求参数。该对象方法也是Spring提供,不过需要我们手动注册该Bean。

代码语言:javascript
复制
@Component
public class ParamValidInterceptor implements HandlerInterceptor { 
   

   @Resource
   LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer;

   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
   
      if (handler instanceof HandlerMethod) { 
   
         HandlerMethod handlerMethod = (HandlerMethod) handler;
         // 获取所有参数名称
         String[] parameterNames = parameterNameDiscoverer.getParameterNames(handlerMethod.getMethod());
         // 获取所有请求参数
         Map<String, String[]> paramMap = paramMap(request);
         int paramIndex = 0;
         MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
         if (methodParameters.length > 0
               && (Objects.isNull(parameterNames) || parameterNames.length == 0)) { 
   
            throw new RuntimeException("参数名为空");
         }
         for (MethodParameter methodParameter : methodParameters) { 
   
            System.out.println(parameterNames[paramIndex]);
            NotNull notNull = methodParameter.getParameterAnnotation(NotNull.class);
            if (isBaseType(methodParameter.getParameterType())
                  && Objects.nonNull(notNull)
                  && Objects.isNull(paramMap.get(parameterNames[paramIndex]))) { 
   
               throw new RuntimeException("参数["+ parameterNames[paramIndex] +"]不能为空");
            }
            if (!isBaseType(methodParameter.getParameterType())) { 
   
               Class<?> parameterType = methodParameter.getParameterType();
               Field[] fields = parameterType.getDeclaredFields();
               for (Field field : fields) { 
   
                  paramValidator(field, paramMap);
               }
            }

            paramIndex ++;
         }
      }
      return true;
   }

   public void paramValidator(Field parameterField, Map<String, String[]> paramMap) { 
   
      parameterField.setAccessible(true);
      Class<?> parameterType = parameterField.getType();
      if (isBaseType(parameterType)) { 
   
         String fieldName = parameterField.getName();
         NotNull fieldNotNull = parameterField.getAnnotation(NotNull.class);
         if (Objects.nonNull(fieldNotNull) && Objects.isNull(paramMap.get(fieldName))) { 
   
            throw new RuntimeException("参数["+ fieldName +"]不能为空");
         }
      } else { 
   
         Field[] fields = parameterType.getDeclaredFields();
         for (Field field : fields) { 
   
            paramValidator(field, paramMap);
         }
      }
   }

   public boolean isBaseType(Class<?> parameterType) { 
   
      switch (parameterType.getName()) { 
   
         case "java.lang.Integer":
         case "java.lang.Long":
         case "java.lang.Short":
         case "java.lang.Byte":
         case "java.lang.Boolean":
         case "java.lang.Character":
         case "java.lang.Float":
         case "java.lang.Double":
         case "java.lang.String":
         case "java.util.Date":

            return true;
         default: return false;
      }
   }

   public Map<String, String[]> paramMap(HttpServletRequest request) { 
   
      return request.getParameterMap();
   }

}

注册拦截器并配置所需对象

代码语言:javascript
复制
@Configuration
public class MvcConfig implements WebMvcConfigurer { 
   

   @Bean
   public ParameterNameDiscoverer parameterNameDiscoverer() { 
   
      return new LocalVariableTableParameterNameDiscoverer();
   }

   @Resource
   private ParamValidInterceptor paramValidInterceptor;

   @Override
   public void addInterceptors(InterceptorRegistry registry) { 
   
      registry.addInterceptor(paramValidInterceptor);
   }
}

创建一个实体类

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person { 
   

   private String name;

   @NotNull
   private Integer age;
}

创建测试接口

代码语言:javascript
复制
@RestController
@RequestMapping("/interceptor")
public class DemoController { 
   


   @GetMapping("/demo")
   public String demoAction(Person person, @NotNull String param) { 
   
      System.out.println("person--->" + person);
      return "success";
   }

}

上述接口中将进行校验param参数以及person对象中的age字段,不能为空。

测试接口校验逻辑

如图传递了age参数,没有传递param参数,抛出了异常。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-282,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Spring拦截器简介
  • 二、Spring拦截器执行源码解析
  • 三、Spring拦截器应用案例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档