一、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";
}
}
测试接口
如控制台打印,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";
}
}
执行结果如下:
如图我们可以看到没有执行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字段,不能为空。
测试接口校验逻辑
如图传递了age参数,没有传递param参数,抛出了异常。