谨记: 过滤器 > 拦截器 > AOP
Spring中自定义过滤器(Filter),当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的 doFilter 方法(可能会有多个过滤器),然后调用filterChain.doFilter(request,response)方法,才调用真实的业务逻辑,至此过滤器任务完成。
拦截器(HandlerInterceptor)有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。Spring中拦截器有三个方法:preHandle,postHandle,afterCompletion。当请求匹配到对应的拦截器时,先执行 preHandle方法,返回值为boolean类型,true代表继续执行,false代表请求终止。若为true时,执行完preHandle方法,则执行真正的业务请求方法。postHandle在处理完业务请求后,返回时生效。而afterCompletion是整个请求完成才执行。
面向切面拦截的是类的元数据(包、类、方法名、参数等)
相对于过滤器、拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑
从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。
ps:springboot版本 1.5.14.RELEASE ,由于属于springmvc内容,版本影响应该不大。注释内容都可以试试,我自己留个印象,我就不删了
两种实现方式:一、 注解@WebFilter 二、 配置文件
自定义filter,可以用@WebFilter进行注册(启动类记得添加注解 @ServletComponentScan 进行扫描),亲测本版本使用@order注解未能对过滤器进行排序,其他版本请自行测试。
/**
* 自定义 过滤器 启动类添加 @ServletComponentScan//扫描自定义过滤器
* 过滤器可以改变用户请求的资源 例如修改用户请求路径
* 过滤器不能直接处理用户请求,返回数据
*/
@Slf4j
//@WebFilter(urlPatterns = "/*", filterName = "myFilter") 注解方式不支持排序
public class myFilter implements Filter {
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig=filterConfig;
//可以这里设置初始化参数 也可以在下面第二种方式:配置文件中配置
//filterConfig.getServletContext().setInitParameter("encoding","utf-8");
//
System.out.println("初始化参数,自定义过滤器加载----------------------------------------------------");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
log.info("进入自定义过滤器1--------------------");
//System.out.println(this.filterConfig.getInitParameter("test0"));
HttpServletResponse httpServletResponse= (HttpServletResponse)response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//System.out.println(this.filterConfig.getInitParameter("test0"));
//统一设置编码 utf-8(encoding 自定义初始化参数)
//if(StringUtils.isNotBlank(this.filterConfig.getInitParameter("encoding"))){
//httpServletRequest.setCharacterEncoding(this.filterConfig.getInitParameter("encoding"));
// }
// //避免重定向路径 死循环
// if(httpServletRequest.getRequestURI().contains("/excel/index")){
// filterChain.doFilter(request,response);
// return;
// }
//重定向(浏览器路径发生改变,重定向到xxx路径)
//httpServletResponse.sendRedirect("/excel/index");
//转发(浏览器路径不会发生改变)
//request.getRequestDispatcher("/excel/index").forward(request,response);
filterChain.doFilter(request,response);
// httpServletRequest.setCharacterEncoding("utf-8");
log.info("离开自定义过滤器1--------------------");
}
@Override
public void destroy() {
System.out.println("自定义过滤器删除----------------------------------------------------");}
}
方式二、采用配置文件,进行注册,可以实现多过滤器执行排序,order越小 优先级越高
/**
* 自定义 请求过滤器配置 order越小优先级约高 同理 order越大优先级约低
*/
@Configuration
public class MyFilterConfig {
//过滤器一
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new myFilter());
//自定义一些参数
registrationBean.addInitParameter("test0","true");
registrationBean.addInitParameter("test1", "test");
registrationBean.addInitParameter("encoding","utf-8");
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(100); // 设置排序
//设置监听级别 FORWARD(转发),INCLUDE,REQUEST(请求),ASYNC(异步),ERROR
registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
return registrationBean;
}
//过滤器二
@Bean
public FilterRegistrationBean mySecondFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new mySecondFilter());
registrationBean.addInitParameter("test2","true");
registrationBean.addInitParameter("test3", "test");
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(10); // 设置排序
registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
return registrationBean;
}
}
过滤器Filter依赖于Servlet容器,过滤范围大。
实现拦截器有两个接口,继承 HandlerInterceptor 或者继承 WebRequestInterceptor都可以。本章节只实现HandlerInterceptor。原因是 HandlerInterceptor更加强大。
**
*自定义 拦截器 也可以集成 WebRequestInterceptor作为拦截器
*/
public class myHandlerInterceptor implements HandlerInterceptor
{
/**
* * 返回值 false 请求终止
* * 返回值 true 请求继续执行
* Object o 表示被拦截的请求目标对象 即controller对象
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("执行了拦截器 --------------preHandle方法");
//return false;
return true;
}
/**
* 请求执行结束,返回时候,执行该方法
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("执行了拦截器 --------------postHandle");
//可以通过 modelAndView 修改视图路径
}
/**
* 请求返回后,最后执行的方法
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param e
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("执行了拦截器 --------------afterCompletion");
}
}
注册自定义 拦截器myHandlerInterceptor 到容器 ,注意WebMvcConfigurer 有许多实现接口,但是为了美观,删除了,用到时候,引入一下就可以了,其他接口方法的用途请自行百度学习。
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 拦截器注册
* @param interceptorRegistry
*/
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
//多拦截器,就继续添加interceptorRegistry.addInterceptor 添加的顺序,就是拦截器的顺序
interceptorRegistry.addInterceptor(new myHandlerInterceptor()).addPathPatterns("/test/*").excludePathPatterns("/test/test2");
interceptorRegistry.addInterceptor(new myHandlerInterceptor2()).addPathPatterns("/test/*").excludePathPatterns("/test/test2");
}
}
AOP中 @Before @After @AfterThrowing@AfterReturning的执行顺序
基于 自定义注解作为切点 实现对日志的记录。
/**
* 操作注解
* @author 曾 自定义注解 用于操作日志
*/
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface OperLog {
String operModul() default ""; // 操作模块
String operType() default ""; // 操作类型
String operDesc() default ""; // 操作说明
}
AOP常用的通知注解有 @before @AfterReturning @ Around 本代码最终使用@ Around方法,其中@before @AfterReturning都已经实现,部分注释了
/**
* 操作日志
*/
@Aspect //切面注解
@Component
@Slf4j
public class OpenLoggerConfig {
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(com.cloud.user.config.OperLog)")
public void operLogPointcut(){
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作 可以改成扫描的包路径
*/
@Pointcut("execution(* com.cloud.user.myUser.*.controller..*.*(..))")
public void operExceptionLogPointcut(){
}
/**
* 执行切点方法之前,执行该方法
* @param joinPoint
*/
//@Before(value = "operLogPointcut()||operExceptionLogPointcut()")
public void saveBeforeOperLog(JoinPoint joinPoint){
log.info("我进来了,我即将要操作!!!------Before Before Before---------------");
}
/**
* 环绕通知,无论是切点方法 开始还是结束,都执行该方法 @Around 需要提供返回值 (小坑)
* @param joinPoint
*/
@Around(value = "operLogPointcut()||operExceptionLogPointcut()")
public Object saveAroundOperLog(ProceedingJoinPoint joinPoint) {
Object proceed=null;
log.info("我进来了,我即将要操作!!!--------Around Around Around-------------");
long startTime = System.currentTimeMillis();
System.err.println("startTime = " + startTime);
try {
proceed = joinPoint.proceed();
}catch (Throwable e){
//异常处理
this.saveExceptionOperLog(joinPoint,e);
long endTime = System.currentTimeMillis();
System.err.println("endTime = " + endTime);
long alltime=endTime-startTime;
log.info("我执行完了,我即将要离开!!!--------Around Around Around----------本次花费时间为:"+alltime+"毫秒");
return proceed;
}
//正常操作处理
this.saveOperLog(joinPoint,proceed);
long endTime = System.currentTimeMillis();
System.err.println("endTime = " + endTime);
long alltime=endTime-startTime;
log.info("我执行完了,我即将要离开!!!--------Around Around Around----------本次花费时间为:"+alltime+"毫秒");
return proceed;
}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
* @param keys 返回结果
*/
//@AfterReturning(pointcut = "operLogPointcut()",returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys){
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
OperLog opLog = method.getAnnotation(OperLog.class);
String operModul = "";
String operType = "";
String operDesc = "";
if (opLog != null) {
operModul = opLog.operModul();
operType = opLog.operType();
operDesc = opLog.operDesc();
}
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
// 请求的参数
Map<String, String> returnMap = converMap(request.getParameterMap());
log.info("请求的路径为:"+request.getRequestURL()+"--------------");
log.info("请求的类方法为:"+methodName+"具体描述:"+operModul+operType+operDesc+"--------------");
log.info("请求的参数为:"+returnMap.toString()+"--------------");
if(keys==null){
log.info("请求方法返回结果为:"+null+"--------------");
return;
}
log.info("请求方法返回结果为:"+keys.toString()+"--------------");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 异常处理日志保存
* @param joinPoint
* @param error
*/
//@AfterThrowing(pointcut = "operLogPointcut()",throwing = "error")
public void saveExceptionOperLog(JoinPoint joinPoint, Throwable error){
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
OperLog opLog = method.getAnnotation(OperLog.class);
String operModul = "";
String operType = "";
String operDesc = "";
if (opLog != null) {
operModul = opLog.operModul();
operType = opLog.operType();
operDesc = opLog.operDesc();
}
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
// 请求的参数
Map<String, String> returnMap = converMap(request.getParameterMap());
log.info("请求的路径为:"+request.getRequestURL()+"--------------");
log.info("请求的类方法为:"+methodName+"具体描述:"+operModul+operType+operDesc+"--------------");
log.info("请求的参数为:"+returnMap.toString()+"--------------");
log.info("异常名称:"+error.getClass().getName()+"--------------");
log.info("异常信息:"+stackTraceToString(error.getClass().getName(), error.getMessage(), error.getStackTrace()));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 转换request 请求参数
*
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<String, String>();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
/**
* 转换异常信息为字符串
*
* @param exceptionName 异常名称
* @param exceptionMessage 异常信息
* @param elements 堆栈信息
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
// StringBuffer strbuff = new StringBuffer();
// for (StackTraceElement stet : elements) {
// strbuff.append(stet + "\n");
// }
//String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
String message = exceptionName + ":" + exceptionMessage ;
return message;
}
}
1、过滤器依赖于Servlet容器 ,拦截器不依赖与servlet容器,是SpringMVC自带的。
2、拦截器是基于java的反射机制的,而过滤器是基于函数回调
3、优先级:过滤器 > 拦截器 > AOP ,同时 粒度(粗->细)过滤器- > 拦截器 -> AOP
4、过滤器可以改变用户请求的资源 例如修改用户请求路径 ,不能直接处理用户请求,返回数据。