springBoot2整合SpringSecurity+Swagger3(源码分析二)


SpringBoot2整合SpringSecurity+Swagger3系列


在一章当中,已经学习到TomcatStarter在Tomcat启动过程中的关键作用 - 执行收集到的ServletContextInitializer的启动作用。其中主要包含三个,一个是在创建Tomcat的时候传入的一个匿名的ServletContextInitializer,一个是用于处理Session和Cookie的,还有一个是用于设置初始化参数的。后面两个都比较简单,本质还是属于Servlet的范畴,而第一个呢?其实第一个是非常重要的,也是将Spring容器与Servlet容器整合到一起的关键。

在前面我们知道,收集到的ServletContextInitializer都存放在initializers属性当中。在Tomcat容器启动之后,就会执行每一个收集到的ServletContextInitializer对象的onStartup方法。

for (ServletContextInitializer initializer : this.initializers) {
	initializer.onStartup(servletContext);
}

此时才会真实执行上面提及到的ServletWebServerApplicationContext#selfInitialize方法了。
再回顾一下

private void selfInitialize(ServletContext servletContext) throws ServletException {
	// 设置servlet容器的Spring容器属性
	prepareWebApplicationContext(servletContext);
	// 注册一个全局的application scope
	registerApplicationScope(servletContext);
	// 注册单例比如servletContext servletConfig contextParameters contextAttributes
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	// 收集Spring当中的Servlet元素并与Sevlet容器发生关系
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

这里最关键的还是整合Spring当中定义的Sevlet元素(Servlet、Filter、Listener)到ServletContext当中。

/**
 * Returns {@link ServletContextInitializer}s that should be used with the embedded
 * web server. By default this method will first attempt to find
 * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
 * {@link EventListener} beans.
 * @return the servlet initializer beans
 */
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
	return new ServletContextInitializerBeans(getBeanFactory());
}

根据当前Spring工厂创建一个ServletContextInitializerBeans对象(其实就是收集各种Servlet元素)

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class<? extends ServletContextInitializer>... initializerTypes) {
	this.initializers = new LinkedMultiValueMap<>();
	this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
			: Collections.singletonList(ServletContextInitializer.class);
	addServletContextInitializerBeans(beanFactory);
	addAdaptableBeans(beanFactory);
	List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
			.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
	this.sortedList = Collections.unmodifiableList(sortedInitializers);
	logMappings(this.initializers);
}

addServletContextInitializerBeans

org.springframework.boot.web.servlet.ServletContextInitializerBeans#getOrderedBeansOfType(org.springframework.beans.factory.ListableBeanFactory, java.lang.Class<T>, java.util.Set<?>)

	private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,
			Set<?> excludes) {
		String[] names = beanFactory.getBeanNamesForType(type, true, false);
		Map<String, T> map = new LinkedHashMap<>();
		for (String name : names) {
			if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
				T bean = beanFactory.getBean(name, type);
				if (!excludes.contains(bean)) {
					map.put(name, bean);
				}
			}
		}
		List<Entry<String, T>> beans = new ArrayList<>();
		beans.addAll(map.entrySet());
		beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));
		return beans;
	}

在这里插入图片描述
获取所有ServletContextInitializer类型的Bean名称,然后获取Bean,排序并返回。结果如下
在这里插入图片描述
接下来在addServletContextInitializerBeans方法中遍历以上的结果,并根据类型(Servlet、Filter、ServletContextInitializer等类型)进行分组

private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
		ListableBeanFactory beanFactory) {
	if (initializer instanceof ServletRegistrationBean) {
		Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
		addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
	}
	else if (initializer instanceof FilterRegistrationBean) {
		Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
		addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
	}
	else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
		String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
		addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
	}
	else if (initializer instanceof ServletListenerRegistrationBean) {
		EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
		addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
	}
	else {
		addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
				initializer);
	}
}

