springboot系列--拦截器加载原理

一、拦截器加载原理

拦截器是在容器启动时,就创建并加载好,此时并未放入拦截器链中,只是放在一个拦截器集合当中,当一个请求进来之后,会通过匹配路径,查看是否有命中集合中的拦截器的拦截路径,如果命中,则放入拦截器链中,如果没有命中则不会放入。之后在执行请求之前,会循环遍历执行命中的拦截器中的逻辑,如果都通过,则执行请求,反之则不执行。

一、拦截器加载进拦截器集合

默认情况下DispatcherServlet会注册3个HandlerMapping,分别是BeanNameUrlHandlerMapping、RequestMappingHandlerMapping以及RouterFunctionMapping。

其中HandlerMapping的作用是存储请求路径与处理方法的映射,收到请求后就能通过HandlerMapping找到对应的处理方法RequestMappingHandlerMapping对象处理通过Controller注解的类中RequestMapping注解标注的方法。

这个对象主要是在WebMvcConfigurationSupport这个配置类里面做了Bean的创建,这个配置类,主要是对springMVC所有配置做初始化。会在项目启动时自动纳入容器管理,并初始化。

@Bean
	@SuppressWarnings("deprecation")
	public RequestMappingHandlerMapping requestMappingHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

// 创建一个RequestMappingHandlerMapping对象,由于这个对象继承了AbstractHandlerMapping这个对象,会先去创建AbstractHandlerMapping这个对象,再创建创建一个RequestMappingHandlerMapping对象
		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		mapping.setOrder(0);
// 添加拦截器		
		mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
		mapping.setContentNegotiationManager(contentNegotiationManager);
		mapping.setCorsConfigurations(getCorsConfigurations());

		PathMatchConfigurer configurer = getPathMatchConfigurer();

		Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
		if (useSuffixPatternMatch != null) {
			mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
		}
		Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
		if (useRegisteredSuffixPatternMatch != null) {
			mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
		}
		Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
		if (useTrailingSlashMatch != null) {
			mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
		}

		UrlPathHelper pathHelper = configurer.getUrlPathHelper();
		if (pathHelper != null) {
			mapping.setUrlPathHelper(pathHelper);
		}
		PathMatcher pathMatcher = configurer.getPathMatcher();
		if (pathMatcher != null) {
			mapping.setPathMatcher(pathMatcher);
		}
		Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
		if (pathPrefixes != null) {
			mapping.setPathPrefixes(pathPrefixes);
		}

		return mapping;
	}
// WebMvcConfigurationSupport类里面的方法
protected final Object[] getInterceptors(
			FormattingConversionService mvcConversionService,
			ResourceUrlProvider mvcResourceUrlProvider) {
			// 加载开始时是为空的
		if (this.interceptors == null) {
			InterceptorRegistry registry = new InterceptorRegistry();
			// 添加拦截器,这个方法是一个空方法,有 WebMvcConfigurationSupport的子类实现,这里会进入DelegatingWebMvcConfiguration这个类
			addInterceptors(registry);
			registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
			registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
			this.interceptors = registry.getInterceptors();
		}
		return this.interceptors.toArray();
	}
	// DelegatingWebMvcConfiguration这个类的添加拦截器方法
	protected void addInterceptors(InterceptorRegistry registry) {
	// 从这进入会到WebMvcConfigurerComposite类的方法
		this.configurers.addInterceptors(registry);
	}

// WebMvcConfigurerComposite的addInterceptors方法
	public void addInterceptors(InterceptorRegistry registry) {
		// 这里循环遍历的delegates是当前类创建的一个private final List<WebMvcConfigurer> delegates = new ArrayList<>();集合,
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.addInterceptors(registry);
		}
	}
	




	//  WebMvcConfigurerComposite这个类里面有一个方法对这个集合进行设置值
		public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.delegates.addAll(configurers);
		}
	}
	


	// 这个设置值的方法又是被DelegatingWebMvcConfiguration这个类调用了,这个类里面有这个方法,再去点击会发现没有地方调用了,
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}



	
	// 点击DelegatingWebMvcConfiguration这个类会发现是由EnableWebMvc这个注解将这个类导入进容器中,除此之外,springboot中的WebMvcAutoConfiguration类中
	// EnableWebMvcConfiguration这个内部类继承了DelegatingWebMvcConfiguration这个类,然后注入了容器中
	/**那也就是说当创建EnableWebMvcConfiguration这个类对象时,容器会先创建DelegatingWebMvcConfiguration对象,然后扫描实现了这个接口的WebMvcConfigurer类,然后设置进
	**private final List<WebMvcConfigurer> delegates = new ArrayList<>();集合中
	**
	*/




