java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JAVA拦截方式

JAVA三种拦截方式详解(原生过滤器Filter、springMVC拦截器、aop切面)

作者:谦风(Java)

在Java开发中方法拦截是一种常见的技术,可以用于在方法执行前后添加额外的逻辑或修改方法的行为,这篇文章主要给大家介绍了关于JAVA三种拦截方式的相关资料,文中介绍的方式分别是原生过滤器Filter、springMVC拦截器、aop切面,需要的朋友可以参考下

最近面试有遇到拦截方式的场景,结合网上xdm的代码整理了下,分为以下三种:
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:

这里补充一下:

若非springboot的情况下,不使用@WebFilter则需要自己设置配置文件

你需要在web.xml文件中配置过滤器,指定要拦截的URL以及要使用的过滤器

<filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>com.zhangximing.springbootinterceptor.interceptor.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/example</url-pattern>
</filter-mapping>

二、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进行拦截,可以使用注解注入的方式生效。

总结 

到此这篇关于JAVA三种拦截方式(原生过滤器Filter、springMVC拦截器、aop切面)的文章就介绍到这了,更多相关JAVA拦截方式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文