分组的结果如下,有两个Filter(分别为监控、安全),一个Servlet(DispatchServlet)和一个Servlet上下文初始器(用于actuator)。
在这里插入图片描述

  • dispatcherServletRegistration

配置Spring Boot的SERVLET环境
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration#dispatcherServletRegistration

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
	DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
			this.webMvcProperties.getServlet().getPath());
	registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
	registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
	if (this.multipartConfig != null) {
		registration.setMultipartConfig(this.multipartConfig);
	}
	return registration;
}

在这里插入图片描述
从上面bean的定义不难看出,DispatcherServletRegistrationBean与DispatcherServlet有很大的关系,所以创建的时候需要注入DispatcherServlet。而从继承结构看,这个类最终继承自RegistrationBean这个类。而这个类的主要目的是用于Servlet 3.0相关Bean的注册的,其实就是将Servlet相关的元素整合到Spring当中。比如Filter、Listener,下面的几个Bean其实都是这个逻辑。首先在RegistrationBean当中存在如下这个抽象方法

/**
 * Register this bean with the servlet context.
 * @param description a description of the item being registered
 * @param servletContext the servlet context
 */
protected abstract void register(String description, ServletContext servletContext);

这个方法是由子类来实现,调用则是在onStartup方法当中。

@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
	String description = getDescription();
	if (!isEnabled()) {
		logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
		return;
	}
	register(description, servletContext);
}

这个方法会在Servlet容器启动初始化的时候进行调用,然后将servletContext传入进来,得到了servletContext之后,就可以进行Servlet、Filter等元素的注册了。
比如ServletRegistrationBean#addRegistration注册Servlet

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
	String name = getServletName();
	return servletContext.addServlet(name, this.servlet);
}

回到DispatcherServletRegistrationBean这个Bean上面,在实例化的时候将DispatcherServlet进行包装

/**
 * Create a new {@link ServletRegistrationBean} instance with the specified
 * {@link Servlet} and URL mappings.
 * @param servlet the servlet being mapped
 * @param alwaysMapUrl if omitted URL mappings should be replaced with '/*'
 * @param urlMappings the URLs being mapped
 */
public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
	Assert.notNull(servlet, "Servlet must not be null");
	Assert.notNull(urlMappings, "UrlMappings must not be null");
	this.servlet = servlet;
	this.alwaysMapUrl = alwaysMapUrl;
	this.urlMappings.addAll(Arrays.asList(urlMappings));
}

然后在Servlet容器初始化的时候再回调方法将DispatcherServlet注册到Servlet容器当中。还有一些Servlet相关的配置信息通过WebMvcProperties来配置了。
在这里插入图片描述

Spring Boot和Spring MVC有一个很大的不同点就在于Servlet容器与Spring容器的启动先后顺序不同。Spring Boot通过将Servlt容器的初始化放在Spring容器刷新过程中(Bean注册和所有非懒加载Bean初始化中间)保证了Servlet 3.0的一些组件(Servlet、Filter等)可以先放到Spring容器中,然后再注册到Servlet容器中,达到整合Spring容器和Servlet容器的目的。

  • webMvcMetricsFilter

来源包:spring-boot-actuator-autoconfigure(性能监控)
来源类:org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration

@Bean
public FilterRegistrationBean<WebMvcMetricsFilter> webMvcMetricsFilter(MeterRegistry registry,
		WebMvcTagsProvider tagsProvider) {
	Server serverProperties = this.properties.getWeb().getServer();
	WebMvcMetricsFilter filter = new WebMvcMetricsFilter(registry, tagsProvider,
			serverProperties.getRequestsMetricName(), serverProperties.isAutoTimeRequests());
	FilterRegistrationBean<WebMvcMetricsFilter> registration = new FilterRegistrationBean<>(filter);
	registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
	registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
	return registration;
}

在这里插入图片描述
在这里插入图片描述

  • securityFilterChainRegistration

来源包:spring-boot-autoconfigure(认证授权)
来源类:org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration

