首先说到这几个词的时候,大家肯定都很熟悉了,甚至觉得这几个的区别刚刚毕业都能回答了,但是我想大家在实际应用过程中是真得会真正的使用吗?换言之,什么时候用过滤器什么时候使用拦截器,什么时候使用springAop呢?很多时候不觉得这些都可以实现吗?比如日志的打印,我觉得都可以实现,那在设计架构的时候应该如何去考量呢?废话不多说,接下来我会用实际的案例来展示,大家看看是否真的如你们心里所想的呢?或者有么有更好的方案欢迎评论区留言!!!
首先说一些基础的它们之间的执行顺序
作用域大小不一样。
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller
中渲染了对应的视图之后请求结束。过滤器/拦截器与aop获取的信息不一样。过滤器/拦截器获取的是请求级别的信息,如url、传参、header、返回值等信息。
aop不能获取请求的参数等信息,和返回结果。aop能获取到调用的类,方法及方法参数。以及方法的返回值。
如果想通过方法进行拦截/过滤等处理,需要使用aop。如果想通过请求判断,需要使用过滤器。但是aop获取的是方法级别的信息,类、方法、方法的传参等等。
背景
现在有个业务场景就是通过前后端密文加解密实现参数传递,前端都是通过密文传递的并且是通过post请求方式的,后端之间使用对象来映射的,我们想通过上面的方式实现和业务解耦
方案讨论
过滤器可以获取到所有的request和response,是根据url匹配的
拦截器获取不到request和response参数,但是可以获取到所有的方法、类、参数等信息,也是根据url匹配的
springAop拦截器获取不到request和response参数,但是可以获取到所有的方法、类、参数等信息,是根据切点的也就是方法来匹配的
还有一点区别那就是如果是get请求可以很轻松获取但是如果是post那就要注意了,因为body的数据读取是通过流形式读取的,如果你在过滤器中读取到了,那么在controller中mvc参数映射的时候就会报错,因为拿不到body的值,因为流读取只会读取到一次,你在过滤器中读取到了,那么mvc参数映射的时候就无法读取不到,还有就是springAop或者是拦截器最早发生的时机也需要发生在mvc参数映射之后的,否则怎么能拿到参数信息呢,这点就是提下
那么既然这么说了,肯定有办法解决嘛,那就是你获取到了body的数据,就需要重新包装下然后set回去
springAop
import lombok.Data;
/**
* 责任链接口
*
* @author fangh
* @date 2023-02-09 11:22
*/
@Data
public abstract class ProcessingObject<T> {
private ProcessingObject<T> successor;
public T handle(T input, String apiName) {
T t = handleWork(input, apiName);
if (successor != null) {
return successor.handle(t, apiName);
}
return t;
}
/**
* 子类的具体处理方法
*
* @param input 待处理的参数
* @param apiName 接口名
* @return 处理后的参数
*/
abstract protected T handleWork(T input, String apiName);
}
import com.fh.inter.mgmt.service.GetVehicleInfoService;
import com.fh.inter.mgmt.service.impl.GenerateReturnSign;
import com.fh.standard.config.SystemCommonConfig;
import com.fh.standard.constant.MdcConstant;
import com.fh.standard.dto.BaseDTO;
import com.fh.standard.enums.ApiEnum;
import com.fh.standard.enums.ApiResultEnum;
import com.fh.standard.exception.BusinessException;
import com.fh.standard.vo.BaseVO;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
/**
* <p>AOP 类,包含请求/响应日志打印、签名验证、车辆信息校验等</p>
* <p>需要留意下 AOP 的执行顺序:</p>
*
* @author fangh
* @date 2023-02-09 11:22
*/
@Aspect
@Component
@Order(0)
@Slf4j
@RequiredArgsConstructor
public class RequestAop {
/**
* 请求参数日志打印
*/
private final RequestLogPrint p1;
/**
* 签名处理
*/
private final ValidateSign p2;
/**
* 校验请求参数
*/
private final CheckRequestParam p3;
private final ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal<>();
/**
* 返回报文签名生成
*/
private final GenerateReturnSign generateReturnSign;
private final GetVehicleInfoService getVehicleInfoService;
private final SystemCommonConfig config;
/**
* Key:apiName 接口方法名 Value:apiDesc 接口方法名中文描述
*/
private final static Map<String, String> METHOD_MAP = Maps.newHashMapWithExpectedSize(ApiEnum.values().length);
static {
METHOD_MAP.put(ApiEnum.RDS_REGISTER.getMethodName(), ApiEnum.RDS_REGISTER.getApiName());
METHOD_MAP.put(ApiEnum.RDS_CHECK.getMethodName(), ApiEnum.RDS_CHECK.getApiName());
METHOD_MAP.put(ApiEnum.RDS_TASK_INFO.getMethodName(), ApiEnum.RDS_TASK_INFO.getApiName());
METHOD_MAP.put(ApiEnum.RDS_DOWNlOAD_STATE_REPORT.getMethodName(), ApiEnum.RDS_DOWNlOAD_STATE_REPORT.getApiName());
METHOD_MAP.put(ApiEnum.RDS_REPORT_LOG.getMethodName(), ApiEnum.RDS_REPORT_LOG.getApiName());
}
/**
* 对 Controller 做切点
*/
@Pointcut("execution(* com.adups.inter.mgmt.controller..*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void doBefore(JoinPoint joinPoint) {
// 接口形参列表
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof BaseDTO) {
BaseDTO baseDTO = (BaseDTO) arg;
String methodName = currentMethod(joinPoint);
// 请求接口名称
String apiName = METHOD_MAP.get(methodName);
// 请求参数日志打印 -> 非注册接口的车辆信息获取 -> 验签 -> ecuDids 解析 -> ecuPartNum 解析 -> exts 解析
// 设置调用顺序
p1.setSuccessor(p2);
p2.setSuccessor(p3);
// 开始按顺序链式处理
p1.handle(baseDTO, apiName);
break;
}
}
/**
* 此处因为在后置处理器中获取不到同线程的MDC值,只能通过此种方式传递
*/
Map<String, String> signMap = Maps.newHashMapWithExpectedSize(1);
signMap.put(MdcConstant.SIGN_KEY, MDC.get(MdcConstant.SIGN_KEY));
signMap.put(MdcConstant.ENCRYPT_KEY, MDC.get(MdcConstant.ENCRYPT_KEY));
threadLocal.set(signMap);
}
@AfterReturning(pointcut = "pointcut()", returning = "returnValue")
public void doAfterReturn(JoinPoint joinPoint, Object returnValue) {
Map<String, String> signMap = threadLocal.get();
String methodName = currentMethod(joinPoint);
String apiName = METHOD_MAP.get(methodName);
if (apiName == null) {
log.error("请求 Url 不存在");
throw new BusinessException(ApiResultEnum.PARAM_VERIFICATION_FAIL, "请求 Url 不存在");
}
if (returnValue == null) {
log.error("{} 接口返回值为 null,请检查代码", apiName);
throw new BusinessException(ApiResultEnum.UNKNOWN_ERROR);
}
BaseVO baseVO = (BaseVO) returnValue;
String sign = generateReturnSign.initReturnSign(baseVO, signMap.get(MdcConstant.SIGN_KEY), null);
baseVO.setSign(sign);
// 正常响应参数日志打印
log.info(apiName + "响应:" + JSON.toJSONString(baseVO));
}
private String currentMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method resultMethod = null;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
resultMethod = method;
break;
}
}
return resultMethod == null ? "" : resultMethod.getName();
}
}
/**
* 请求参数打印
*
* @author Xinling Jing
* @date 2021-05-17 11:02
*/
@Component
@Slf4j
@Setter
public class RequestLogPrint extends ProcessingObject<BaseDTO> {
@Override
protected BaseDTO handleWork(BaseDTO input, String apiName) {
log(JSON.toJSONString(input), apiName);
return input;
}
private void log(String json, String apiName) {
log.info(apiName + "请求:" + json);
}
}
过滤器
package com.adups.inter.mgmt.filter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fh.inter.mgmt.dto.RegisterDTO;
import com.fh.inter.mgmt.service.GetVehicleInfoService;
import com.fh.standard.config.SystemCommonConfig;
import com.fh.standard.constant.MdcConstant;
import com.fh.standard.entity.VehicleInfoDO;
import com.fh.standard.enums.ApiEnum;
import com.fh.standard.util.AesUtils;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 报文解密过滤器
*
* @author fangh
* @date 2023-02-15 18:27
*/
@Order(0)
@WebFilter(urlPatterns = "/idiagnosis/*")
@RequiredArgsConstructor
public class DecreptFilter implements Filter {
private final SystemCommonConfig config;
private final GetVehicleInfoService getVehicleInfoService;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!config.isEncrypt()) {
chain.doFilter(servletRequest, response);
return;
}
// 获取请求uri
String uri = ((HttpServletRequest) servletRequest).getRequestURI();
// 截取注册映射值
String apiName = StrUtil.subAfter(uri, StrUtil.SLASH, true);
RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
//获取post的值
String bodyMessage = IOUtils.toString(requestWrapper.getBody(), "UTF-8");
String decodeBodyMessage;
if (ApiEnum.RDS_REGISTER.getMethodName().equals(apiName)) {
// 解密后的报文
decodeBodyMessage = config.isEncrypt() ? AesUtils.aesDecodeStr(bodyMessage, config.getKey()) : bodyMessage;
// 解析json
RegisterDTO registerDTO = JSONUtil.toBean(decodeBodyMessage, RegisterDTO.class);
// 获取车辆信息
VehicleInfoDO vehicleInfoDO = getVehicleInfoService.getVehicleInfoByVin(registerDTO.getVin());
registerDTO.setVehicle(vehicleInfoDO);
// 覆写request数据
requestWrapper.setBody(JSONUtil.toJsonStr(registerDTO).getBytes(StandardCharsets.UTF_8));
MDC.put(MdcConstant.REGISTER_BODY_MESSAGE, decodeBodyMessage);
} else {
// 截取到deviceId值
String deviceId = StrUtil.subAfter(uri, StrUtil.SLASH, true);
// 根据deviceId获取车辆信息
VehicleInfoDO vehicleInfoDO = getVehicleInfoService.getVehicleInfoByDeviceId(deviceId);
// 报文解密
decodeBodyMessage = config.isEncrypt() ? AesUtils.aesDecodeStr(bodyMessage, vehicleInfoDO.getSecret()) : bodyMessage;
// 车辆的secret
MDC.put(MdcConstant.ENCRYPT_KEY, vehicleInfoDO.getSecret());
// 解密后的报文
MDC.put(MdcConstant.BODY_MESSAGE, decodeBodyMessage);
// 车辆信息
MDC.put(MdcConstant.VEHICLE_INFO, JSONUtil.toJsonStr(vehicleInfoDO));
}
chain.doFilter(requestWrapper, responseWrapper);
// 获取原响应报文
byte[] content = responseWrapper.getContent();
// 解析response
String responseData = IOUtils.toString(content, "UTF-8");
// 加密响应报文
String result;
if (ApiEnum.RDS_REGISTER.getMethodName().equals(apiName)) {
result = AesUtils.aesEncryptStr(responseData, config.getKey());
} else {
// 非注册请求 加密key为车的密钥
result = AesUtils.aesEncryptStr(responseData, MDC.get(MdcConstant.ENCRYPT_KEY));
}
// 覆写response
content = result.getBytes();
// 注意 此处是servletResponse 不是responseWrapper,写responseWrapper的话 依旧响应不了
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(content);
outputStream.flush();
outputStream.close();
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* RequestWrapper
*
* @author fangh
* @date 2023-02-16 17:46
*/
public class RequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = this.toByteArray(request.getInputStream());
}
private byte[] toByteArray(ServletInputStream inputStream) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n;
while ((n = inputStream.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
@Override
public BufferedReader getReader() {
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();
}
};
}
public byte[] getBody() {
return body;
}
public void setBody(byte[] data) {
body = data;
}
}
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
/**
* ResponseWrapper
*
* @authorfangh
* @date 2023-02-16 17:46
*/
public class ResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream byteArrayOutputStream;
private final ServletOutputStream servletOutputStream;
private final PrintWriter writer;
public ResponseWrapper(HttpServletResponse response) {
super(response);
byteArrayOutputStream = new ByteArrayOutputStream();
servletOutputStream = new WapperedOutputStream(byteArrayOutputStream);
writer = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, StandardCharsets.UTF_8));
}
@Override
public ServletOutputStream getOutputStream() {
return servletOutputStream;
}
@Override
public PrintWriter getWriter() {
return writer;
}
@Override
public void flushBuffer() {
if (servletOutputStream != null) {
try {
servletOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null) {
writer.flush();
}
}
@Override
public void reset() {
byteArrayOutputStream.reset();
}
public String getResponseData(String charset) throws IOException {
flushBuffer();
return byteArrayOutputStream.toString(charset);
}
private static class WapperedOutputStream extends ServletOutputStream {
private final ByteArrayOutputStream bos;
public WapperedOutputStream(ByteArrayOutputStream stream) {
bos = stream;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) {
bos.write(b);
}
}
public byte[] getContent() {
flushBuffer();
return byteArrayOutputStream.toByteArray();
}
}