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 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); }
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)。
配置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); }
那么这个onStartup是何时执行的呢?再注意到RegistrationBean实现了ServletContextInitializer接口,按照Servlet规范,这个方法会在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容器的目的。
来源包: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; }
来源包: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的过程首先会实例化一个DispatcherServletRegistrationBean对象,这里并没有复杂的逻辑不过是设置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上下文对象。
来源包: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断点设置值而已。
比较简单,结果如下
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(已经处理过了),后者还有其他的一些配置,比如以下这些都是系统默认需要的。
接下来还会考虑加载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(); }