过滤器Filter,拦截器Interceptor,切面AOP的使用和区别,以及全局异常处理器的使用
过滤器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("对不起,发生错误,请联系管理员");
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?