在web应用开发时,我们经常会需要拦截请求,在请求前后做一些事,比如打印日志等,这种方式就相当于spring中的面向切面编程,以下总结了下实现这种方式的一些方法。
###1. 使用拦截器+过滤器
这种方法是目前比较通用的方法,不受限于框架限制,不管是用SSH还是springmvc都可以采用,实现方式大致如下:
(1)定义一个拦截器,比如WebRequestInterceptor,在preHandle里面拦截request,从中取出参数等信息,然后打印出来即可。
(2)定义一个过滤器,比如WebResponseFilter,在doFilter里面使用HttpServletResponseWrapper包装传进来的response,然后重写一些方法即可实现。
(注意:这里有个坑,就是直接使用拦截器无法从response里面取出返回值!)
(3)相关代码大致如下:
WebInterceptor.java
package com.zuolin.interceptor;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.zuolin.filter.WrapperResponse;
public class WebInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(WebInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
if (logger.isDebugEnabled()) {
StringBuffer sb = new StringBuffer();
sb.append("{");
Enumeration headers = request.getHeaderNames();
int i = 0;
while (headers.hasMoreElements()) {
String header = headers.nextElement();
if (i > 0)
sb.append(", ");
sb.append(header + ": " + request.getHeader(header));
i++;
}
sb.append("}");
logger.debug("Pre handling request: {}, headers: {}", getRequestInfo(request, true), sb.toString());
}
return true;
}
private static String getRequestInfo(HttpServletRequest request, boolean requestDetails) {
StringBuffer sb = new StringBuffer();
sb.append(request.getMethod()).append(" ");
sb.append(request.getRequestURI());
if (requestDetails) {
Enumeration e = request.getParameterNames();
sb.append("{");
int i = 0;
while (e.hasMoreElements()) {
String name = e.nextElement();
String val = request.getParameter(name);
if (val != null && !val.isEmpty()) {
if (i > 0)
sb.append(", ");
sb.append(name).append(": ").append(val);
i++;
}
}
sb.append("}");
}
return sb.toString();
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object,
Exception exception) throws Exception {
String requestInfo = getRequestInfo(request, false);
if (logger.isDebugEnabled()) {
logger.debug("Complete request: {}", requestInfo);
}
}
}
WrapperOutputStream.java
package com.zuolin.filter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
public class WrapperOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos) {
this.bos = bos;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
}
WrapperResponse.java
package com.zuolin.filter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class WrapperResponse extends HttpServletResponseWrapper{
private ByteArrayOutputStream buffer;
private ServletOutputStream out;
public WrapperResponse(HttpServletResponse response) throws UnsupportedEncodingException {
super(response);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
}
public byte[] getContent() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
}
ResponseFilter.java
package com.zuolin.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ResponseFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ResponseFilter.class);
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
WrapperResponse wrapperResponse = new WrapperResponse((HttpServletResponse) response);
filterChain.doFilter(request, wrapperResponse);
byte[] content = wrapperResponse.getContent();
if (logger.isDebugEnabled() && content != null && content.length > 0) {
logger.debug("Response Content: {}", new String(content));
}
ServletOutputStream out = response.getOutputStream();
out.write(content);
out.flush();
}
@Override
public void init(FilterConfig paramFilterConfig) throws ServletException {
}
}
然后记得在web.xml或spring的配置文件中加上以上类的定义就可以直接使用了。
这种方式有个缺陷,就是要写很多代码,而且一般人看不懂这里的流程,因为使用的是流。
###2. 使用spring中的advice
这种就是spring里面的面向切面编程的使用了,这种方式只能依赖于spring的aspect存在,实现方法为:
(1)声明一个类,比如叫WebRequestAroundAdvice;
(2)定义切入点;
(3)定义切入点的处理方法。
(4)相关代码如下:
WebRequestAroundAdvice.java
package com.zuolin.interceptor;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSONObject;
@Aspect
public class WebRequestAroundAdvice {
private static final Logger logger = LoggerFactory.getLogger(WebRequestAroundAdvice.class);
@Pointcut( value = "execution(* com.zuolin.controller.*.*(..))" )
public void pointcut(){}
@Around("pointcut()")
public Object handle(ProceedingJoinPoint joinPoint) throws Throwable{
preHandle();
Object retVal = joinPoint.proceed();
postHandle(retVal);
return retVal;
}
private void preHandle() {
if (logger.isDebugEnabled()) {
HttpServletRequest request = ( (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ).getRequest();
StringBuffer sb = new StringBuffer();
sb.append("{");
Enumeration headers = request.getHeaderNames();
int i = 0;
while (headers.hasMoreElements()) {
String header = headers.nextElement();
if (i > 0)
sb.append(", ");
sb.append(header + ": " + request.getHeader(header));
i++;
}
sb.append("}");
logger.debug("Pre handling request: {}, headers: {}", getRequestInfo(request, true), sb.toString());
}
}
private void postHandle(Object retVal) {
if (logger.isDebugEnabled()) {
HttpServletRequest request = ( (ServletRequestAttributes) RequestContextHolder.getRequestAttributes() ).getRequest();
logger.debug("Post handling request: {}, response: {}", getRequestInfo(request, false), JSONObject.toJSONString(retVal));
}
}
private String getRequestInfo(HttpServletRequest request, boolean requestDetails) {
StringBuffer sb = new StringBuffer();
sb.append(request.getMethod()).append(" ");
sb.append(request.getRequestURI());
if (requestDetails) {
Enumeration e = request.getParameterNames();
sb.append(" ").append("{");
int i = 0;
while (e.hasMoreElements()) {
String name = e.nextElement();
String val = request.getParameter(name);
if (val != null && !val.isEmpty()) {
if (i > 0)
sb.append(", ");
sb.append(name).append(": ").append(val);
i++;
}
}
sb.append("}");
}
return sb.toString();
}
}
然后在spring的配置文件中配置这个bean就可以直接使用了。
这种方式的优点就是代码很简洁,只要稍微学过一点AOP都能很容易理解,缺点就是依赖于spring。
这里只使用了spring的around advice,当然,使用其它的几种advice的组合也可以实现相同的效果,或者混合使用拦截器和advice也可以,关键在于你。
欢迎关注我的公众号“彤哥读源码”,查看更多“源码&架构&算法”系列文章, 与彤哥一起畅游源码的海洋。