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;
}在定义一个拦截器,实现自定义函数接口MethodInterceptor的invoke方法:
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注解则默认过滤所有路径。
发表回复