Java 之Interceptor--Servlet 请求的拦截

一、背景

需求是这样的:在表格中添加一个创建时间栏目,显示每条数据详细的创建时间,如图

本来这是一个很简单的需求,只需要在bootstrap 的column 中添加创建时间字段就可以,当然我也这么实现了,本以为没什么问题,但是测试妹子说删除功能不行了???不会吧,我就加一个字段而已,怎么删除不行了呢,之前还正常的啊。带着问题,我开始跟踪代码。

 1. 首先看到前台页面的删除功能的ajax 请求,没发现有什么问题

//删除多个文章
	$(".rightDelete").click(function(){
		var rows = $("#articleListTable").bootstrapTable("getSelections");
		if(rows!=""){
			$.ajax({
				url:xxx/delete.do",
				type:'post',
				dataType:'json',
				data:JSON.stringify(rows),
				contentType:'application/json',
				success:function(msg) {
					if (msg.result) {
					} else {
					}
				}
			});
		}else{
	    	<@ms.notify msg="请选择文章!" type="warning"/>
	    }
	});

2. 接着看后台方法,也没有说明问题,和之前一样,唯一不同的是,当我在delete 这个方法打了断点之后,请求并没有进入到delete 这个方法中。

@RequestMapping("/delete")
	@RequiresPermissions("article:del")
	public void delete(@RequestBody List<ArticleEntity> article, HttpServletRequest request, HttpServletResponse response) {
		int appId = this.getAppId(request);
		int[] ids = new int[article.size()];
		//循环获取id数据
		for(int i=0;i<article.size();i++){
			ids[i] = article.get(i).getArticleID();
		}
		
		if (ids.length == 0 || ids == null) {
			this.outJson(response, ModelCode.CMS_ARTICLE, false, "", this.redirectBack(request, false));
			return;
		}
		// 删除多个帖子
		articleBiz.deleteBasic(ids);
		this.outJson(response, ModelCode.CMS_ARTICLE, true, "", this.redirectBack(request, false));
	}

3. 进入调试模式,前台ajax 请求没有问题,后台没有进入到/delete 方法,但是这时候控制台的一条日志吸引了我的注意:

[xxx 2018-06-05 08:46:39,055](DEBUG) - xxx.xxx.basic.interceptor.ActionInterceptor - (BaseInterceptor.java:259) url:/xxx/error/400.do 

从这条信息可以看到一个陌生的类--ActionInterceptor ,这个类翻译为中文的大概意思就是动作拦截,并且拦截的结果是error/400.do。看到这里大概明白了一些,在我每次发起请求的时候是不是都会先经过这个这个拦截器,在拦截器做一些逻辑控制,如果请求的参数不符合要求则跳转到对应的错误页面,例如这里的400.do 页面。不多说,试一下就知道了呗,找到ActionInterceptor 入口的方法,发起请求,果然每个.do 方法都会被这个类拦截然后做逻辑判断,如果请求方法或者参数有错则会跳转到error/404.do,正常则直接跳转到请求的url 。

4. 回到开始的问题,那么我在发起删除请求的时候,被拦截之后,是什么原因导致拦截之后跳转到400 页面呢?

一步一步跟代码:ActionInterceptor.preHandle --> HandlerExecutionChain.applyPreHandle -->  DispatcherServlet.doDispatch 方法中抛出了异常,异常信息如下

