Spring Boot基础功能:拦截器与过滤器

拦截器(Inteceptor)与过滤器(Filter)都用于在请求处理过程中执行特定操作,他们的实现方式、作用范围和使用场景上有一定的区别。

1 拦截器

拦截器是一种动态拦截调用方法的机制,它可以在Spring Mvc中动态拦截控制器方法的执行,是基于Spring MVC框架的一部分。

实现方式: 拦截器通过实现HandlerInterceptor接口或继承HandlerInterceptorAdapter类来创建自定义拦截器。

作用范围: 拦截器作用与具体的控制器方法,用于拦截和处理请求以及对响应进行处理。拦截器可以访问Spring Mvc的上下文,包括处理器方法的参数、请求和响应对象,以及Spring的其他功能(如依赖注入)。

适用范围: 拦截器可以进行权限验证、日志记录、参数处理、异常处理等操作。拦截器适用于对请求进行细粒度的拦截和处理,如验证用户身份、记录请求日志等。

1.1 拦截器的执行顺序

● preHandle
    ■ return true
        □ controller
        □ postHandle
        □ afterCompltion
    ■ return false
        □ 拦截,流程结束

1.2 创建拦截器

创建拦截器LoginInterceptor.java:

/**
 * 登录检查
 * 1.配置到拦截器要拦截哪些请求
 * 2.把这些配置放在容器中
 *
 * 实现HandlerInterceptor接口
 */
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行之前
     * 登录检查写在这里,如果没有登录,就不执行目标方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//      获取进过拦截器的路径
        String requestURI = request.getRequestURI();

        //      登录检查逻辑
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser !=null){
//          放行
            return true;
        }
//      拦截   就是未登录,自动跳转到登录页面,然后写拦截住的逻辑
        return false;
    }

    /**
     * 目标方法执行完成以后
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 页面渲染以后
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}    

配置到容器里``:

@Configuration
//定制SpringMVC的一些功能都使用WebMvcConfigurer
public class AdminWebConfig implements WebMvcConfigurer {

    /**
     * 配置拦截器
     * @param registry 相当于拦截器的注册中心
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//       下面这句代码相当于添加一个拦截器   添加的拦截器就是我们刚刚创建的
         registry.addInterceptor(new LoginInterceptor())
//       addPathPatterns()配置我们要拦截哪些路径 addPathPatterns("/**")表示拦截所有请求,包括我们的静态资源
                 .addPathPatterns()
//       excludePathPatterns()表示我们要放行哪些(表示不用经过拦截器)
//       excludePathPatterns("/","/login")表示放行“/”与“/login”请求
//       如果有静态资源的时候可以在这个地方放行
                 .excludePathPatterns("/","/login");
    }
}    

1.3 拦截器的请求参数

1.3.1 HttpServletRequest

HttpServletRequest源于Servlet, 可以获取请求数据

获取请求头Content-Type的示例

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String contenType = request.getHeader("Content-Type");
    System.out.println("preHandle..."+contenType);
    // 通过
    return true;
}
1.3.2 HttpServletResponse

HttpServletResponse源于ServLet, 可用于设置响应数据

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    response.setCharacterEncoding("UTF-8");
    // 通过
    return true;
}
1.3.4 Object

Object参数是被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行再包装。

  @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String contenType = request.getHeader("Content-Type");
//        System.out.println(handler);
        HandlerMethod hm = (HandlerMethod)handler;
//        通过hm.getMethod()就可以拿到原始执行的对象,拿到这个对象就可以进行反射
         hm.getMethod();
        System.out.println("preHandle..."+contenType);
//      放行
        return true;
    }
1.3.5 ModelAndView

封装了SpringMVC跳转的相关数据。

1.3.6 Exception ex

通过ex可以拿到原始程序执行过程中出现的异常。

假设controller层抛了异常,在这里是可以拿到异常对象的,但是我们有异常处理机制,所以这里就没有那么大的需求了

如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理

1.4 拦截器的执行顺序

拦截器采用管道模式,preHandle与配置顺序相同,postHandle于配置顺序相反,afterCompletion与配置顺序相反。

1.5 常用拦截器操作

1.5.1 配置静态资源拦截器
/**
* 在MyInterceptorConfig中重写addResourceHandlers方法,重新指定静态资源
* 推荐在前后端分离时使用,后台不需要访问静态资源
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   registry.addResourceHandler("/**").addResourceLocations(
           "classpath:/static/");
   super.addResourceHandlers(registry);
}

/**
 * 将MyInterceptorConfig类由继承WebMvcConfigurationSupport改为实现WebMvcConfigurer
 * 推荐在非前后端分离时使用,后台需要访问静态资源
 *
 */
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); // 拦截所有内容:/** 拦截部分内容:/admin/**
    }
}
1.5.2 配置转换页面

