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

 

终于大功告成了,在此过程中,熟悉了过滤器和拦截器的各种坑,也算是有收获

 

posted @ 2019-08-14 21:36  文刂亻右景彡  阅读(1568)  评论(0编辑  收藏  举报