Springboot实现拦截器的两种方式

实现过滤请求有两种方式:
一种就是用拦截器,一种就是过滤器
拦截器相对来说比较专业,而过滤器虽然不专业但是也能完成基本的拦截请求要求。

一、拦截器方式

1、配置HandlerInterceptor

下面这个也是我们公司项目拦截器的写法,总体来说感觉还不错,我就记录了下来。
利用了一个静态Pattern变量存储不走拦截器的路径,然后在preHandle方法当中进行过滤,让他返回true。

@Component
public class LoginInterceptor implements HandlerInterceptor{
    
    
	
	private static final Pattern SHOULD_NOT_FILTER_URL_PATTERN;
	
	static {
    
    
		List<String> urlList = new ArrayList<>();
		// 将不走拦截器的请求存放到Pattern
		urlList.add("(socket/.*)");
		urlList.add("(user/findUserList)");
		StringBuilder sb = new StringBuilder();
		for (String url : urlList) {
    
    
			sb.append(url);
			sb.append("|");
		}
		sb.setLength(sb.length() - 1);
		SHOULD_NOT_FILTER_URL_PATTERN = Pattern.compile(sb.toString());
	}
	
	/**
	 * 在请求处理之前进行调用(Controller方法调用之前)
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    
		
		HttpSession session = request.getSession();
		// 获取访问的url
 		String servletPath = request.getServletPath();
		// 排除特定请求
		if (SHOULD_NOT_FILTER_URL_PATTERN.matcher(servletPath).find()) {
    
    
			return true;
		}
		if (session.getAttribute("user") != null) {
    
    
			// 可能有的项目在校验完session,还会校验token
			String token = request.getHeader("access_token");
			// 此处业务省略。。。
			return true;
		}
		return false;
	}

	/**
	 * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
	 */
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
    
    
		// TODO Auto-generated method stub
		
	}

	/**
	 * 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
	 */
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
    
    
		// TODO Auto-generated method stub
		
	}
}

2、注册拦截器

配置完上面的拦截器还需要注册拦截器。

WebMvcConfigurerAdapter类是 Spring内部的一种配置方式
采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制
WebMvcConfigurerAdapter是WebMvcConfigurer接口的一个实现类

springboot2.5.4版本,注册拦截器的WebMvcConfigurerAdapter类,已经是个过期类。

如果你使用的springboot版本发现也是个过期类,可以直接改为implements WebMvcConfigurer就可以WebMvcConfigurer虽然是接口类,但是他的方法都是有方法体的方法,所以可以选择不重写。

@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {
    
    
	
	@Autowired
    private LoginInterceptor loginInterceptor;
	

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    
    
		// 多个拦截器组成一个拦截器链
		// addPathPatterns 用于添加拦截规则
		// excludePathPatterns 用户排除拦截
		registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
		super.addInterceptors(registry);
	}
}

3、使用拦截器的坑

继承WebMvcConfigurerAdapter 重写addInterceptors方法的时候,一定要使用注入方式,将loginInterceptor注入到变量当中,要么就使用@Bean注解的方式,将loginInterceptor(拦截器)注入到容器。

之所以这么搞是因为拦截器在 Bean 初始化之前进行,所以在拦截器中无法像这样注入 Bean。
就算加了@Component,他是存放于容器当中了,但是他存放容器当中的对象属性,是空属性。

在WebMvcConfigurerAdapter 使用@Autowired注入了一遍拦截器,属性就有值了。

说白了不管采用哪种方案,目的只有一个,让对象的属性有值,因为拦截器比其他对象初始化早,导致属性为空,想让他有值,就想办法让他重新走一遍spring注入容器,也可以采用这种方式:

@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {
    
    
	
	// 这么写的目的是为了在SessionInterceptor中能注入spring中的service
	@Bean
	LoginInterceptor loginInterceptor() {
    
    
		return new LoginInterceptor();
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    
    
		// 多个拦截器组成一个拦截器链
		// addPathPatterns 用于添加拦截规则
		// excludePathPatterns 用户排除拦截
		registry.addInterceptor(loginInterceptor()).addPathPatterns("/**");
		super.addInterceptors(registry);
	}
}

4、拦截器执行两遍的问题

通过以上代码配置确实拦截器执行了,在拦截器的preHandle,放一个输出语句,然后执行一个接口,可以看出拦截器是执行了两遍,与此同时这个问题还有个特点,它并没有干扰具体的业务功能,就是controller正常返回,没有任何错误。

这个问题,找了好久没想到竟然是这个原因

就是当我们浏览器访问接口的时候,他会默认去访问一个图标,而他也相当于是访问的我们项目路径,所以也会进入拦截器。

因为这个问题,搜了很多资料,有的还说是spring有一个异步线程,还有的说当请求数据正确拦截器方法会执行一次,当请求数据或请求路径错误时会自动触发一个/error请求,这个经过测试不会出现,一般访问哪个接口报错了,直接接口就错了,不会因为报错还访问一个error请求。

当时还有人说在preHandle加上这个if判断就好了,这个我也试了,加上确实就好了,但是实际上就相当于拦截器失效。这段代码的意思就是假如访问的是项目当中的路径,也就是controller当中存在的映射路径,就放行,假如不是就拦截。拦截器当中true就是放行,false拦截。
在这里插入图片描述

二、过滤器方式

本人这一篇博客写了关于filter的一些知识:https://blog.csdn.net/weixin_43888891/article/details/112550155

1、实现Filter接口

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Component;

@Component
@WebFilter(filterName = "requestParamFilter",urlPatterns = "/*")
public class RequestParamFilter implements Filter {
    
    

	private static final Pattern SHOULD_NOT_FILTER_URL_PATTERN;
	
	static {
    
    
		List<String> urlList = new ArrayList<>();
		// 将不走拦截器的请求存放到Pattern
		urlList.add("(socket/.*)");
		urlList.add("(user/findUserList)");
		StringBuilder sb = new StringBuilder();
		for (String url : urlList) {
    
    
			sb.append(url);
			sb.append("|");
		}
		sb.setLength(sb.length() - 1);
		SHOULD_NOT_FILTER_URL_PATTERN = Pattern.compile(sb.toString());
	}

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    
    

    }
	
	// 发送请求会执行这个方法
    // 一个doFilter相当于拦截器的执行前和执行后
    // filterChain.doFilter后面的内容就是执行后的内容,假如不执行filterChain.doFilter方法相当于方法被拦截
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
    
    
        System.out.println("sunhan---请求参数过滤器!---test1");
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpSession session = request.getSession();
		// 获取访问的url
 		String servletPath = request.getServletPath();
		// 排除特定请求
		if (SHOULD_NOT_FILTER_URL_PATTERN.matcher(servletPath).find()) {
    
    
			filterChain.doFilter(servletRequest,servletResponse);
		}
        System.out.println("开始拦截了................");
        //业务代码
    }

    @Override
    public void destroy() {
    
    

    }
}

2、使用过滤器需要注意的

拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑

执行顺序如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43888891/article/details/119778754