application.yml:

spring:
  thymeleaf:
    prefix: classpath:/templates/ #视图前缀
    suffix: .html                 #视图后缀
    encoding: UTF-8               #编码
    cache: false                  #缓存(默认开启,开发时建议关闭)

配置转换页面:

registry.addViewController("/index").setViewName("index");
1.5.3 匹配路径
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
1.5.4 例外路径
String[] excepts = new String[]{"/404.html","/posts/**","/webjars/**"};
registry.addInterceptor(new MyInterceptor()).excludePathPatterns("/**");
1.5.5 取消拦截操作

第一步:自定义注解用于不需要拦截的方法

/**
 * 自定义注解用来指定某个方法不用拦截
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UnInterception {
}

第二步:修改配置,判断例外的注解

/**
 * 在请求匹配controller之前执行
 * @param request
 * @param response
 * @param handler
 * @return
 * @throws Exception
 */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    UnInterception unInterception = method.getAnnotation(UnInterception.class);
    if(null != unInterception) {
        logger.info("不需要拦截,可以执行");
        return true;
    }

    // 返回true才会执行方法,返回false不会执行
    return false;
}

第三步:在不需要拦截的控制器方法上加上注解

@Controller
@RequestMapping("/interceptor")
public class InterceptorController {

    @UnInterception
    @RequestMapping("/test")
    public String test() {
        return "hello";
    }
}

1.6 MethodInterceptor

MethodInterceptor是Aop项目中的拦截器,拦截目标是方法,不局限于Controller类中的方法。

1.6.1 定义拦截器

先自定义一个函数接口:

package org.aopalliance.intercept;

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation var1) throws Throwable;
}

在定义一个拦截器,实现自定义函数接口MethodInterceptorinvoke方法:

package com.wang.chao.micro.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("MyMethodInterceptor  "+methodInvocation.getMethod().getName());
        return methodInvocation.proceed();
    }
}
1.6.2 使用AspectJExpressionPointcut构造切点并注册
package com.wang.chao.micro.interceptor.config;

import com.wang.chao.micro.interceptor.MyMethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InterceptorConfig {
    public static final String traceExecution = "execution(* com.wang.chao.micro..*.*(..))";

    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor(){
        MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(traceExecution);

        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(methodInterceptor);
        return advisor;
    }
}
1.6.3 使用JdkRegexpMethodPointcut构造切点
package com.wang.chao.micro.interceptor.config;

import com.wang.chao.micro.interceptor.MyMethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InterceptorConfig {
    public static final String traceExecution = "execution(* com.wang.chao.micro..*.*(..))";

    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor() {
        MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        //case 1
        // AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // pointcut.setExpression(traceExecution);

        //case 2
        JdkRegexpMethodPointcut pointcut2 = new JdkRegexpMethodPointcut();
        pointcut2.setPattern("com.wang.chao.micro.*");

        advisor.setPointcut(pointcut2);
        advisor.setAdvice(methodInterceptor);
        return advisor;
    }
1.6.4 注解自定义注解AspectJExpressionPointcut
package com.wang.chao.micro.interceptor.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterceptorAnnotation {
}
package com.wang.chao.micro.interceptor.config;

import com.wang.chao.micro.interceptor.MyMethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InterceptorConfig {
    public static final String traceAnnotationExecution = "@annotation(com.wang.chao.micro.interceptor.annotation.InterceptorAnnotation)";
    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor() {
        MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        //case 3
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(traceAnnotationExecution);

        advisor.setPointcut(pointcut);
        advisor.setAdvice(methodInterceptor);
        return advisor;
    }
1.6.5 自定义注解 AnnotationMatchingPointcut
package com.wang.chao.micro.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void saveUser(String name, String sex) {
        System.out.println("name = " + name + ", sex = " + sex+" id =");
        sayHello(name);
    }

    public void sayHello(String name){
        System.out.println("Hello name = " + name+" Welcome in Spring Interceptor world");
    }
}
1.6.6 测试

测试类

package com.wang.chao.micro.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void saveUser(String name, String sex) {
        System.out.println("name = " + name + ", sex = " + sex+" id =");
        sayHello(name);
    }

    public void sayHello(String name){
        System.out.println("Hello name = " + name+" Welcome in Spring Interceptor world");
    }
}

