Java學習-5 折騰了幾晚的Filter、HandlerInterceptorAdapter、RequestWrapper


直接上代碼吧,懶得寫了

 

1、過濾器,難點:

service的調用,網上說可以Autowired,我試了半天不行,最后在init里去手動是可以了,而且觀察也只會初始化一次,一樣的。

body參數的獲取后,流會關閉。如果不考慮其它地方還要再取值,可用ContentCachingRequestWrapper。

如果在攔截器等地方還要再取的話,要用RequestWrapper,代碼在后面

package com.filter;

import com.model.user.User;
import com.service.sys.LogService;
import com.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

public class LoginFilter implements Filter {
    //無法用AutoWired自動注入,改為在init里手動注入
    private LogService logService;

    @Override
    public void init(FilterConfig filterConfig) {
        /*
        //試了半天DelegatingFilterProxy,一直不行,還是手動注入可以就算了,跟蹤發現只會調用一次,也一樣
        if (logService == null) {
            ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
            LogService logService = context.getBean(LogService.class);
            this.logService = logService;
        }
        */
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //改在攔截器里處理,此處直接放過
//        chain.doFilter(request, response);

        RequestWrapper requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            requestWrapper = new RequestWrapper((HttpServletRequest) request);
        }
        if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }


        /*
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;
        HttpSession session = servletRequest.getSession();

        // 獲得用戶請求的URI
        String path = servletRequest.getRequestURI();
        User user = (User) session.getAttribute("user");

        // 登陸頁面無需過濾
        if (path.contains("/login/")) {
            doFilter(servletRequest, servletResponse, chain, user);
            return;
        }
        // 判斷如果沒有取到賬號信息,就跳轉到登陸頁面
        if (user == null) {
            // 跳轉到登陸頁面
            servletResponse.sendRedirect(servletRequest.getContextPath() + "/login/index.do");
        } else {
            // 已經登陸,繼續此次請求。如有需要,可以再驗證下賬號的操作權限,以防跳過前端界面,直接用工具后台發起的請求
            doFilter(servletRequest, servletResponse, chain, user);
        }
        */
    }

    void doFilter(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain chain, User user) throws IOException, ServletException {
        String contentType = servletRequest.getContentType();
        String method = servletRequest.getMethod();
        String path = servletRequest.getServletPath();

        if (contentType != null && contentType.startsWith("multipart/form-data") && "POST".equalsIgnoreCase(method)) {
            System.out.println("當前請求為文件上傳,不作請求日志收集");
        } else {
            ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(servletRequest);
            ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(servletResponse);
            long start = new Date().getTime();
            try {
                chain.doFilter(requestWrapper, responseWrapper);
            } finally {
                String requestBody = servletRequest.getQueryString();
                if (StringUtils.isEmpty(requestBody)) {
                    requestBody = new String(requestWrapper.getContentAsByteArray());
                }
                responseWrapper.copyBodyToResponse();
            }
        }
    }

    @Override
    public void destroy() {

    }
}

 

2、RequestWrapper

網上最多的就是這個,但我試了半天,一直踩坑。主要有:

import javax.servlet.ReadListener;  這個找不到,后來發現是servlet版本太低,升高版本就有了

照着抄后,post提交的body仍然消失了(但沒報錯),表現在比如登錄,明明輸了賬號密碼,也能在Filter里接收到參數,但Controller就是為空,坑了幾晚

package com.filter;

import com.util.HttpUtil;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;

public class RequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        body = HttpUtil.getBodyString(request).getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read() {
                return bais.read();
            }
        };
    }

    @Override
    public String getHeader(String name) {
        return super.getHeader(name);
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return super.getHeaderNames();
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        return super.getHeaders(name);
    }
}

 

3、關鍵的就在這里,最后還是一篇貼子救了命

http://blog.sina.com.cn/s/blog_550048a30102x7pp.html

在大神的代碼下,終於可以了,淚流滿面!

其實之前兩種代碼風格都有見過,就是不知道要判斷一下,按contenttype來區分