// 注解EnableWebMvc
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}


// EnableWebMvcConfiguration这个内部类
@Configuration(
        proxyBeanMethods = false
    )
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
	}

添加完拦截器后还是会回到WebMvcConfigurationSupport这个类中的getInterceptors

这个方法里继续执行 剩余逻辑。

下面代码就是InterceptorRegistry类对外提供获取拦截器集合里所有拦截器的方法

	protected List<Object> getInterceptors() {
		return this.registrations.stream()
				.sorted(INTERCEPTOR_ORDER_COMPARATOR)
				.map(InterceptorRegistration::getInterceptor)
				.collect(Collectors.toList());
	}

然后将获取到的所有拦截器放到AbstractHandlerMapping这个抽象类中的

复制代码
private final List<Object> interceptors = new ArrayList<>();

这个集合当中。

之后将继续执行剩下的逻辑

之后会执行RequestMappingHandlerMapping的拦截器初始化方法initInterceptors,这个方法是在他的父类AbstractHandlerMapping这个类中。

	@Override
	protected void initApplicationContext() throws BeansException {
		extendInterceptors(this.interceptors);
		detectMappedInterceptors(this.adaptedInterceptors);
// 这里就是初始化拦截器
		initInterceptors();
	}

// 还是在当前类下
	protected void initInterceptors() {
		if (!this.interceptors.isEmpty()) {
			for (int i = 0; i < this.interceptors.size(); i++) {
				Object interceptor = this.interceptors.get(i);
				if (interceptor == null) {
					throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
				}
				this.adaptedInterceptors.add(adaptInterceptor(interceptor));
			}
		}
	}

这样做的目的是为了之后请求进来了,将拦截器添加到拦截器链中

二、拦截器加载进拦截器链

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 如果handler不是handler执行链对象,就创建一个
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        // 获取请求进来的路径,这里一定要注意server:servlet:context-path: /work  这个在yaml配置的路径,不会被截取到。    
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
        // 循环遍历这个就是之前挡在AbstractHandlerMapping这个类里面的adaptedInterceptors集合中的拦截器
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                // 关键在这里,如果匹配上了,才会添加进拦截器链中等待执行
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

原始请求:/work/work/test/export,才能命中拦截器路径,这是由于系统配置多加了一个/work

复制代码
server:
  servlet:
    context-path: /work

二、实现多拦截器链式执行,确保每个拦截器按顺序执行且支持动态添加或移除拦截器

想要动态添加和移除拦截器可以借用nacos来进行控制。由于spring并未提供直接从拦截器链中或者拦截器集合中移除的方法,所以可以使用控制拦截器是否执行拦截器逻辑的方式,变相达到动态移除和添加拦截器的效果

一、nacos配置

test:
  dynamic-interceptors: [
    {
      "name": "FirstInterceptor",
      "enabled": true, // 控制是否添加进容器
      "valid": false, // 控制是否开启拦截器
      "order": 2,  // 控制排序
      "includePatterns": ["/work/test/**"],
      "excludePatterns": ["/work/demo/**"] // 白名单
    },
    {
      "name": "SecondInterceptor",
      "enabled": true,
      "valid": false,
       "order": 1,
      "includePatterns": ["/work/**"],
      "excludePatterns": []
    }
  ]

二、获取nacos配置

// 可动态获取最新的nacos配置
@Data
@Configuration
@ConfigurationProperties("test")
public class InterceptorPro {


    // 拦截器信息
    private List<DynamicInterceptors> dynamicInterceptors;

    @Data
    public static class DynamicInterceptors {

    private String name;
    private boolean enabled;
    private boolean valid;
    private Integer order;
    private List<String> includePatterns;
    private List<String> excludePatterns;
}


}

三、提前创建好的拦截器,需要实现自己的拦截逻辑

