最近面试有遇到拦截方式的场景,结合网上xdm的代码整理了下,分为以下三种:
 java原生过滤器Filter、springMVC拦截器、aop切面
目录:
- 一、java原生过滤器Filter
- 二、springMVC拦截器
- 三、aop切面
 
一、java原生过滤器Filter
package com.zhangximing.springbootinterceptor.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;
/**
 * 自定义Filter
 * 对请求的header 过滤token
 *
 * 过滤器Filter可以拿到原始的HTTP请求和响应的信息,
 *     但是拿不到你真正处理请求方法的信息,也就是方法的信息
 *
 * @Component 注解让拦截器注入Bean,从而让拦截器生效
 * @WebFilter 配置拦截规则
 *
 * 拦截顺序:filter—>Interceptor-->ControllerAdvice-->@Aspect -->Controller
 *
 */
@Slf4j
@Component
@WebFilter(urlPatterns = {"/**"},filterName = "authFilter")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("TokenFilter init {}",filterConfig.getFilterName());
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String param = request.getParameter("param");
        response.setContentType("text/html;charset=UTF-8");
        //获取请求头token
        String token = "";
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
        while(headerNames.hasMoreElements()) {//判断是否还有下一个元素
            String nextElement = headerNames.nextElement();//获取headerNames集合中的请求头
            if ("token".equals(nextElement)){
                token = httpServletRequest.getHeader(nextElement);
                log.info("请求头key[" + nextElement + "]:" + token);
            }
        }
        log.info("doFilter-我拦截到了请求:"+ param);
        if (null != param && "pass".equals(param)){
            //验证token
            if ("7758258xx".equals(token)){
                chain.doFilter(request,response);//到下一个链
            }else{
                response.getWriter().write("doFilter-请求头token不通过");
            }
        }else{
            log.info("doFilter-参数param不符合条件");
            response.getWriter().write("doFilter-参数param不通过");
        }
    }
    @Override
    public void destroy() {
        log.info("destroy");
    }
}
简单测试直接用的postman,参数是一个param和一个请求头token:
 
二、springMVC拦截器
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
 * 自定义拦截器
 * 自定义拦截器后,需要配置进Spring
 *
 * 拦截器Interceptor可以拿到原始的HTTP请求和响应的信息,
 *    也可以拿到你真正处理请求方法的信息,但是拿不到传进参数的那个值。
 *
 *拦截顺序:filter—>Interceptor-->ControllerAdvice-->@Aspect -->Controller
 */
@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {
    /**
     * 在访问Controller某个方法之前这个方法会被调用。
     * @param request
     * @param response
     * @param handler
     * @return false则表示不执行postHandle方法,true 表示执行postHandle方法
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("Interceptor preHandle {}","");
        String token = request.getHeader("token");
        log.info("Interceptor preHandle token :{}",token);
        log.info("Interceptor preHandle uri {}",request.getRequestURL().toString());
        response.setContentType("text/html;charset=UTF-8");
        //spring boot 2.0对静态资源也进行了拦截,当拦截器拦截到请求之后,
        // 但controller里并没有对应的请求时,该请求会被当成是对静态资源的请求。
        // 此时的handler就是 ResourceHttpRequestHandler,就会抛出上述错误。
        if (handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            log.info("Token Interceptor preHandle getMethod {}",method.getName());
        }else if(handler instanceof ResourceHttpRequestHandler){//静态资源
            ResourceHttpRequestHandler resourceHttpRequestHandler = (ResourceHttpRequestHandler) handler;
            log.info("Token Interceptor preHandle getMethod {}",resourceHttpRequestHandler.getMediaTypes());
        }
        if (!"7758258xx".equals(token)){
            response.getWriter().write("doInterceptor-请求头token不通过");
            return false;
        }
        //false则表示不执行postHandle方法,不执行下一步chain链,直接返回response
        return true;
    }
    /**
     * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
     * preHandle方法处理之后这个方法会被调用,如果控制器Controller出现了异常,则不会执行此方法
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("Interceptor postHandle");
    }
    /**
     * 不管有没有异常,这个afterCompletion都会被调用
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("Interceptor afterCompletion");
    }
这里注意下,需要将拦截器配置进spring
import com.zhangximing.springbootinterceptor.interceptor.MyInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 *  MyInterceptor 自定义拦截器后,需要配置进Spring
 * 也可以mapping,跨域设置
 */
@Slf4j
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
    @Autowired
    MyInterceptor myInterceptor;
    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.info("addInterceptors tokenInterceptor");
        registry.addInterceptor(myInterceptor)
                .addPathPatterns("/**")//指定该类拦截的url
        .excludePathPatterns( "/static/**");//过滤静态资源
    }
    /**
     * 如果实现了Filter跨域拦截,这个跨域无效
     * 拦截器实现 跨域支持
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        log.info("addInterceptors addCorsMappings");
        registry.addMapping("/**")
                .allowedOriginPatterns("*")  //本人测试时springboot2.7版本用的是这个
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT","OPTIONS","HEAD")
                .allowedHeaders("*")
                .maxAge(3600);
    }
}
测试同理:
 
三、aop切面
引入maven:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
若出现无法解析aspectjweaver则需要手动加入其他版本maven解决问题
<dependency>
   <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
 * @Author: zhangximing
 * @Email: 530659058@qq.com
 * @Date: 2023/8/18 10:15
 * @Description: 切面
 */
