1、配置嵌入式Servlet容器 SpringBoot默认使用Tomcat作为嵌入式的Servlet容器;
问题?
1.1 如何定制和修改Servlet容器的相关配置;
修改和server有关的配置(ServerProperties{也是EmbeddedServletContainerCustomizer});
1 2 3 4 5 6 7 8 9 server.port =8088 server.context-path =/ server.tomcat.uri-encoding =UTF-8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 server.tomcat.xxx
编写一个EmbeddedServletContainerCustomizer
:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
1 2 3 4 5 6 7 8 9 10 11 12 @Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer () { return new EmbeddedServletContainerCustomizer() { @Override public void customize (ConfigurableEmbeddedServletContainer container) { container.setPort(8083 ); } }; }
1.2 注册Servlet三大组件(Servlet、Filter、Listener) 由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册三大组件用以下方式
1 2 3 4 5 6 7 8 9 10 11 @Bean public ServletRegistrationBean myServlet () { ServletRegistrationBean registrationBean = new ServletRegistrationBean( new CustomServlet(), "/myServlet" ); return registrationBean; }
1 2 3 4 5 6 7 @Bean public FilterRegistrationBean myFilter () { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new CustomFilter()); registrationBean.setUrlPatterns(Arrays.asList("/hello" ,"/myServlet" )); return registrationBean; }
ServletListenerRegistrationBean
1 2 3 4 5 6 @Bean public ServletListenerRegistrationBean myListener () { ServletListenerRegistrationBean<CustomListener> registrationBean = new ServletListenerRegistrationBean<>(new CustomListener()); return registrationBean; }
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
DispatcherServletAutoConfiguration中:
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 @Bean ( name = {"dispatcherServletRegistration" } ) @ConditionalOnBean ( value = {DispatcherServlet.class }, name = {"dispatcherServlet" }) public ServletRegistrationBean dispatcherServletRegistration (DispatcherServlet dispatcherServlet) { ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet, new String[]{this .serverProperties.getServletMapping()} ); registration.setName("dispatcherServlet" ); registration.setLoadOnStartup(this .webMvcProperties.getServlet().getLoadOnStartup()); if (this .multipartConfig != null ) { registration.setMultipartConfig(this .multipartConfig); } return registration; }
2、SpringBoot能不能支持其他的Servlet容器? 在之前写定制嵌入式的Servlet容器相关的规则配置类的时候
在ServletConfig.class
中用的ConfigurableEmbeddedServletContainer
声明的变量,选中,点击Ctrl+h,会在右侧显示它的继承树:
可配置的嵌入式容器工程
3、替换为其他嵌入式Servlet容器 默认支持:
3.1 Tomcat(默认使用) 1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
3.2 Jetty(长链接,例如聊天) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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.3 Undertow(不支持JSP) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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 >
3.4 嵌入式Servlet容器自动配置原理 在自动配置包里的EmbeddedServletContainerAutoConfiguration.class
:嵌入式的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 52 53 54 55 56 57 58 59 60 @AutoConfigureOrder (-2147483648 )@Configuration @ConditionalOnWebApplication @Import ({EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class }) // 导入BeanPostProcessorsRegistrar ; 给容器中导入一些组件public class EmbeddedServletContainerAutoConfiguration { ... @Configuration @ConditionalOnClass ({Servlet.class , Undertow .class , SslClientAuthMode .class }) @ConditionalOnMissingBean ( value = {EmbeddedServletContainerFactory.class }, search = SearchStrategy.CURRENT ) public static class EmbeddedUndertow { public EmbeddedUndertow () { } @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory () { return new UndertowEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass ({Servlet.class , Server .class , Loader .class , WebAppContext .class }) @ConditionalOnMissingBean ( value = {EmbeddedServletContainerFactory.class }, search = SearchStrategy.CURRENT ) public static class EmbeddedJetty { public EmbeddedJetty () { } @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory () { return new JettyEmbeddedServletContainerFactory(); } } @Configuration @ConditionalOnClass ({Servlet.class , Tomcat .class }) // 判断当前容器没有用户自己定义EmbeddedServletContainerFactory: 嵌入式的Servlet容器工厂 @ConditionalOnMissingBean ( value = {EmbeddedServletContainerFactory.class }, search = SearchStrategy.CURRENT ) public static class EmbeddedTomcat { public EmbeddedTomcat () { } @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory () { return new TomcatEmbeddedServletContainerFactory(); } } }
EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)
1 2 3 4 public interface EmbeddedServletContainerFactory { EmbeddedServletContainer getEmbeddedServletContainer (ServletContextInitializer... var1) ; }
EmbeddedServletContainer(嵌入式的Servlet容器)
以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 25 26 public EmbeddedServletContainer getEmbeddedServletContainer ( ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = this .baseDirectory != null ? this .baseDirectory : this .createTempDir("tomcat" ); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this .protocol); tomcat.getService().addConnector(connector); this .customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false ); this .configureEngine(tomcat.getEngine()); Iterator var5 = this .additionalTomcatConnectors.iterator(); while (var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } this .prepareContext(tomcat.getHost(), initializers); return this .getTomcatEmbeddedServletContainer(tomcat); }
或
1 2 EmbeddedServletContainerCustomizer()
EmbeddedServletContainerCustomizer :定制器帮我们修改了Servlet容器的配置?
怎么修改的原理?
容器中导入了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 38 39 40 41 public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { if (bean instanceof ConfigurableEmbeddedServletContainer) { this .postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer)bean); } return bean; } ... private void postProcessBeforeInitialization (ConfigurableEmbeddedServletContainer bean) { Iterator var2 = this .getCustomizers().iterator(); while (var2.hasNext()) { EmbeddedServletContainerCustomizer customizer = (EmbeddedServletContainerCustomizer)var2.next(); customizer.customize(bean); } private Collection<EmbeddedServletContainerCustomizer> getCustomizers () { if (this .customizers == null ) { this .customizers = new ArrayList( this .beanFactory.getBeansOfType( EmbeddedServletContainerCustomizer.class , false , false ).values () ) ; Collections.sort(this .customizers, AnnotationAwareOrderComparator.INSTANCE); this .customizers = Collections.unmodifiableList(this .customizers); } return this .customizers; } }
ServerProperties也是定制器,步骤:
SpringBoot根据导入的依赖情况,给容器中添加相应的嵌入式容器工厂,EmbeddedServletContainerFactory[TomcatEmbeddedServletContainerFactory]
容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;只要是嵌入式的Servlet容器工厂,后置处理器就工作;
后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer ,调用定制器的定制方法
4、嵌入式Servlet容器启动原理 什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat?
获取嵌入式的Servlet容器工厂:
SpringBoot应用启动运行run方法
refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext ,否则:AnnotationConfigApplicationContext
refresh(context),刷新刚才创建好的ioc容器; 查看AbstractApplicationContext.class
源码:
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 public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { this .prepareRefresh(); ConfigurableListableBeanFactory beanFactory = this .obtainFreshBeanFactory(); this .prepareBeanFactory(beanFactory); try { this .postProcessBeanFactory(beanFactory); this .invokeBeanFactoryPostProcessors(beanFactory); this .registerBeanPostProcessors(beanFactory); this .initMessageSource(); this .initApplicationEventMulticaster(); this .onRefresh(); this .registerListeners(); this .finishBeanFactoryInitialization(beanFactory); this .finishRefresh(); } catch (BeansException var9) { if (this .logger.isWarnEnabled()) { this .logger.warn( "Exception encountered during context initialization - cancelling refresh attempt: " + var9 ); } this .destroyBeans(); this .cancelRefresh(var9); throw var9; } finally { this .resetCommonCaches(); } } }
1 EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory 创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
1 2 this .embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
嵌入式的Servlet容器创建对象并启动Servlet容器;
先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;
IOC容器启动创建嵌入式的Servlet容器
5、使用外置的Servlet容器 嵌入式Servlet容器:应用打成可执行的jar
优点:简单、便携;
缺点:默认不支持JSP、优化定制比较复杂(使用定制器{ServerProperties、自定义EmbeddedServletContainerCustomizer},自己编写嵌入式Servlet容器的创建工厂{EmbeddedServletContainerFactory});
外置的Servlet容器:外面安装Tomcat—-应用war包的方式打包;
5.1 创建JSPweb项目教程:
生成webapp文件夹(可手动创建,也可用以下方法):
点击编译器右上的Project Structure按钮—>Modules—>项目展开—>Web—>Web Resource Directories
双击Web Resource Directory下的路径,弹出对话框:
点击OK,然后在弹出对话框点Yes即可;
将默认的路径...\WEB-INF\web.xml
改为...\src\main\webapp\WEB-INF\web.xml
,点击OK即可,最后Apply—>OK
如何启动?
编译器上方Edit Configurations…
添加本地Tomcat配置即可:
配置完后添加部署项目:
出现对话框,选择第二个暴露的就行
最后Apply—>OK。运行Tomcat即可
5.2 步骤
必须创建一个war项目;(利用IDEA创建好目录结构)
将嵌入式的Tomcat指定为provided;
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency >
必须编写一个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(SpringBootWebJspApplication.class ) ; } }
5.3 原理
servlet3.0(规范文档)
5.4 查看8.2.4 Shared libraries / runtimes pluggability
条内容: 规则:
服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例;
ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类
5.5 流程
启动Tomcat
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
SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)
标注的所有这个类型的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例;
每一个WebApplicationInitializer都调用自己的onStartup;
相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
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 39 40 41 42 43 44 45 46 47 48 49 50 51 protected WebApplicationContext createRootApplicationContext (ServletContext servletContext) { SpringApplicationBuilder builder = this .createSpringApplicationBuilder(); StandardServletEnvironment environment = new StandardServletEnvironment(); environment.initPropertySources(servletContext, (ServletConfig)null ); builder.environment(environment); builder.main(this .getClass()); ApplicationContext parent = this .getExistingRootWebApplicationContext(servletContext); if (parent != null ) { this .logger.info("Root context already created (using as parent)." ); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null ); builder.initializers( new ApplicationContextInitializer[]{ new ParentContextApplicationContextInitializer(parent) } ); } builder.initializers( new ApplicationContextInitializer[]{ new ServletContextApplicationContextInitializer(servletContext) } ); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class ) ; builder = this .configure(builder); SpringApplication application = builder.build(); if (application.getSources().isEmpty() && AnnotationUtils.findAnnotation(this .getClass(), Configuration.class ) ! = null ) { application.getSources().add(this .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 this .run(application); }
查看上面run方法源码:
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 public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null ; FailureAnalyzers analyzers = null ; this .configureHeadlessProperty(); SpringApplicationRunListeners listeners = this .getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this .prepareEnvironment(listeners, applicationArguments); Banner printedBanner = this .printBanner(environment); context = this .createApplicationContext(); new FailureAnalyzers(context); this .prepareContext(context, environment, listeners, applicationArguments, printedBanner); this .refreshContext(context); this .afterRefresh(context, applicationArguments); listeners.finished(context, (Throwable)null ); stopWatch.stop(); if (this .logStartupInfo) { (new StartupInfoLogger(this .mainApplicationClass)).logStarted( this .getApplicationLog(), stopWatch ); } return context; } catch (Throwable var9) { this .handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9); throw new IllegalStateException(var9); } }
启动Servlet容器,再启动SpringBoot应用