public static String getBodyString(ServletRequest request) {
        String contenttype = request.getContentType();
        if (contenttype != null && contenttype.contains("x-www-form-urlencoded")) {
            String bodystring = "";
            Enumeration pars = request.getParameterNames();
            while (pars.hasMoreElements()) {
                String n = (String) pars.nextElement();
                bodystring += n + "=" + request.getParameter(n) + "&";
            }
            bodystring = bodystring.endsWith("&") ? bodystring.substring(0, bodystring.length() - 1) : bodystring;
            return bodystring;

        } else if (contenttype != null && contenttype.contains("json")) {
            StringBuilder sb = new StringBuilder();
            InputStream inputStream = null;
            BufferedReader reader = null;
            try {
                inputStream = request.getInputStream();
                reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return sb.toString();
        }
        return "";
    }

 

4、過濾器,如果前面沒處理好,這邊要么是獲取到的post是空,要么一獲取完post,就會導致Controller沒值,或是報什么流關閉之類的,也是折騰了幾晚

package com.filter;

import com.model.sys.Log;
import com.model.user.User;
import com.service.sys.LogService;
import com.util.HttpUtil;
import com.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class AuthInterceptor extends HandlerInterceptorAdapter {
    @Autowired
    private LogService logService;

    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getRequestURI();
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        // 登陸頁面無需過濾
        if (path.contains("/login/")) {
            return true;
        }
        // 判斷如果沒有取到賬號信息,就跳轉到登陸頁面
        if (user == null) {
            // 跳轉到登陸頁面
            response.sendRedirect(request.getContextPath() + "/login/index.do"); //response是整個頁面跳轉
            //request.getRequestDispatcher("/login/index.do").forward(request, response); //request是內部重定向
            return true;
        } else {
            startTimeThreadLocal.set(System.currentTimeMillis());//線程安全(該數據只有當前請求的線程可見)
            return true;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException {
        Object startTimeObj = startTimeThreadLocal.get();
        if (startTimeObj == null) return;

        long time = System.currentTimeMillis() - startTimeThreadLocal.get();
        HandlerMethod methodHandler = (HandlerMethod) handler;
        HttpSession session = request.getSession();
        String path = request.getServletPath();
        User user = (User) session.getAttribute("user");
        AuthAnnotation authAnnotation = methodHandler.getMethodAnnotation(AuthAnnotation.class);
        String description = authAnnotation != null ? authAnnotation.description() : null;

        String params = request.getQueryString();
        if (StringUtils.isEmpty(params)) {
            params = HttpUtil.getBodyString(request);
        }

        Log log = new Log();
        log.setCrudType("MVC");
        log.setUserId(user.getId());
        log.setUrl(path);
        log.setDescription(description);
        log.setRespTime(StringUtils.toInteger(time));
        log.setParams(params);
        logService.insert(log);
    }
}

 

5、本來在Filter里處理也是可以,但Filter處理不了控制器的屬性(或是我不會),最終換到攔截器里處理,過濾器本來直接放空,但發現不行,就是上面說的流只能讀一次的問題。現在過濾器就是中轉一下,業務的驗證邏輯還是放在攔截器里處理,就是為了這種控制器上面自定義的屬性

  @AuthAnnotation(description = "業務流程", authType = AuthAnnotation.AuthType.LOGIN)
    @RequestMapping(value = "/wfFlow")
    public String wfFlow() {
        return "views/workflow/wfFlow";
    }
package com.filter;

import java.lang.annotation.*;

@Documented //文檔生成時,該注解將被包含在javadoc中,可去掉
//@Target(ElementType.METHOD)//目標是方法
@Retention(RetentionPolicy.RUNTIME) //注解會在class中存在,運行時可通過反射獲取
@Inherited
public @interface AuthAnnotation {
    String description();

    enum AuthType {
        PUBLIC, LOGIN, EDIT
    }

    AuthType authType() default AuthType.LOGIN;
}

 

終於大功告成了,在此過程中,熟悉了過濾器和攔截器的各種坑,也算是有收獲

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM