过滤器与拦截器

1. 过滤器跟拦截器的区别

(1)过滤器(Filter):用户请求进入Controller层之前进行过滤。

(2)拦截器(Interceptor):用户请求进入Controller层之后,进入方法之前拦截。

2. spring中的拦截器

在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、预先设置数据以及统计方法的执行效率等等。

今天就来详细的谈一下spring中的拦截器。spring中拦截器主要分两种,一个是HandlerInterceptor,一个是MethodInterceptor。


2.1 HandlerInterceptor拦截器

HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。

实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。

这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承他之后只需要重写必要的方法。

注册:
(1)通过spring.xml配置文件注册
拦截器类需要实现HandlerInterceptor接口,并且需要在servlet.xml中配置<mvc:interceptor>
(2)通过配置类进行注册
1.创建我们自己的拦截器类并实现 HandlerInterceptor 接口
2.创建一个Java类添加@Configuration并继承WebMvcConfigurerAdapter或WebMvcConfigurer,并重写 addInterceptors 方法。
3.实例化我们自定义的拦截器,然后将对像手动添加到拦截器链中(在addInterceptors方法中添加)

public class TestInterceptor implements HandlerInterceptor {
    private static int preFlag = 0, postFlag = 0, afterFlag = 0;

    // 在Controller得到请求的时候进行拦截,如果不放行请求则Controller得不到请求
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器:拦截器拦截到了请求,请求的URI为:" + request.getRequestURI());
        System.out.println("拦截器:拦截次数=" + (++preFlag));
        return true; // 释放请求
        // 如果拦截器不释放请求,则请求会一直处于被拦截状态
    }
    // 在Controller处理完请求后,视图渲染之前,拦截器进行验证
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器:拦截器开始解析请求" + modelAndView.getViewName());
        System.out.println("拦截器:解析请求次数=" + (++postFlag));
    }
    // 视图渲染完成之后调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器:请求完成");
        System.out.println("拦截器:渲染次数=" + (++afterFlag));
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");//可以编写多个路径
        // registry.addInterceptor(new MyInterceptor()).addPathPatterns("/login","/login/")
        //也可以添加忽略路径
        //registry.addInterceptor(new MyInterceptor()).addPathPatterns("/login","/login/").excludePathPatterns("/logout")
    }
}


2.2 MethodInterceptor拦截器

继承AOP类
MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是controller中的方法。实现MethodInterceptor拦截器大致也分为两种,
一种是实现MethodInterceptor接口,另一种利用AspectJ的注解或配置。
在invoke()方法中写拦截规则

注册:
(1)通过spring.xml配置文件注册
拦截器类需要实现MethodInterceptor接口,并且需要在.xml中配置<aop:config>,类似于AOP
(2)通过aop的@Aspect注解进行注册
类添加@Aspect注解与@Pointcut()切入点和方法上添加@Before()、@Around()等即可

经测试发现,interceptor先于AOP执行。


2.3 谈一谈区别

上面的两种拦截器都能起到拦截的效果,但是他们拦截的目标不一样,实现的机制不同,所以有的时候适用不同的场景。

HandlerInterceptoer拦截的是请求地址,所以针对请求地址做一些验证、预处理等操作比较合适。
当你需要统计请求的响应时间时MethodInterceptor将不太容易做到,因为它可能跨越很多方法或者只涉及到已经定义好的方法中一部分代码。
MethodInterceptor利用的是AOP的实现机制,在本文中只说明了使用方式,关于原理和机制方面介绍的比较少,
因为要说清楚这些需要讲出AOP的相当一部分内容。在对一些普通的方法上的拦截HandlerInterceptoer就无能为力了,这时候只能利用AOP的MethodInterceptor。

3.过滤器

注册方式
(1)@WebFilter(urlpatters="/*")
该类需要实现Filter接口,重写doFilter()方法

@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("-----doFilter-----");
        chain.doFilter(request, response);
    }
}

在 @WebFilter 注解中可以配置过滤器的拦截规则。这个注解要生效,还需要我们在项目启动类上配置 @ServletComponentScan 注解
@ServletComponentScan 注解虽然名字带了 Servlet,但是实际上它不仅仅可以扫描项目中的 Servlet 容器,也可以扫描 Filter 和 Listener。

@SpringBootApplication
@ServletComponentScan
public class FilterdemoApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(FilterdemoApplication.class, args);
    }
 
}

这是我们在 Spring Boot 中使用过滤器的第一种方式,在实际项目中,这种方式使用较少,因为这种方式有一个很大的弊端就是无法指定 Filter 的优先级,
如果存在多个 Filter 时,无法通过 Order 指定优先级。

(2)@Bean
该类需要添加@Component将接口扫描到容器,并实现Filter接口

@Component
@Order(-1)
public class MyFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("-----doFilter-----");
        chain.doFilter(request, response);
    }
}

第二种方式就是将过滤器配置成 Bean,注册到 Spring 容器中去。
这种方法不再需要 @ServletComponentScan 注解,只要一个 Bean 即可
而且这种方式还有一个优势,就是如果存在多个 Filter,可以通过 @Order(-1) 注解指定多个 Filter 的优先级

虽然解决了优先级问题,但是这种方式没有办法设置 Filter 的拦截规则!
是的,直接定义 Bean 的话,默认的拦截规则就是 /* 即拦截所有请求,开发者无法进行自定义配置。

(3)FilterRegistrationBean

第三种方案还是将 Filter 封装成一个 Bean,但这个 Bean 是 FilterRegistrationBean,
通过 FilterRegistrationBean 我们既可以配置 Filter 的优先级,也可以配置 Filter 的拦截规则。

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<Filter> XXXPrecautionFilterRegistrationBean() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.addUrlPatterns(Arrays.asList("/hello/*"));// url拦截规则
        filterRegistrationBean.setFilter(new XXXFilter());
        filterRegistrationBean.setOrder(-2);
        return filterRegistrationBean;
    }
}

一般在项目中,我们都是使用 FilterRegistrationBean 来配置过滤器

RegistrationBean 有众多的实现类,我们之前使用的 FilterRegistrationBean 只是其中之一:

实现类的作用一目了然:

  1. ServletListenerRegistrationBean 用来注册监听器。

  2. ServletRegistrationBean 用来注册 Servlet。

  3. DispatcherServletRegistrationBean 用来注册 DispatcherServlet。

  4. FilterRegistrationBean 用来注册过滤器。

  5. DelegatingFilterProxyRegistrationBean 则用来注册 DelegatingFilterProxy,DelegatingFilterProxy 在 Spring Security、Spring Session、Shiro等整合时非常有用。

4.AOP切面

依赖

    <!--spring AOP的包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>

注解实现:

@Aspect 就是把一个类定义为切面供容器读取。(类添加@Aspect注解)

@before: 前置通知,在方法执行之前执行。
@After:后置通知,在方法执行后执行。
@AfterReturning: 返回通知,在方法返回结果之后执行。
@AfterThrowing:异常通知,在方法抛出异常之后执行。
@Around:环绕通知,围绕着方法执行。

1.简单实现

@Component
@Aspect
public class TestAspect {
    @Pointcut("execution( * com.atguigu.gulimall.product.controller.CouponController.*(..))")
    private void log(){
    }
    @Before("log()")
    public void TestBefore(JoinPoint joinPoint){
        System.out.println("在调用方法的时候都会调用这个切面");
    }
}

2.结合自定义注解实现
#1

@Documented
@Target({ElementType.METHOD}) // 在方法上加
@Retention(RetentionPolicy.RUNTIME) // 运行时
public @interface AnnotationTest {//自定义注解
}

 

#2

@RequestMapping("/test1")
@AnnotationTest
public String list(@RequestBody Map<String, Object> params){
    return "ok";
}

 

#3

@Pointcut("@annotation(com.atguigu.gulimall.product.annoation.AnnotationTest)")
private void annotation(){

}
@After("annotation()")
public void testAnnotation(){
    System.out.println("执行注解注释的方法后执行此方法");
}

spring配置文件实现:

自定义切入类

public class DIYPointCut {
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}

spring配置

<bean id="diy" class="com.bai.spring.diy.DIYPointCut"/>

<!--aop的配置-->
<aop:config>
    <!--切面 ref=增强类对象名-->
    <aop:aspect ref="diy">
        <!--切入点可以有多个-->
        <aop:pointcut id="diyPointCut"
                      expression="execution(* com.bai.spring.service.UserServiceImpl.*(..))"/>
        <aop:before method="before" pointcut-ref="diyPointCut"/>
        <aop:after method="after" pointcut-ref="diyPointCut"/>
    </aop:aspect>
</aop:config>

 

posted @ 2023-03-23 15:18  少年阿丁  阅读(71)  评论(0编辑  收藏  举报