org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize value of type java.sql.Timestamp from String "2018-05-31 17:18:53": not a valid representation (error: Failed to parse Date value '2018-05-31 17:18:53': Can not parse date "2018-05-31 17:18:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null))
at [Source: java.io.PushbackInputStream@ef552ae; line: 1, column: 3322] (through reference chain: java.util.ArrayList[0]->com.mingsoft.cms.entity.ArticleEntity["basicDateTime"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.sql.Timestamp from String "2018-05-31 17:18:53": not a valid representation (error: Failed to parse Date value '2018-05-31 17:18:53': Can not parse date "2018-05-31 17:18:53Z": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSS'Z'', parsing fails (leniency? null))
at [Source: java.io.PushbackInputStream@ef552ae; line: 1, column: 3322] (through reference chain: java.util.ArrayList[0]->xxx.xxx.entity.ArticleEntity["basicDateTime"])

大概意思就是无法解析日期,Json 格式的日期,无法解析为ArticleEntity 中的basicDateTime 的日期格式(Date),终于。问题原因找到了,解决办法有好几种,这里就不说了。

5. 刨根问底

根据第四点的描述可以知道,当我发起请求的时候,servlet 容器会先取访问DispatcherServlet 类中的 doDispatch 方法,我把这个方法的主要部分摘出来,主要看加粗部分:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		...
          HandlerExecutionChain mappedHandler = null; try { ... try { ... if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) {// 异常,刚才那个删除问题的异常会在这被捕获 dispatchException = ex; } catch (Throwable err) { ... } ... } catch (Exception ex) { ... } catch (Throwable err) { ... } finally { ... } }

再进入HandlerExecutionChain.applyPreHandle 方法

      /**
	 * Apply preHandle methods of registered interceptors.
	 * @return {@code true} if the execution chain should proceed with the
	 * next interceptor or the handler itself. Else, DispatcherServlet assumes
	 * that this interceptor has already dealt with the response itself.
	 * 执行已经注册的拦截的 preHandle()方法。如果返回true,则执行链可以执行下一个拦截器的preHandle()方法或 handler 自身
         */
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}             

已经注册的拦截器也就是ActionInterceptor.preHandle,这个拦截器实现了HandlerInterceptorAdapter 的preHandle 抽象方法,从而达到拦截所有.do 请求的目的。

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		...
		// 为request 添加一些属性
                // 初始化一些值    
		...
		return true;
	}    

二、Interceptor 简介

Java 里的拦截器是动态拦截 action 调用的对象。它提供了一种机制可以使开发者可以定义在一个 action 执行的前后执行的代码,也可以在一个 action 执行前阻止其执行,同时也提供了一种可以提取 action 中可重用部分的方式。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前进行拦截,然后在之前或之后加入某些操作。

三、Interceptor 原理

拦截器 Interceptor 的拦截功能是基于 Java 的动态代理来实现的,具体可以参考博文“ 用 Java 实现拦截器 Interceptor 的拦截功能 ”,也可以通过阅读 Spring 源代码来了解更为权威的实现细节。

四、自定义Interceptor 的实现

第一步:自定义一个实现了Interceptor接口的类,或者继承抽象类AbstractInterceptor

  SpringMVC还提供了HandlerInterceptorAdapter 其是抽象类,也是HandlerInterceptor的子类,在实现了HandlerInterceptor的三个函数后还增加了一个函数。

(1)preHandle: 在执行controller处理之前执行,返回值为boolean ,返回值为true时接着执行postHandle和afterCompletion,如果我们返回false则中断执行

(2)postHandle:在执行controller的处理后,在ModelAndView处理前执行

(3)afterCompletion :在DispatchServlet执行完ModelAndView之后执行

(4)afterConcurrentHandlingStarted:这个方法会在Controller方法异步执行时开始执行,而Interceptor的postHandle方法则是需要等到Controller的异步执行完才能执行,只要继承这个类并实现其方法就可以了。

package com.github.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * @date 2018年6月5日 上午10:57:04
 */
public class ActionInterceptor extends HandlerInterceptorAdapter {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("拦截器,拦截所有的action 请求");
		// ...
		// 初始化一些值
		// 对request 做一些操作,例如设置属性值
		// ...
		return true;
	}

}

第二步:在配置文件中注册定义的拦截器。

<mvc:interceptors>
		<!-- 使用 bean 定义一个 Interceptor,直接定义在 mvc:interceptors 下面的 Interceptor 将拦截所有的请求 -->  
   		 <bean class="com.github.interceptor.ActionInterceptor"/> 
		<mvc:interceptor>
			<mvc:mapping path="/**" />
			<!-- 定义在 mvc:interceptor 下面的 Interceptor,表示对特定的请求进行拦截 -->
			<bean class="com.github.interceptor.xxxActionInterceptor" />
		</mvc:interceptor>
	</mvc:interceptors>

  

第三步:在需要使用Action中引用上述定义的拦截器,为了方便也可以将拦截器定义为默认的拦截器,这样在不加特殊说明的情况下,所有的都被这个拦截器拦截。

 五、过滤器与拦截器的区别

过滤器可以简单的理解为“取你所想取”,过滤器关注的是web请求;拦截器可以简单的理解为“拒你所想拒”,拦截器关注的是方法调用,比如拦截敏感词汇。

1. 拦截器是基于java反射机制来实现的,而过滤器是基于函数回调来实现的。(有人说,拦截器是基于动态代理来实现的)

2. 拦截器不依赖servlet容器,过滤器依赖于servlet容器。

3. 拦截器只对Action起作用,过滤器可以对所有请求起作用。

4. 拦截器可以访问Action上下文和值栈中的对象,过滤器不能。

5. 在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。

 六、总结

回到最开始的问题,我们发起请求时,经过拦截器拦截,对request 作了一系列的操作,此时拦截器的注册没有问题。当json 数据转换为article 映射数据时某个字段解析异常导致,整个actico 请求出错。

参考:https://blog.csdn.net/qq924862077/article/details/53524507  

猜你喜欢

转载自www.cnblogs.com/hellovoyager1/p/9138589.html