请求调用测试

package com.wang.chao.micro.controller;

import com.wang.chao.micro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping(value="/" ,method = RequestMethod.GET)
    public void saveUser(String name,String sex){
       userService.saveUser(name,sex);

    }
}

启动调用测试

package com.wang.chao.micro;

import com.wang.chao.micro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Application {
    @Autowired
    private ApplicationContext applicationContext;
    public static void main(String[] args) {
        ApplicationContext applicationContext =  SpringApplication.run(Application.class, args);

        UserService userService=(UserService) applicationContext.getBean("userService");
        userService.saveUser("wagnchao","nan");
        userService.sayHello("wangchaochao");
    }

}

2 过滤器Filter

过滤器可以过滤所有的请求,对请求前后进行处理,可以获取并修改请求和响应中的信息,所以可以用来对一些无效的路径进行过滤或者进行URL级别的权限控制,同时可以对请求的信息进行敏感词过滤以防止SQL注入,但是无法调用IOC容器。

过滤器由Servlet提供,基于函数回调实现链式对网络请求与响应的修改,其可以对WEB服务器管理的几乎所有所有资源进行拦截修改(jsp, image, html, css ...)。

Filter的生命周期:

  • init() 初始化Filter实例,Filter生命周期与Servlet相同,也就是当web容器(tomcat)启动时,调用init()方法初始化实例,Filter只会初始化一次,需要初始化参数的时候,可以放到init()中;
  • doFilter() 业务处理,拦截执行的请求,对请求和响应进行处理,一般需要处理的业务操作都在这个方法中实现;
  • destory() 销毁实例,关闭容器时调用Filter销毁实例。

2.1 实现Filter接口实现过滤器

在启动类添加@ServletComponentScan注解, 让Spring Boot可以发现。

@SpringBootApplication
@ServletComponentScan
public class NosocomiumApplication {

    public static void main(String[] args) {
        SpringApplication.run(NosocomiumApplication.class, args);
    }

}

通过 @WebFilter 注解,将类声明为 Bean 过滤器类。此时可以指定要拦截的url , 但是不能指定过滤器执行顺序

@Slf4j
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {

    @Resource
    private RedisTemplate redisTemplate;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //获取访问 ip 地址
        String ipAddr = getIpAddr(request);
        // 存入缓存10s不允许访问
        String key = new StringBuilder().append("bizKey").append(ipAddr).toString();

        if (redisTemplate.hasKey(key)) {
            // 访问次数自增1
            redisTemplate.opsForValue().increment(key, 1);
            log.warn("访问过快,存在强刷行为!key={}", key);
        } else {
            // 第一次访问
            redisTemplate.opsForValue().set(key, 1, 10,
                    TimeUnit.SECONDS);
        }
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception e) {
            log.warn("认证失败,e:{},url:{},parameters:{}", e,request.getRequestURL(),request.getParameterMap());
            servletResponse.setContentType("application/json");
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.getWriter().write(JSONUtil.toJsonStr(Result.fail("业务执行报错~")));
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

    public static String getIpAddr(HttpServletRequest request){
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("X-Forwarded-For");
            if (ipAddress != null && ipAddress.length() != 0 && !"unknown".equalsIgnoreCase(ipAddress)) {
                // 多次反向代理后会有多个ip值,第一个ip才是真实ip
                if (ipAddress.indexOf(",") != -1) {
                    ipAddress = ipAddress.split(",")[0];
                }
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
            }
        }catch (Exception e) {

        }
        return ipAddress;
    }
}

2.2 使用@Component注解实现

使用@Component注解后,可以使用@Order注解来制定过滤器的顺序,值越小越优先执行。

@Component
@Order(1)
public class MyFilter1 implements Filter {
    // ...
}
@Component
@Order(2)
public class MyFilter2 implements Filter {
    // ...
}
  • 不制定order顺序默认按过滤器名称的字母顺序执行;
  • 不使用@WebFilter注解则默认过滤所有路径。

发表回复

您的电子邮箱地址不会被公开。