过滤器Filter,拦截器Interceptor,切面AOP的使用和区别,以及全局异常处理器的使用

发布时间 2023-09-05 06:06:12作者: 凌碎瞳缘

 

过滤器Filter

  相比起Interceptor与AOP,Filter并不属于spring框架,而属于web环境。所以他的拦截范围会更加广,是三者中最早对数据进行拦截的。而在业务处理中,越早拦截数据对性能的拦截也会越小,所以在书写通用代码时,我们一般会优先考虑Filter。

@Slf4j
@WebFilter(urlPatterns = "/*")//拦截所有访问地址
public class LoginCheckFilter implements Filter {

    //重写doFilter方法
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        
        //将ServletRequest转为HttpServletRequest,以便进行解析令牌,获取链接等操作
        HttpServletRequest req=(HttpServletRequest)servletRequest;
        //将ServletRequest转为HttpServletResponse,因为该重写方法没有提供将数据返回给前端的途径,所以在发现问题时,想将数据传回前端,就要将数据手动写入
        HttpServletResponse resp=(HttpServletResponse)servletResponse;

//        获取http链接
        String url=req.getRequestURL().toString();

//        获取令牌
        String token=req.getHeader("token");

        try {
            JwtUtils.parseJwt(token);//该方法为自建的工具类,用来对JWT令牌进行解码,如果解码失败则会报错
        } catch (Exception e) {
//            e.printStackTrace();
            log.info("解析JWT令牌失败...");
            Result error=Result.error("NOT_LOGIN");//Result是自建的一个类,用来统一封装封装给前端的数据
            String notLogin=JSONObject.toJSONString(error);//手动转换数据为JSON格式。JSONObject.toJSONString是阿里云的提供的fastjson依赖内的方法
            resp.getWriter().write(notLogin);//将数据手动传给前端
            return;//如果失败,直接return,数据不会继续向下传入
        }

//        数据无误,放行
        filterChain.doFilter(servletRequest,servletResponse);
        
        
//        放行的代码完成后,又会回到这里,此时可以继续对代码进行补充
        log.info("补充说明!");
    }
}

 

 

 拦截器Interceptor

  如果在web环境中的话,感觉Interceptor的使用面比较小,对数据进行全面拦截可以用Filter,如果对拦截的数据需要进行接口,方法等的细分,可以用AOP切面编程。Interceptor需要在配置类被调用

@Configuration//设置该类为配置类
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //addPathPatterns()设置覆盖的路径,下列设置为所有路径,excludePathPatterns()用于排除路径
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}
@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
    
    @Override
    //重写该方法,会在请求处理前进行拦截,如果请求通过return true,如果请求不通过则return false
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        return true;
    }
    
    @Override
    //重写该方法,会在请求处理后进行访问,但会在DispatcherServlet进行试图渲染前被调用
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        
    }

    @Override
    //重写该方法,会在请求处理后,并且DispatcherServlet进行试图渲染完毕后被调用
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        
    }
}

 

切面AOP

  当我们需要对某些接口或方法进行代码加强,但又不想破坏原先代码时,就可以用AOP对接口或方法,有选择性的进行统一增强。

 

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 

  想用AOP,肯定需要让你写的增强性代码知晓自己增强的是哪些接口或方法,这时候就要写切入点表达式,而切入点有两种表达式,execution(),@annotation()。

 

  execution()

  execution()表达式适用于同时增强同一个目录下,或名称相同的接口或方法

  *代表一个任意属性,例如任意一个返回的参数,任意一种参数类型,任意一个方法名。

  ..代表多个任意属性,一般只存在于两个地方。一个是地址路径中,代表任意的目录层级下。一个是方法参数中,代表多个任意参数类型

  例如:

  execution(* com.cyk.service.DeptService.*(..))  代表com.cyk.service.DeptService接口中的方法

  execution(* com.cyk.*.*(..))  代表com.cyk下的包中的方法

  execution(* com.cyk..*.*(..))  代表com.cyk下的包以及所有子包中的方法

 

  @annotation()

  @annotation()适用于增强指定的接口或方法,需要配合自定义注解适用。之后只需在将要进行代码增强的接口或方法上,写上自定义注解即可,例如下方代码,之后接口或方法上想使用,即写@Mylog

@Pointcut("@annotation(com.cyk.aop.Mylog)")//括号里写上自定义的注解类地址

注解类

@Retention(RetentionPolicy.RUNTIME) //括号内指定该注解何时生效,现在是运行时生效
@Target(ElementType.METHOD) //指定当前注解可以运用在哪些地方,现在是所有方法生效
public @interface Mylog {//自定义注解
}

需要被增强的方法

@Mylog
@DeleteMapping ("/{id}")
public Result delete(@PathVariable Integer id){
    log.info("根据id删除部门:{}",id);
    deptService.delete(id);
    return Result.success();
}

 

接下来则是几种通知

环绕通知

@Slf4j
@Aspect//定义为切面类的注解
@Component
@Order(1)//设置切面类的响应顺序,默认是按照切面类的类名升序排序,设置后按照参数内的数字大小升序排序
public class LogAspect {

    @Autowired
    //在切面类中,方法参数里没有HttpServletRequest,但可以通过这种方式读入HttpServletRequest
    private HttpServletRequest request;

//    @Pointcut("")可以对表达式进行统一管理,就和一个工具类一样。
    @Pointcut("@annotation(com.cyk.aop.Mylog)")//该表达式需要一个自定义注解,括号里写上自定义的注解文档,在需要的方法上写上自定义注解即可生效
    private void pt(){}
    
    
    @Around("pt()")//其他通知想获取方法的相关信息,要在参数栏用ProceedingJoinPoint的父类JoinPoint
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

//        解析jwt令牌,获取令牌中存储的用户数据
        String jwt=request.getHeader("token");
        Claims claims= JwtUtils.parseJwt(jwt);
        Integer operateUser=(Integer)claims.get("id");
        
//        获取目标对象的类名
        String className = proceedingJoinPoint.getTarget().getClass().getName();
//        获取目标方法的方法名
        String methodName = proceedingJoinPoint.getSignature().getName();
//        获取目标方法调用时传入的参数
        Object[] args = proceedingJoinPoint.getArgs();
        String methodParams = Arrays.toString(args);

        Object result=proceedingJoinPoint.proceed();//调用原始程序运行,并获取返回值
        return result;
    }
    
    @Before("pt()")
    public void BeforeTime(){
        
    }
}

 前置通知

@Before("pt()")
//在目标方法运行前执行
public void BeforeGame(JoinPoint joinPoint){

}

后置通知(三种)

@After("pt()")
//在目标方法运行后执行,无论运行是否异常都会执行
public void AfterGame(JoinPoint joinPoint){

}

@AfterReturning("pt()")
//只有在目标方法正常运行完成后才会执行
public void AfterReturningGame(JoinPoint joinPoint){

}

@AfterThrowing("pt()")
//只有在目标方法运行出现异常时才会执行
public void AfterThrowingGame(JoinPoint joinPoint){

}

 

全局异常处理器

  因为这也是一种对代码进行统一管理的方式,所以我也放在这里记录了。

  全局异常处理器是用来统一捕获所有接口类@Controller(表现层)抛出的异常并进行处理的。

@RestControllerAdvice//申明该类为全局异常处理类
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)//括号写处理的写报错范围,默认只捕捉运行时异常
    public Result ex(Exception e){//Result为自建的工具类,用于统一格式发给前端
        e.printStackTrace();
        return Result.error("对不起,发生错误,请联系管理员");
    }
}