@Slf4j
@Component  //表示它是一个Spring的组件
@Aspect  //表示它是一个切面
public class MyAspect {
    private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    /**
     * 第一个*代表返回类型不限
     * 第二个*代表所有类
     * 第三个*代表所有方法
     * (..) 代表参数不限
     * com.zhangximing.springbootinterceptor.controller 测试的controller层
     */
    @Pointcut("execution(public * com.zhangximing.springbootinterceptor.controller.*.*(..))")
    public void pointCut(){};
    @Before(value = "pointCut()")
    public void before(JoinPoint joinPoint){
        System.out.println("方法执行前执行......before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("<=====================================================");
        logger.info("请求来源: =》" + request.getRemoteAddr());
        logger.info("请求URL:" + request.getRequestURL().toString());
        logger.info("请求方式:" + request.getMethod());
        logger.info("响应方法:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("请求参数:" + Arrays.toString(joinPoint.getArgs()));
        logger.info("连接点的方法签名对象:"+joinPoint.getSignature());
        logger.info("连接点所在的目标对象:"+joinPoint.getTarget());
        logger.info("代理对象:"+joinPoint. getThis());
        logger.info("------------------------------------------------------");
        startTime.set(System.currentTimeMillis());
    }
    // 定义需要匹配的切点表达式,同时需要匹配参数
    /**
     * @description 要拦截修改参数的值只有使用这个方法,Around相当于before+after
     * @param pjp
     * @param arg 类型可以根据pointCut指定切点类下的方法确定,也可以使用统一的Object,也可以不写参数
     * @return
     * @throws Throwable
     */
    @Around("pointCut() && args(arg)")
    public Object around(ProceedingJoinPoint pjp, Object arg) throws Throwable{
        logger.info("入参:{}",arg);
        logger.info("方法环绕start...around");
        JSONObject param = JSONObject.parseObject(JSONObject.toJSONString(arg));
        if ("zxm".equals(param.getString("name"))){
            JSONObject result = new JSONObject();
            result.put("success",false);
            result.put("msg","error");
            return result;
        }
        param.put("exist",true);
        param.put("name","cml");
        //修改值
        Object[] objects = new Object[]{param};
        Object objectNew = pjp.proceed(objects);
        logger.info("方法环绕end...around");
        return objectNew;
    }
    @After("within(com.zhangximing.springbootinterceptor.controller.*)")
    public void after(){
        System.out.println("方法之后执行...after.");
    }
    /**
     *
     * @param AjaxResult  rst 该参数类型需要与测试的Controller层的返回值类型一致,否则不生效,也就是找不到
     *            该测试中的AjaxResult是测试项目中封装好的出参
     */
    @AfterReturning(pointcut="pointCut()",returning = "rst")
    public void afterRunning(JSONObject rst){
        if(startTime.get() == null){
            startTime.set(System.currentTimeMillis());
        }
        System.out.println("方法执行完执行...afterRunning");
        logger.info("耗时(毫秒):" +  (System.currentTimeMillis() - startTime.get()));
        logger.info("返回数据:{}", rst);
        logger.info("==========================================>");
    }
    @AfterThrowing("within(com.zhangximing.springbootinterceptor.controller.*)")
    public void afterThrowing(){
        System.out.println("异常出现之后...afterThrowing");
    }
}
实现效果用的是如下controller:
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/test1")
    public String test1(@RequestParam(required = false, value = "param") String param){
        log.info("test1:"+param);
        return "test1:"+param;
    }
    @RequestMapping("/test2")
    public JSONObject test2(@RequestBody JSONObject params){
        log.info("test2:"+params.toJSONString());
        params.put("success",true);
        return params;
    }
}
参数判断拦截以及参数修改等方式都可以通过aop切面来实现,这是比较基本的aop拦截实现
 
 最后关于aop失效补充下,切面只能对被spring代理的对象起作用,目前是针对的请求入口进行拦截,我试了下踩坑,比如说如果要对service或dao进行拦截,可以使用注解注入的方式生效。
 
 



















