宁波建站模板源码,深圳网站优化费用,网站新闻详细页面设计,遵义市住房和城乡建设局官方网站6tomcat 介绍
tomcat 是 web容器#xff08;servlet 容器#xff09;#xff0c;不管请求是访问静态资源HTML、JSP还是java接口#xff0c;对tomcat而言#xff0c;都是通过servlet访问#xff1a;
访问静态资源#xff0c;tomcat 会交由一个叫做DefaultServlet的类来处…tomcat 介绍
tomcat 是 web容器servlet 容器不管请求是访问静态资源HTML、JSP还是java接口对tomcat而言都是通过servlet访问
访问静态资源tomcat 会交由一个叫做DefaultServlet的类来处理。访问 JSPtomcat 会交由一个叫做JspServlet的类来处理。访问 Servlet tomcat 会交由一个叫做 InvokerServlet的类来处理。
所谓 jsp 就是 html 加上 java 代码片段JspServlet 最终输出的也是 html 而已。
tomcat 启动 spring 项目
了解 springboot 内嵌 tomcat 启动原理之前应该了解“祖先” spring 怎么启动和加载上下文的对 springboot 的理解才深刻。
web.xml配置
spring 启动必须依赖 web 容器这里以 tomcat 举例。
spring 项目中简单的 web 配置如下
?xml version1.0 encodingUTF-8?
web-app listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener context-param param-namecontextConfigLocation/param-name param-value/WEB-INF/root-context.xml/param-value /context-param servlet servlet-nameapp1/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-value/WEB-INF/app1-context.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-nameapp1/servlet-name url-pattern/app1/*/url-pattern /servlet-mapping
/web-appspring 怎么启动
tomcat 通过调用 spring 中的 servlet 对象DispatcherServlet然后调用该对象的 init() 方法作为入口启动spring。
如何调用到 spring 中的 servlet 对象分为以下两种
web.xml 配置文件方式tomcat 通过 web.xml 配置文件中 servlet 标签指定的 servlet 类路径反射生成 servlet 对象。java config 方式没有 web.xml 配置文件了项目中必须实现 WebApplicationInitializer 接口并现实 WebApplicationInitializer 的 onStartup 方法在 onStartup 方法中创建 dispacherServlet 并指定使用。
上述 java config 方式衍生出另一个问题WebApplicationInitializer 的实现类怎么被 tomcat 调用到呢
HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {Overridepublic void onStartup(Nullable SetClass? webAppInitializerClasses, ServletContext servletContext)throws ServletException {......}
}tomcat 会通过SPI机制找到 ServletContainerInitializer 接口的所有实现类然后反射生成对象并轮流执行实现类中 onStartup 方法。SpringServletContainerInitializer 就是 ServletContainerInitializer 实现类之一。SpringServletContainerInitializer 类上有HandlesTypes({WebApplicationInitializer.class})注释这个注释是 servlet 规范中的tomcat 会通过字节码加载技术ASM找到注解中指定类及子类WebApplicationInitializer.class 及子类然后将其作为参数传给 SpringServletContainerInitializer 的 onStartup方法使用。我们自定义的WebApplicationInitializer 实现类就这样被调用到了 。
所以我们实现了 WebApplicationInitializer 接口就会被 tomcat 加载。
怎么加载 springContext?
tomcat 通过调用 DispatcherServlet 对象的 init() 方法加载 springContext 上下文所以 DispatcherServlet 对象内有一个字段存放 springContext。
若配置多个 servletspringContext 有几个
每个 servlet 创建过程中都会创建 springcontext各 servlet 都有自己的上下文。所以各 servlet 的 init-param 标签中指定的扫描路径如果有重复的重复的 bean 对象也会在各 servlet 的 springcontext 中新建不会共用。那么重复的 bean 对象是不是有点浪费内存呢确实如此所以在多个servlet的情况下需要配 listener 标签表示配置父容器root context。可以合理配置父容器加载 service 和 repository 层的 bean这部分可以共用而 controller 层的 bean 是在各 servlet 中的子容器中加载因为涉及到servlet路由嘛。
不过一般都不会有多个servlet通常常规项目中 web.xml 中 listener 标签根本就不需要配置。
springboot 启动 tomcat
启动 web 容器
上述说的 spring 启动必须依赖 web 容器启动。由 web 容器通过 SPI 机制加载 spring 自己的 servlet DispatcherServlet再通过 servlet 对象创建 springContext。
而 springboot 不需要外部 web 容器了那它怎么监听端口接收请求呢难道 springboot 内部又重新写了一个 servlet当然不是看下面代码
Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 同时会创建 web 容器。onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}......}......
}上述代码片很熟悉吧如果不熟的查查spring初始化context流程这里不做详细描述。 主要看 onRefresh() 方法该方法启动容器的流程大概如下以 tomcat 为例
onRefresh() 方法内会调用 createWebServer() 方法createWebServer() 方法内会调用 tomcat||jetty||undertow jar 包依赖提供的方法来创建 web 容器并启动。比如 tomcat它是这样创建new Tomcat()。创建容器后对 tomcat 容器的Connector进行配置并将 DispatcherServlet 添加到 tomcat 容器中。最后 tomcat.start() 启动容器。
tomcat\jetty\undertow springboot用哪个
我们了解到了 springboot 会调用 createWebServer() 方法创建“合适”的 web 容器。
那 springboot 怎么判断该创建 tomcat、jetty 还是 undertow 容器呢
实际上就是在 createWebServer() 方法里面判断的该方法代码如下
private void createWebServer() {WebServer webServer this.webServer;ServletContext servletContext getServletContext();if (webServer null servletContext null) {// 选择用哪个容器ServletWebServerFactory factory getWebServerFactory();// 创建及启动 web 容器this.webServer factory.getWebServer(getSelfInitializer());getBeanFactory().registerSingleton(webServerGracefulShutdown,new WebServerGracefulShutdownLifecycle(this.webServer));getBeanFactory().registerSingleton(webServerStartStop,new WebServerStartStopLifecycle(this, this.webServer));}else if (servletContext ! null) {try {getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException(Cannot initialize servlet context, ex);}}initPropertySources();
}上述代码片getWebServerFactory() 方法会获得 tomcat||jetty||undertow 的 ServletWebServerFactory 用这个 factory 对象就能创建对应的 web 容器。
而在 getWebServerFactory() 方法内是按类型从 SpringContext 中获取 ServletWebServerFactory 类型的 bean。
所以只要 SpringContext 注入什么容器的 ServletWebServerFactoryspringboot 就会启动什么容器。
什么地方注入 ServletWebServerFactory 呢
在 ServletWebServerFactoryConfiguration 这个配置类将tomcat||jetty||undertow的 ServletWebServerFactory 注入进 springContext配置类伪代码如下
Configuration(proxyBeanMethods false)
class ServletWebServerFactoryConfiguration { // tomcat 的 factoryConfiguration(proxyBeanMethods false)ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })ConditionalOnMissingBean(value ServletWebServerFactory.class, search SearchStrategy.CURRENT)static class EmbeddedTomcat {BeanTomcatServletWebServerFactory tomcatServletWebServerFactory( ... ) { TomcatServletWebServerFactory factory new TomcatServletWebServerFactory();... return factory;}}// Jetty 的 factoryConfiguration(proxyBeanMethods false)ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })ConditionalOnMissingBean(value ServletWebServerFactory.class, search SearchStrategy.CURRENT)static class EmbeddedJetty {BeanJettyServletWebServerFactory JettyServletWebServerFactory( ... ) { JettyServletWebServerFactory factory new JettyServletWebServerFactory();... return factory;}}// Undertow 的 factory.......
}可以看出只要引入了某 web 容器的依赖对应的 ConditionalOnClass 就能满足该 web 容器的 ServletWebServerFactory 就会被注入进 springboot。
那项目如果引入了多个 web 容器依赖springboot 使用哪一个
还想使用哪个getWebServerFactory() 方法内就直接报错了Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : xxx。
我们平时使用 tomcat 那 jetty、undertow 等容器依赖便不会引入。那上述 ServletWebServerFactoryConfiguration 类的代码ConditionalOnClass 中某些类肯定找不到运行时不会报错吗
首先应该知道 spring 怎么判断是否是 bean 对象常人理解 spring 是通过 JVM 反射获取类注解信息来确定是否反射生成 bean 对象注入 SpringContext 中。
但实际 spring 并不是这样判断的如果通过 JVM 获取类信息那不是启动前要把所有类都加载一次这和 JVM 用时加载的思想冲突了。所以spring 是通过 ASM 技术从 class 字节码文件中获取注解信息来判断是否是需要的 bean。
之所以没有依赖也不会报错是因为spring 会通过 ASM 技术取出 ConditionalOnClass 注解中所有的 values后会用 ClassLoader 尝试加载这些 values如果加载不到catch住异常使其不会报错同时这个类也被认为不符合注入条件不会生成对象注入 springcontext。所以没引入所有 web 容器依赖也不会报错。