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; }
终于大功告成了,在此过程中,熟悉了过滤器和拦截器的各种坑,也算是有收获