Auto-configuration for Spring Security’s Filter. Configured separately from SpringBootWebSecurityConfiguration to ensure that the filter’s order is still configured when a user-provided WebSecurityConfiguration exists.

@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
		SecurityProperties securityProperties) {
	DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
			DEFAULT_FILTER_NAME);
	registration.setOrder(securityProperties.getFilter().getOrder());
	registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
	return registration;
}

在这里插入图片描述
创建Bean的过程首先会实例化一个DelegatingFilterProxyRegistrationBean对象,这里并没有复杂的逻辑不过是设置targetBeanName为springSecurityFilterChain。
在这里插入图片描述
然后根据securityProperties设置属性,包括顺序、收集分派类型。
在这里插入图片描述

private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
	if (securityProperties.getFilter().getDispatcherTypes() == null) {
		return null;
	}
	return securityProperties.getFilter().getDispatcherTypes().stream()
			.map((type) -> DispatcherType.valueOf(type.name()))
			.collect(Collectors.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
}

创建流、收集、然后转为EnumSet.实例化对象如下所示
在这里插入图片描述
然后这个类又实现了ApplicationContextAware接口,所以在Bean初始化阶段又会进入到setApplicationContext方法当中。

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
	this.applicationContext = applicationContext;
}

这里的逻辑比较简单,仅仅是设置了Spring上下文对象。

这个对象在Servlet容器的初始化过程中会获取对应的Filter,对应的一个代理DelegatingFilterProxy

@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
	String description = getDescription();
	if (!isEnabled()) {
		logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
		return;
	}
	register(description, servletContext);
}
@Override
protected String getDescription() {
	Filter filter = getFilter();
	Assert.notNull(filter, "Filter must not be null");
	return "filter " + getOrDeduceName(filter);
}

@Override
public DelegatingFilterProxy getFilter() {
	return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) {

		@Override
		protected void initFilterBean() throws ServletException {
			// Don't initialize filter bean on init()
		}

	};
}

根据前面设置的目标Bean名称和容器创建DelegatingFilterProxy对象

/**
 * Create a new {@code DelegatingFilterProxy} that will retrieve the named target
 * bean from the given Spring {@code WebApplicationContext}.
 * <p>For use in Servlet 3.0+ environments where instance-based registration of
 * filters is supported.
 * <p>The target bean must implement the standard Servlet Filter interface.
 * <p>The given {@code WebApplicationContext} may or may not be refreshed when passed
 * in. If it has not, and if the context implements {@link ConfigurableApplicationContext},
 * a {@link ConfigurableApplicationContext#refresh() refresh()} will be attempted before
 * retrieving the named target bean.
 * <p>This proxy's {@code Environment} will be inherited from the given
 * {@code WebApplicationContext}.
 * @param targetBeanName name of the target filter bean in the Spring application
 * context (must not be {@code null}).
 * @param wac the application context from which the target filter will be retrieved;
 * if {@code null}, an application context will be looked up from {@code ServletContext}
 * as a fallback.
 * @see #findWebApplicationContext()
 * @see #setEnvironment(org.springframework.core.env.Environment)
 */
public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
	Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
	this.setTargetBeanName(targetBeanName);
	// 设置容器
	this.webApplicationContext = wac;
	if (wac != null) {
		// 设置环境
		this.setEnvironment(wac.getEnvironment());
	}
}

然后将这个过滤器注册到Servlet容器当中然后设置相关的参数,比如DispatcherType、UrlMappings、
org.springframework.boot.web.servlet.DynamicRegistrationBean#register

@Override
protected final void register(String description, ServletContext servletContext) {
	D registration = addRegistration(description, servletContext);
	if (registration == null) {
		logger.info(
				StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?)");
		return;
	}
	configure(registration);
}

这里有一个很奇怪的事情,就是创建DelegatingFilterProxy对象的时候将initFilterBean的实现覆盖了
在这里插入图片描述
而默认的实现为

