SpringBoot 笔记 ( 十一 ):Servlet容器 SpringBoot默认使用Tomcat作为嵌入式的Servlet容器
1)、定制和修改Servlet容器的相关配置 1、修改配置文件中的和 server 有关的配置 ServerProperties【也是EmbeddedServletContainerCustomizer】
1 2 3 4 5 6 7 server.port =8081 server.context-path =/crud server.tomcat.uri-encoding =UTF-8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 server.tomcat.xxx
2、编写一个 EmbeddedServletContainerCustomizer 嵌入式的Servlet容器的定制器,来修改Servlet容器的配置
1 2 3 4 5 6 7 8 9 10 @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer () { return new EmbeddedServletContainerCustomizer() { @Override public void customize (ConfigurableEmbeddedServletContainer container) { container.setPort(8083 ); } }; }
2)、注册Servlet三大组件【Servlet、Filter、Listener】 由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册三大组件用以下方式:
1.ServletRegistrationBean 1 2 3 4 5 6 7 @Bean public ServletRegistrationBean myServlet () { ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet" ); return registrationBean; }
2.FilterRegistrationBean 1 2 3 4 5 6 7 @Bean public FilterRegistrationBean myFilter () { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello" ,"/myServlet" )); return registrationBean; }
3.ServletListenerRegistrationBean 1 2 3 4 5 @Bean public ServletListenerRegistrationBean myListener () { ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener()); return registrationBean; }
4、DIspatcherServlet 配置 SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器,DIspatcherServlet
DispatcherServletAutoConfiguration中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @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; }
3)、替换为其他嵌入式Servlet容器 默认支持:
1.Tomcat(默认使用) 1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器; </dependency >
2.Jetty(长链接类的服务) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <exclusions > <exclusion > <artifactId > spring-boot-starter-tomcat</artifactId > <groupId > org.springframework.boot</groupId > </exclusion > </exclusions > </dependency > <dependency > <artifactId > spring-boot-starter-jetty</artifactId > <groupId > org.springframework.boot</groupId > </dependency >
3.Undertow(高并发的不支持JSP) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <exclusions > <exclusion > <artifactId > spring-boot-starter-tomcat</artifactId > <groupId > org.springframework.boot</groupId > </exclusion > </exclusions > </dependency > <dependency > <artifactId > spring-boot-starter-undertow</artifactId > <groupId > org.springframework.boot</groupId > </dependency >
4)、嵌入式Servlet容器自动配置原理 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容器工厂),主要是用来创建 EmbeddedServletContainer 也就是嵌入式的Servlet容器。这个是一个接口,也就是我们有对应的实现类,就是上面的三种嵌入式的容器。
1 2 3 4 5 6 7 public interface EmbeddedServletContainerFactory { EmbeddedServletContainer getEmbeddedServletContainer ( ServletContextInitializer... initializers) ;}
下面我们以 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); }
但是我们对嵌入式容器的配置修改是怎么生效?实际上是 ServerProperties、EmbeddedServletContainerCustomizer
:定制器帮我们修改了Servlet容器的配置,也就是我们在上面导入的 @Import(BeanPostProcessorsRegistrar.class)
然后导入了EmbeddedServletContainerCustomizerBeanPostProcessor(后置处理器):bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作。
容器中导入了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 37 @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; } ServerProperties也是定制器
步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的 EmbeddedServletContainerFactory 比如这里就是TomcatEmbeddedServletContainerFactory
2)、容器中某个组件要创建对象就会调用后置处理器:EmbeddedServletContainerCustomizerBeanPostProcessor。
只要是嵌入式的容器工厂,后置处理器就工作。
3)、后置处理器,从容器中获取所有的 EmbeddedServletContainerCustomizer ,调用定制器的定制方法,而我们的ServerProperties
就是我们要的定制器。
5)、嵌入式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方法,web 的 ioc容器会创建嵌入式的Servlet容器,createEmbeddedServletContainer()。
5)、获取嵌入式的Servlet容器工厂 createEmbeddedServletContainer() 这个方法的第一步就是去创建这个工厂,getEmbeddedServletContainerFactory。
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件,TomcatEmbeddedServletContainerFactory 创建对象,后置处理器就获取所有的定制器来先定制Servlet容器的相关配置。完成配置和启动。
6)、使用容器工厂获取嵌入式的Servlet容器 this.embeddedServletContainer = containerFactory.getEbeddedServletContainer(getSelfInitializer());
7)、嵌入式的Servlet容器创建对象并启动Servlet容器 先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来,这个时候我们自己写的Controller Service 以及各种配置类才会生效。
6)、使用外置的Servlet容器 我们采用嵌入式Servlet容器,这样我们的应用打成可执行的jar
优点:简单、便携
缺点:默认不支持JSP、优化定制比较复杂(使用定制器ServerProperties、自定义EmbeddedServletContainerCustomizer,自己编写嵌入式Servlet容器的创建工厂EmbeddedServletContainerFactory)
所以我们可以使用外置的Servlet容器:外面安装Tomcat—应用war包的方式打包;
1.步骤 1)、必须创建一个war项目,我们还是使用 SpringBootInitlizer 这个工具创建项目,但是注意我们在创建项目的时候我们选择部署的方式为 war 就可以生成 war的形式。
然后我们需要去项目的结构地方的web模块点击我们的目录结构让他自动生成webapp 目录。生成在 src/main 下面。然后就是生成 web 应用描述符,也就是生成 web.xml
接着我们需要修改应用的启动配置,自己添加一个tomcat的服务器。然后在 deploy 这个地方加入我们的 war包。
2)、将嵌入式的Tomcat指定为provided;
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency >
3)、必须编写一个SpringBootServletInitializer 的子类类名随意但是必须继承,并调用configure方法
1 2 3 4 5 6 7 8 9 public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure (SpringApplicationBuilder application) { return application.sources(SpringBoot04WebJspApplication.class); } }
4)、启动服务器就可以使用;
2.启动原理 1. 打包方式
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器。
war包:启动服务器,然后服务器启动SpringBoot应用,使用的是 SpringBootServletInitializer,启动ioc容器。
servlet3.0一个规范:
1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面 ServletContainerInitializer 实例
2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
3)、还可以使用 @HandlesTypes,在应用启动的时候加载我们感兴趣的类
2.启动流程: 1)、启动Tomcat 2)、SpringServletContainerInitializer 的调用 org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer 也就是我们应用启动的时候会创建这个对象。
3)、创建 @HandlesTypes 的对象 SpringServletContainerInitializer 将 @HandlesTypes(WebApplicationInitializer.class) 标注的所有这个类型的类都传入到 onStartup 方法的 Set<Class<?>>,为这些 WebApplicationInitializer 类型的类创建实例
4)、调用 onStartup 方法 每一个 WebApplicationInitializer 都调用自己的onStartup;
5)、SpringBootServletInitializer 的 onStartup 我们的 SpringBootServletInitializer 的类是继承自 WebApplicationInitializer 并且被放到了@HandlesTypes里,所以会被创建对象,并执行onStartup方法,也就是我们采用 war 包工程生成的这个类。
6)、创建 ICO 容器 SpringBootServletInitializer实例执行onStartup的时候会 createRootApplicationContext 创建容器
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 protected WebApplicationContext createRootApplicationContext ( ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); StandardServletEnvironment environment = new StandardServletEnvironment(); environment.initPropertySources(servletContext, null ); builder.environment(environment); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null ) { this .logger.info("Root context already created (using as parent)." ); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null ); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder = configure(builder); SpringApplication application = builder.build(); if (application.getSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null ) { application.getSources().add(getClass()); } Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation" ); if (this .registerErrorPageFilter) { application.getSources().add(ErrorPageFilterConfiguration.class); } return run(application); }
7)、Spring的应用就启动并且创建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 public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null ; FailureAnalyzers analyzers = null ; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null ); stopWatch.stop(); if (this .logStartupInfo) { new StartupInfoLogger(this .mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } }
先启动Servlet容器,然后容器根据我们的Servlet的3.0 的标准,去启动我们SpringBoot 下生成的一个类,这个类再启动SpringBoot应用。