@Slf4j
@Component("FirstInterceptor")
public class FirstInterceptor implements HandlerInterceptor {

    @Resource
    private InterceptorPro interceptorPro;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        log.info("开始进入拦截器:{}","FirstInterceptor");

        for (InterceptorPro.DynamicInterceptors dynamicInterceptors : interceptorPro.getDynamicInterceptors()) {
            if(this.getClass().getSimpleName().equals(dynamicInterceptors.getName()) && Boolean.FALSE == dynamicInterceptors.isValid()){
                log.info("当前拦截器未开启|拦截器名:{}",dynamicInterceptors.getName());
                return Boolean.TRUE;
            }
        }

        String channel = request.getHeader("channel");
        if (!"test".equalsIgnoreCase(channel)) {
            log.info("channel不等于test,禁止通过:{}","FirstInterceptor");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }
}

@Slf4j
@Component("SecondInterceptor")
public class SecondInterceptor implements HandlerInterceptor {

    @Resource
    private InterceptorPro interceptorPro;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("开始进入拦截器:{}","SecondInterceptor");

        for (InterceptorPro.DynamicInterceptors dynamicInterceptors : interceptorPro.getDynamicInterceptors()) {
            if(this.getClass().getSimpleName().equals(dynamicInterceptors.getName()) && Boolean.FALSE == dynamicInterceptors.isValid()){
                log.info("当前拦截器未开启|拦截器名:{}",dynamicInterceptors.getName());
                return Boolean.TRUE;
            }
        }

        String channel = request.getHeader("channel");
        if (!"test".equalsIgnoreCase(channel)){
            log.info("channel不等于test,禁止通过:{}","SecondInterceptor");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }
}

四、添加添加器到拦截器集合当中

@Component
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private InterceptorPro interceptorPro;
    

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 这里按照nacos配置好的顺序由低到高进行排序,拦截器的顺序是按照添加拦截器的先后,由高到低进行排序的,越先添加的拦截器优先级越高
        List<InterceptorPro.DynamicInterceptors> interceptors = interceptorPro.getDynamicInterceptors().stream().filter(Objects::nonNull)
                .sorted(Comparator.comparing(InterceptorPro.DynamicInterceptors::getOrder))
                .collect(Collectors.toList());

        // 遍历nacos配置
        for (InterceptorPro.DynamicInterceptors config : interceptors) {
            // 通过nacos配置好的拦截器名字,从容器中获取拦截器对象
            HandlerInterceptor interceptor = (HandlerInterceptor) applicationContext.getBean(config.getName());
            // 如果enabled配置开启,则添加进拦截器集合当中,注意这里并没有添加到拦截器链中
            if (config.isEnabled()) {
                registry.addInterceptor(interceptor)
                        .addPathPatterns(config.getIncludePatterns().toArray(new String[0]))
                        .excludePathPatterns(config.getExcludePatterns().toArray(new String[0]));
            }
        }
        
    }

第二种排序方式:

相关推荐
幸好我会魔法4 小时前
人格分裂(交互问答)-小白想懂Elasticsearch
大数据·spring boot·后端·elasticsearch·搜索引擎·全文检索
危险、5 小时前
Spring Boot 无缝集成SpringAI的函数调用模块
人工智能·spring boot·函数调用·springai
何中应5 小时前
从管道符到Java编程
java·spring boot·后端
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS贸易行业crm系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源
CPU NULL7 小时前
新版IDEA创建数据库表
java·数据库·spring boot·sql·学习·mysql·intellij-idea
长路 ㅤ   9 小时前
SpringBoot支持动态更新配置文件参数
java·spring boot·后端·动态配置
东方fan12 小时前
SpringBoot 使用海康 SDK 和 flv.js 显示监控画面
spring boot·海康·flv.js
cccl.13 小时前
JAVA(SpringBoot)集成Kafka实现消息发送和接收。
spring boot·后端·kafka
计算机-秋大田13 小时前
微信阅读网站小程序的设计与实现(LW+源码+讲解)
spring boot·后端·微信·微信小程序·小程序·课程设计
这里是杨杨吖14 小时前
SpringBoot+Electron教务管理系统 附带详细运行指导视频
spring boot·后端·electron·教务