@Override
protected void initFilterBean() throws ServletException {
	synchronized (this.delegateMonitor) {
		if (this.delegate == null) {
			// If no target bean name specified, use filter name.
			if (this.targetBeanName == null) {
				this.targetBeanName = getFilterName();
			}
			// Fetch Spring root application context and initialize the delegate early,
			// if possible. If the root application context will be started after this
			// filter proxy, we'll have to resort to lazy initialization.
			WebApplicationContext wac = findWebApplicationContext();
			if (wac != null) {
				this.delegate = initDelegate(wac);
			}
		}
	}
}

Servlet容器在启动过程中,会执行Filter的初始化,然后调用这个initFilterBean方法获取真实的代理过滤器,在这里就是bean名称为springSecurityFilterChain的Bean。那么为什么现在不按照这个逻辑呢?在下一章中我们会介绍到springSecurityFilterChain这个Bean的创建过程,可以说这个Bean的创建还是非常复杂的。而且Tomcat启动之后使用的是一个守护后台线程,而主线程会继续执行Spring的refresh操作,所以会出现两个线程同时去创建这个springSecurityFilterChain的情况,最后导致冲突甚至容器启动失败。所以在这里将这个方法覆盖掉,啥也不做,而springSecurityFilterChain的创建、初始化由Spring容器去完成。至于啥时候再去设置这个代理过滤器真实的代理呢?那就是真实调用的时候

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {

	// Lazily initialize the delegate if necessary.
	Filter delegateToUse = this.delegate;
	if (delegateToUse == null) {
		synchronized (this.delegateMonitor) {
			delegateToUse = this.delegate;
			if (delegateToUse == null) {
				WebApplicationContext wac = findWebApplicationContext();
				if (wac == null) {
					throw new IllegalStateException("No WebApplicationContext found: " +
							"no ContextLoaderListener or DispatcherServlet registered?");
				}
				delegateToUse = initDelegate(wac);
			}
			this.delegate = delegateToUse;
		}
	}

	// Let the delegate perform the actual doFilter operation.
	invokeDelegate(delegateToUse, request, response, filterChain);
}

如上面源码所示,第一次过滤器起作用的时候,会去判断delegateToUse是否存在,如果不存在则会去初始化。同样,这里也必须考虑可能同时多个请求过来,这里使用synchronized来保证线程安全。初始化代理的逻辑如下所示,通过bean名称获取Bean是很快的,所以用synchronized是很合理的。

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
	String targetBeanName = getTargetBeanName();
	Assert.state(targetBeanName != null, "No target bean name set");
	Filter delegate = wac.getBean(targetBeanName, Filter.class);
	if (isTargetFilterLifecycle()) {
		delegate.init(getFilterConfig());
	}
	return delegate;
}

在上面源码使用DCL来保证并发安全性,所以delegate属性是需要加上volatile修饰符的。

  • servletEndpointRegistrar

来源包:spring-boot-actuator-autoconfigure(性能监控)
来源类:org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration.WebMvcServletEndpointManagementContextConfiguration

ManagementContextConfiguration for servlet endpoints.

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public static class WebMvcServletEndpointManagementContextConfiguration {

	private final ApplicationContext context;

	public WebMvcServletEndpointManagementContextConfiguration(ApplicationContext context) {
		this.context = context;
	}

	@Bean
	public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties,
			ServletEndpointsSupplier servletEndpointsSupplier) {
		DispatcherServletPath dispatcherServletPath = this.context.getBean(DispatcherServletPath.class);
		return new ServletEndpointRegistrar(dispatcherServletPath.getRelativePath(properties.getBasePath()),
				servletEndpointsSupplier.getEndpoints());
	}

}

在这里插入图片描述
比较简单吗,注册一堆的EndpointServlet

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
	this.servletEndpoints.forEach((servletEndpoint) -> register(servletContext, servletEndpoint));
}

private void register(ServletContext servletContext, ExposableServletEndpoint endpoint) {
	String name = endpoint.getEndpointId().toLowerCaseString() + "-actuator-endpoint";
	String path = this.basePath + "/" + endpoint.getRootPath();
	String urlMapping = path.endsWith("/") ? path + "*" : path + "/*";
	EndpointServlet endpointServlet = endpoint.getEndpointServlet();
	Dynamic registration = servletContext.addServlet(name, endpointServlet.getServlet());
	registration.addMapping(urlMapping);
	registration.setInitParameters(endpointServlet.getInitParameters());
	logger.info("Registered '" + path + "' to " + name);
}

首先是读取WebEndpointProperties类型Bean获取配置的属性,这些属性的前缀为management.endpoints.web。接下来获取DispatcherServletPath类型的Bean。而这个实现在默认情况下为DispatcherServletRegistrationBean。其实就上面名称为dispatcherServletRegistration的Bean。

在创建Bean的过程中主要是根据传入的basePath和servlet断点设置值而已。
在这里插入图片描述
比较简单,结果如下
在这里插入图片描述

addAdaptableBeans

protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
	MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
	addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
	addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
	for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
		addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
				new ServletListenerRegistrationBeanAdapter());
	}
}

接下来从容器中获取MultipartConfigElement类型Bean,主要是用于文件上传的配置信息。
在这里插入图片描述
然后再次到容器中获取Servlet和Filter,前者只有DispatchServlet(已经处理过了),后者还有其他的一些配置,比如以下这些都是系统默认需要的。
在这里插入图片描述
比如characterEncodingFilter在Spring中定义如下

@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

	private final HttpProperties.Encoding properties;

	public HttpEncodingAutoConfiguration(HttpProperties properties) {
		this.properties = properties.getEncoding();
	}

	@Bean
	@ConditionalOnMissingBean
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}
	
	...
}	

类似的hiddenHttpMethodFilter和formContentFilter

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	public static final String DEFAULT_PREFIX = "";

	public static final String DEFAULT_SUFFIX = "";

	private static final String[] SERVLET_LOCATIONS = { "/" };

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

	@Bean
	@ConditionalOnMissingBean(FormContentFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
	public OrderedFormContentFilter formContentFilter() {
		return new OrderedFormContentFilter();
	}

接下来还会考虑加载EventListener类型Bean.

其中SUPPORTED_TYPES在类加载的时候添加的,这些都属于Servlet规范了。

public class ServletListenerRegistrationBean<T extends EventListener> extends RegistrationBean {

	private static final Set<Class<?>> SUPPORTED_TYPES;

	static {
		Set<Class<?>> types = new HashSet<>();
		types.add(ServletContextAttributeListener.class);
		types.add(ServletRequestListener.class);
		types.add(ServletRequestAttributeListener.class);
		types.add(HttpSessionAttributeListener.class);
		types.add(HttpSessionListener.class);
		types.add(ServletContextListener.class);
		SUPPORTED_TYPES = Collections.unmodifiableSet(types);
	}
    ...
}	

通过以上步骤,收集到的Servlet元素如下所示
在这里插入图片描述
接下来通过流处理(针对每一个map的条目值排序然后通过flatMap转给一个流收集为列表)为列表,然后转为不可修改列表设置为对象属性,并根据日志级别打印日志(org.springframework.boot.web.servlet.ServletContextInitializerBeans为Debug级别)。

List<ServletContextInitializer> sortedInitializers = this.initializers.values()
			.stream()
			.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);

在这里插入图片描述
最后再回过头来看一下ServletContextInitializerBeans这个类的类型。
在这里插入图片描述
竟然是一个集合,而针对这个集合的迭代就是迭代上面获取的排序好的列表。

@Override
public Iterator<ServletContextInitializer> iterator() {
	return this.sortedList.iterator();
}

@Override
public int size() {
	return this.sortedList.size();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值