Spring之拦截器

2年前 (2022) 程序员胖胖胖虎阿
225 0 0

一、Spring拦截器简介

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

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

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

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()方法,用于将拦截器添加到程序中。

@Configuration
public class MvcConfig implements WebMvcConfigurer {

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

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

@RestController
@RequestMapping("/interceptor")
public class DemoController {


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

}

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

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

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

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()**方法

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()。

测试发生异常的执行结果

@RestController
@RequestMapping("/interceptor")
public class DemoController {

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

执行结果如下:

Spring之拦截器

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

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

三、Spring拦截器应用案例

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

创建一个 @NotNull 注解

@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
}

创建一个参数校验拦截器

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

@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();
   }

}

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

@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);
   }
}

创建一个实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

   private String name;

   @NotNull
   private Integer age;
}

创建测试接口

@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字段,不能为空。

测试接口校验逻辑
Spring之拦截器
Spring之拦截器
如图传递了age参数,没有传递param参数,抛出了异常。

版权声明:程序员胖胖胖虎阿 发表于 2022年10月31日 上午8:16。
转载请注明:Spring之拦截器 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...