前言:
上一篇介紹了,使用AOP的方式去攔截校驗參數,本章講解使用攔截器去校驗參數,以及遇到的問題。
簡介:
Spring web mvc 處理攔截器,就是案例所用到的去校驗參數,類似與serlvet開發中裏的filter過濾器。用於對攔截前及後處理。
常見場景:
一、攔截器類:
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
preHandle:預處理回調方法,實現攔截器的預處理,第三個參數爲響應的處理器;返回值:true表示繼續流程;false表示流程中斷(如校驗失敗,參數缺少),不會繼續調用其他的攔截器或處理器,此時我們需要通過response來產生響應;
postHandle:後處理回調方法,實現處理攔截器的後處理(返回請求結果之前)。
afterCompletion:整個請求處理完畢回調方法,在返回結果之後進行調用,但僅調用處理器執行鏈中preHandle返回true的攔截器的afterCompletion。
二、攔截器適配器
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
/**
* This implementation always returns {@code true}.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* This implementation is empty.
*/
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
/**
* This implementation is empty.
*/
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
/**
* This implementation is empty. 這個方法實現自AsyncHandlerInterceptor
*/
@Override
public void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
}
}
三、流程圖如下:
1、攔截器執行順序是按照Spring配置文件中定義的順序而定的。
2、會先按照配置攔截器的順序執行所有攔截器的preHandle方法,一直遇到return false爲止,比如第二個preHandle方法是return false,則第三個以及以後所有攔截器都不會執行。若都是return true,則按配置的攔截器配置順序加載完preHandle方法。
3、然後執行目標方法(目標controller接口),若中間拋出異常,則跟return false效果一致,不會繼續執行postHandle,只會倒序執行afterCompletion方法。
4、在目標方法執行完業務邏輯(頁面還未渲染數據)時,按倒序執行postHandle方法。若第三個攔截器的preHandle方法return false,則會執行第二個和第一個的postHandle方法和afterCompletion(postHandle都執行完纔會執行這個,也就是頁面渲染完數據後,執行after進行清理工作)方法。(postHandle和afterCompletion都是倒序執行)
四、校驗參數demo:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckParam {
/**
* 請求當前接口所需要的參數,多個以小寫的逗號隔開
* @return
*/
String fieldNames() default "";
/**
*傳遞參數的對象類型
*/
Class<?> parameter() default Object.class;
/**
* 默認不校驗參數;
* @return
*/
boolean require() default false;
}
請求中獲取流:public class HttpHelper {
public static String getBodyString(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
try(BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), Charset.forName("UTF-8")))) {
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
return sb.toString();
}
}
攔截器實現:public class OpenHandlerInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = Logger.getLogger(OpenHandlerInterceptor.class);
protected static final String APPLICATION_CHARSET="application/json; charset=utf-8";
protected static final String UTF_8 = "UTF-8";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
if(handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
CheckParam checkParam = method.getAnnotation(CheckParam.class);
if(checkParam == null || !checkParam.require() || StringUtils.isEmpty(checkParam.fieldNames())) {
return true;
}
//防止中文,符號
String fieldNames = checkParam.fieldNames().replace(",",",");
boolean jsonParam = CheckFieldsNotNull(request, fieldNames);
if(!jsonParam){
ErrorMsg(response);
return false;
}
}
} catch (Exception e) {
e.printStackTrace();
ErrorMsg(response);
return false;
}
return true;
}
/**
* 校驗必傳參數屬性是否存在
* @param request
* @param fieldNames
* @return
* @throws Exception
*/
public static boolean CheckFieldsNotNull(HttpServletRequest request, String fieldNames) throws Exception{
LOGGER.info(String.format("校驗字段:{%s}",fieldNames));
String requestParam = HttpHelper.getBodyString(request);
if(StringUtils.isEmpty(requestParam)){
return false;
}
Gson gson = new Gson();
ArrayList<Map> arrayList = gson.fromJson(requestParam, ArrayList.class);
for (String fieldName : fieldNames.split(",")) {
Preconditions.checkNotNull(arrayList.get(0).get(fieldName),fieldName + "is null");
}
return true;
}
/**
* 錯誤信息
* @param response
* @throws IOException
*/
public static void ErrorMsg( HttpServletResponse response) throws Exception {
PrintWriter out = response.getWriter();
response.setCharacterEncoding(UTF_8);
response.setContentType(APPLICATION_CHARSET);
out.write(JsonMapper.toJsonString(Result.fail(ErrorMessageEnum.MISS_REQUEST_PARAM.getValue(),ErrorMessageEnum.MISS_REQUEST_PARAM.getDesc())));
}
}
以上代碼就實現了攔截器的參數校驗,但是從中遇到一個問題,流只能讀一次。所以我們在controller再次獲取的是null值,爲了解決請求中的流多次複用,所以實現了一個Filter,通過Filter來解決流只能讀取一次的問題。代碼如下:public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestReaderHttpServletRequestWrapper(ServletRequest request) throws IOException {
super(request);
body = HttpHelper.getBodyString(request).getBytes(Charset.forName(OpenHandlerInterceptor.UTF_8));
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
以上類繼承自:
HttpServletRequestWrapper
這個類是HttpServletRequest的一個包裝類,大家應該知道裝飾器模式。這個類對接下來過濾器中的使用是有非常重要的意義的。public class HttpServletRequestReplacedFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
//獲取請求中的流如何,將取出來的字符串,再次轉換成流,然後把它放入到新request對象中。
// 在chain.doFiler方法中傳遞新的request對象
if(requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
總結: