Spring Boot配置嵌入式Servlet容器的一些思考 :
注册 Servlet Filter、Listener
当使用嵌入式的Servlet容器(Tomcat、Jetty等)时,我们通过将Servlet、Filter和Listener声明为Spring Bean而达到注册的效果;或者注册ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean的Bean。Spring Boot默认内嵌的Tomcat为servlet容器。通用的Servlet容器配置都以“server”作为前缀,而Tomcat特有配置都以“server.tomcat”作为前缀。
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
问题
如何定制和修改Servlet容器中的相关配置;
SpringBoot能不能支持其他的Servlet容器;
配置Servler容器
配置文件
1 2 3 4 5 6
#默认程序端口为8080 server.port=8080 #用户会话session过期事件,以秒为单位 server.session.cookie.comment=60 #配配置默认的访问路径,默认为/ server.context-path= /
通常的Servlet容器设置为为server.xxx=XXX 而Tomcat的设置则为server.tomcat=XXX
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
@ConfigurationProperties (prefix = "server" , ignoreUnknownFields = true )public class ServerProperties implements EmbeddedServletContainerCustomizer , EnvironmentAware , Ordered { private Integer port; private InetAddress address; private String contextPath; private String displayName = "application" ; @NestedConfigurationProperty private ErrorProperties error = new ErrorProperties(); private String servletPath = "/" ; private final Map<String, String> contextParameters = new HashMap<String, String>(); private Boolean useForwardHeaders; private String serverHeader; private int maxHttpHeaderSize = 0 ; private int maxHttpPostSize = 0 ; private Integer connectionTimeout; private Session session = new Session(); @NestedConfigurationProperty private Ssl ssl; @NestedConfigurationProperty private Compression compression = new Compression(); @NestedConfigurationProperty private JspServlet jspServlet; private final Tomcat tomcat = new Tomcat(); private final Jetty jetty = new Jetty(); private final Undertow undertow = new Undertow(); private Environment environment;
代码配置
1 2 3 4 5 6 7 8 9 10 11 12 13
@Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer () { return new EmbeddedServletContainerCustomizer() { @Override public void customize (ConfigurableEmbeddedServletContainer container) { container.setPort(8083 ); } }; }
启动服务
这个两个配置本质上都是差不多的都调用了 embeddedServletContainerCustomizer() 方法在SpringBoot中我们也会遇到很多的xxCustomizer帮助我们进行定制配置。
注册三大组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
package com.hph.springboot.config;import com.hph.springboot.filter.MyFilter;import com.hph.springboot.listener.MyListener;import com.hph.springboot.servlet.MyServlet;import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Arrays;@Configuration public class MyServerConfig { @Bean public ServletRegistrationBean myServlet () { ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet" ); registrationBean.setLoadOnStartup(1 ); return registrationBean; } @Bean public FilterRegistrationBean myFilter () { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello" ,"/myServlet" )); return registrationBean; } @Bean public ServletListenerRegistrationBean myListener () { ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return registrationBean; } @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer () { return new EmbeddedServletContainerCustomizer() { @Override public void customize (ConfigurableEmbeddedServletContainer container) { container.setPort(8083 ); } }; } }
点击ServletRegistrationBean进入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public ServletRegistrationBean (Servlet servlet, String... urlMappings) { this (servlet, true , urlMappings); } public ServletRegistrationBean (Servlet 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)); }
点击FilterRegistrationBean进入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public FilterRegistrationBean () { } public FilterRegistrationBean (Filter filter, ServletRegistrationBean... servletRegistrationBeans) { super (servletRegistrationBeans); Assert.notNull(filter, "Filter must not be null" ); this .filter = filter; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
package com.hph.springboot.servlet;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class MyServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello MyServlet" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package com.hph.springboot.listener;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;public class MyListener implements ServletContextListener { @Override public void contextInitialized (ServletContextEvent sce) { System.out.println("contextInitialized...web应用启动" ); } @Override public void contextDestroyed (ServletContextEvent sce) { System.out.println("contextDestroyed...当前web项目销毁" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
package com.hph.springboot.filter;import javax.servlet.*;import java.io.IOException;public class MyFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("MyFilter process..." ); chain.doFilter(request,response); } @Override public void destroy () { } }
SpringBoot帮我们自动配置SpringMVC的时候,自动注册SpringMVC的前端控制器,DispatcherServlet;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Bean (name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean (value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public ServletRegistrationBean dispatcherServletRegistration ( DispatcherServlet dispatcherServlet) { ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet, this .serverProperties.getServletMapping()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this .webMvcProperties.getServlet().getLoadOnStartup()); if (this .multipartConfig != null ) { registration.setMultipartConfig(this .multipartConfig); } return registration; }
替换为其他的嵌入式Servlet容器
SpringBoot默认支持
Tomcat(默认使用)
1 2 3 4 5
<!-- 引入web模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
引入web模块时默认的时Tomcat
Jetty
只需要修改pom文件即可
Undertow
原理
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
@AutoConfigureOrder (Ordered.HIGHEST_PRECEDENCE)@Configuration @ConditionalOnWebApplication @Import (BeanPostProcessorsRegistrar.class)public class EmbeddedServletContainerAutoConfiguration { @Configuration @ConditionalOnClass ({ Servlet.class, Tomcat.class }) @ConditionalOnMissingBean (value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory () { return new TomcatEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass ({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean (value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedJetty { @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory () { return new JettyEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass ({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean (value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedUndertow { @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory () { return new UndertowEmbeddedServletContainerFactory(); } }
EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)
1 2 3 4 5
public interface EmbeddedServletContainerFactory { EmbeddedServletContainer getEmbeddedServletContainer ( ServletContextInitializer... initializers) ;}
EmbeddedServletContainer:(嵌入式的Servlet容器)
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory () { return new TomcatEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass ({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean (value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedJetty { @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory () { return new JettyEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass ({ Servlet.class, Undertow.class, SslClientAuthMode.class })@ConditionalOnMissingBean (value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedUndertow { @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory () { return new UndertowEmbeddedServletContainerFactory(); } }
以TomcatEmbeddedServletContainerFactory为例分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
@Override public EmbeddedServletContainer getEmbeddedServletContainer ( ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = (this .baseDirectory != null ? this .baseDirectory : createTempDir("tomcat" )); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this .protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false ); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this .additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatEmbeddedServletContainer(tomcat); }
1 2 3 4
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer ( Tomcat tomcat) { return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
public TomcatEmbeddedServletContainer (Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null" ); this .tomcat = tomcat; this .autoStart = autoStart; initialize(); } private void initialize () throws EmbeddedServletContainerException { TomcatEmbeddedServletContainer.logger .info("Tomcat initialized with port(s): " + getPortsDescription(false )); synchronized (this .monitor) { try { addInstanceIdToEngineName(); try { removeServiceConnectors(); this .tomcat.start(); rethrowDeferredStartupExceptions(); Context context = findContext(); try { ContextBindings.bindClassLoader(context, getNamingToken(context), getClass().getClassLoader()); } catch (NamingException ex) { } startDaemonAwaitThread(); } catch (Exception ex) { containerCounter.decrementAndGet(); throw ex; } } catch (Exception ex) { throw new EmbeddedServletContainerException( "Unable to start embedded Tomcat" , ex); } } }
如何对嵌入式容器的配置修改时怎么样生效的。
使用serverProperties
EmbeddedServletContainerCustomizer : 定制器帮我们修改了Servlet容器的配置导入BeanPostProcessorsRegistrar:给容器中导入一些组件
1 2 3 4 5 6 7 8 9 10 11 12 13
@Override public void registerBeanDefinitions (AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this .beanFactory == null ) { return ; } registerSyntheticBeanIfMissing(registry, "embeddedServletContainerCustomizerBeanPostProcessor" , EmbeddedServletContainerCustomizerBeanPostProcessor.class); registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor" , ErrorPageRegistrarBeanPostProcessor.class); }
容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
@Override public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { if (bean instanceof ConfigurableEmbeddedServletContainer) { postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); } return bean; } private void postProcessBeforeInitialization ( ConfigurableEmbeddedServletContainer bean) { for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { customizer.customize(bean); } } private Collection<EmbeddedServletContainerCustomizer> getCustomizers () { if (this .customizers == null ) { this .customizers = new ArrayList<EmbeddedServletContainerCustomizer>( this .beanFactory .getBeansOfType(EmbeddedServletContainerCustomizer.class, false , false ) .values()); Collections.sort(this .customizers, AnnotationAwareOrderComparator.INSTANCE); this .customizers = Collections.unmodifiableList(this .customizers); } return this .customizers; }
步骤
步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer ,调用定制器的定制方法
嵌入式Servlet容器启动原理
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;
获取嵌入式的Servlet容器工厂:
1)、SpringBoot应用启动运行run方法
2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext ,否则:AnnotationConfigApplicationContext
3)、refresh(context);刷新刚才创建好的ioc容器;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); registerListeners(); finishBeanFactoryInitialization(beanFactory); finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }
4)、 onRefresh(); web的ioc容器重写了onRefresh方法
5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer ();
6)、获取嵌入式的Servlet容器工厂:
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory 创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
7)、使用容器工厂获取嵌入式的Servlet容器 :this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());
8)、嵌入式的Servlet容器创建对象并启动Servlet容器;
先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;
IOC容器启动创建嵌入式的Servlet容器