SpringBoot有啥高科技?怎么支持SpringMvc的?
前言:刚毕业我就接触到了SpringBoot,当初感觉必成大器,第一印象就是内置了所有环境,打完包丢哪里都能跑起来,简化了tomcat Xml配置的一系列部署操作
1.SpringMvc XML配置
说到配置SpringMvc,大家第一时间反应就是xml配置,目前国内的各类博客或者各类老师都是套用这种方式,一直都是认为这种方式是唯一的方式,再说Spring官方一直支持。
1.1 配置web.xml
web.xml是servlet容器的配置文件,当启动一个WEB项目时,servlet容器首先会读取项目中的webapp/WEB-INFO文件夹的web.xml配置文件里的配置,主要用来配置监听器listener,servlet,上下文参数context-param。
<!-- 配置监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置DispatcherServlet --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <!-- ServletContext参数 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-config/*.xml</param-value> </context-param>
ContextLoaderListener(上下文加载监听) 继承了ServletContextListener(Servlet上下文监听),当ServletContext的生命周期发生变化会触发相应的事件
protected void configureAndRefreshWebApplicationContext (ConfigurableWebApplicationContext wac, ServletContext sc) { //添加ServletContext wac.setServletContext(sc); //添加Spring*.xml String configLocationParam = sc.getInitParameter("contextConfigLocation"); wac.setConfigLocation(configLocationParam); customizeContext(sc, wac); //读取配置加载,刷新Spring上下文 wac.refresh(); }
DispatcherServlet 用来接收SpringMVC所有请求的servlet程序,注册到Servlet容器中。
1.2配置applicationContext.xml
主要扫描业务类,AOP切面配置,事务配置,数据源配置等
<!--扫描包注解 不扫描@controller--> <context:component-scan base-package="com.wangnian"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
1.3配置springmvc.xml
主要扫描Controller,拦截器,视图转换等
<!--扫描包注解 只扫描@Controller--> <context:component-scan base-package="com.wangnian.controller" > <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan>
1.4 启动大概流程
在启动Servlet容器,会去读取web.xml配置文件注册Servlet并执行ServletContextListener的contextInitialized方法读取用户自定义的xml配置文件并创建bean,刷新Spring上下本。
2.SpringMvc 另外一种配置
2.1 怎么注册DispatcherServlet ?
猜想1:也是xml配置方式,Spring官网都把零xml的配置当成一种优势,那显然不是。
猜想2:@webservlet,我们找找DispatcherServlet这个类?居然没有@webservlet注解
那只能看看SpringMvc的文档,发现SpringMvc官方配置也推荐使用javaConfig的配置方式。
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { //SpringWeb注解配置应用程序上下本 AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); //注册一个或多个要处理的带注解的类 ac.register(AppConfig.class); //spring上下文刷新 ac.refresh(); //创建DispatcherServlet DispatcherServlet servlet = new DispatcherServlet(ac); //servlet中注册DispatcherServlet ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); } }
onStartup方法会得到ServletContext,如果是jetty 就是jetty提供的 如果是tomcat 就是 tomcat。
AnnotationConfigWebApplicationContext是继承了上面的ConfigurableWebApplicationContext ,实例化IOC容器
AnnotationConfigApplicationContext不仅支持@Configuration注解的类,任何@Compnent注解的类或者 按照JSR-330注解的类都被AnnotationConfigApplicationContext支持 。
DispatcherServlet是通过new DispatcherServlet()出来的
然后在AppConfig上加入@ComponentSan注解扫描业务层和控制层的bean
2.2 onStartup啥时候能调到?
上面讲到了web.xml 是在servlet容器启动的时候加载的
那webApplicationInitializer应该也要在servlet容器启动的时候被加载到
是不是tomcat也学Spring一样得到所有WebApplicationInitializer的实现,然后调用onStartup。
但是他绝对不会这么干,因为WebApplicationInitializer是Spring提供的,一个实现Servlet规范的容器不可能依赖Spring的jar包。
接下来我们来看看SpringMvc的启动核心科技
首先tomcat是一个Servlet容器,遵循并实现了Servlet的规范,tomcat7之后是3.0的,在3.0的有个新的特性
就是它 :ServletContainerInitializer(Servlet容器初始化器)
在web容器启动时为提供给第三方组件做一些初始化的工作,例如注册servlet或者listener等。
前提是必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类
一般伴随着ServletContainerInitializer一起使用的还有HandlesTypes注解,会在调用onStartup方法的时候会把所有实现的类集合传给你。
我们看看Spring的实现的 SpringServletContainerInitializer
@Override public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { //一个装WebApplicationInitializer实现的集合 List<WebApplicationInitializer> initializers = new LinkedList<>(); for (Class<?> waiClass : webAppInitializerClasses) { //通过反射拿到HandlesTypes注解指定的class的实现类 initializers.add(ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } //进行排序 AnnotationAwareOrderComparator.sort(initializers); //循环调用所有集合里的onStartup方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } }
这是spring惯用方法,将所有实现WebApplicationInitializer的实现类,遍历执行onStartup方法
3.SpringBoot怎么配置的SpringMvc
看完SpringMvc的javaConfig之后是不是就大概清楚SpringBoot是怎么才能做到零配置的。
3.1 SpringBoot是怎么创建DispatcherServlet ?
在DispatcherServletAutoConfiguration静态类中声明了一个bean DispatcherServlet
protected static class DispatcherServletConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails()); return dispatcherServlet; } }
SpringBoot main方法启动的时候就检查是否是web项目,怎么检查呢?
对的 大家猜的对,就是尝试forName加载一下初始化这个写死的类路径javax.servlet.Servlet,如果能实例化就代表是。
类:WebApplicationType
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET;
如果是Servlet类型就会使用AnnotationConfigServletWebServerApplicationContext去刷新spring上下文
/** * The class name of application context that will be used by default for non-web * environments. */ public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"; /** * The class name of application context that will be used by default for web * environments. */ public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
这样DispatcherServlet被作为一个普通Bean被实例化并注册到IOC容器中。
3.2 SpringBoot是怎么在ServletContext中注册DispatcherServlet ?
在DispatcherServletAutoConfiguration类代码下面还有一个DispatcherServletRegistrationConfiguration类
protected static class DispatcherServletRegistrationConfiguration { @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } }
它也会被作为一个普通Bean被实例化并注册到IOC容器中
SpringBoot启动Tomcat
AnnotationConfigServletWebServerApplicationContext在调用刷新spring上下文之后调用createWebServer方法
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
createWebServer()
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); //根据工厂模式调用对应的servlet实例,如果是tomcat就调用TomcatServletWebServerFactory的getWebServer() this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
创建tomcat实例并启动
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); 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); } //准备上下文,主要会提前放入他自己的ServletContextInitializer(Servlet上下文是初始化器) //实现类,供SpringBoot在ServletContainerInitializer的onStartup()里遍历调用自己的onStartup() //为了注册用户自定义的Filter和Servlet到ServletContext中 prepareContext(tomcat.getHost(), initializers); //启动tomcat return getTomcatWebServer(tomcat); }
具体代码:org.springframework.boot.web.embedded.tomcat.TomcatWebServer ->initialize();
private void initialize() throws WebServerException { // Start the server to trigger initialization listeners this.tomcat.start(); }
这时候按照servlet3.0的标准,Tomcat启动的时候会调用ServletContainerInitializer所有实现类的onStartup()方法
具体代码:TomcatStarter->onStartup();
@Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } }
具体代码:ServletRegistrationBean->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); }
具体代码:ServletRegistrationBean->addRegistration();
@Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); return servletContext.addServlet(name, this.servlet); }
这样 DispatcherServlet 就注册进去了。
这也为啥SpringBoot只支持Servlet3.0的容器,只不过赶上了3.0的好特性,才让我们开发者体验到非常友善的傻白甜的开发。
4.扩展
首先SpringBoot有两种部署方式 丢Tomcat和java -jar运行。
对于两种,它的启动的也不一样
4.1 SpringBoot内置的容器
首先Springboot并不是web应用,在你只引入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.1.6.RELEASE</version> <scope>compile</scope> </dependency>
它只不过是一个Spring的项目
那官方说的内置servlet容器,默认使用的tomcat是谁引进来的?
如果是web项目就必须得引入spring-boot-starter-web,而它依赖了spring-boot-starter-tomcat
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>9.0.21</version> <scope>compile</scope> <exclusions> <exclusion> <artifactId>tomcat-annotations-api</artifactId> <groupId>org.apache.tomcat</groupId> </exclusion> </exclusions> </dependency>
4.2丢war包的方式
配置很简单,只需要继承SpringBootServletInitializer,而SpringBootServletInitializer实现了WebApplicationInitializer接口
package com.example; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.web.SpringBootServletInitializer; public class SpringBootServletStart extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { //这里是@SpringBootApplication类 return application.sources(DemoApplication.class); } }
这种方式和SpringMvc的javaConfig一样方式,tomcat启动的时候去找WebApplicationInitializer的实现类
当执行到SpringBootServletInitializer的onStartup方法的时候,new SpringBootApplication.run()
4.3 java -jar运行
maven package打的jar是不能直接运行的。
为啥我们 maven package一下就可以,那是因为SpringBoot项目都有一个插件
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin>
mvn package spring-boot:repackage
所以SpringBoot 打完包在tagger里看到两个,一个是.jar.original 一个是.jar ,也就是说Maven首先在package阶段打包生成*.jar文件;然后执行spring-boot:repackage重新打包,会把项目运行的所有依赖的jar包都整合到一个单独的jar包中,并配置Manifest文件以及JarLauncher
https://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins.html#build-tool-plugins-maven-plugin
Manifest-Version: 1.0 Created-By: Maven Archiver 3.4.0 Build-Jdk-Spec: 13 Implementation-Title: demo Implementation-Version: 0.0.1-SNAPSHOT Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.example.demo.DemoApplication Spring-Boot-Version: 2.2.2.RELEASE Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
受”误解“的Java AIO
为什么说 AIO 受”误解“,虽然这个”误解“被打上了双引号,但还是不得不承认它的发展状况并不好。AIO 是 Java 7 开始提供的新特性,而这个”新特性“到如今都成了陈年老酒还鲜有人去品味它。要知道 Java 7 可是在 2011年7月份发布的,市面上基于 AIO 实现的通信框架竟然寥寥无几,关于这项技术的介绍文章也普遍比较粗略。通过阅读那些介绍 AIO 的文章,似乎从学术层面大家就不怎么待见这项技术。 作为 AIO 的学习者、受益者,我觉得有必要先对网上的一些 ”偏见“ 表达一下自己的观点。如果能有幸在认知上搭成共识,之后的学习交流会更加顺畅一点。通常偏见源于比较,AIO 与 BIO、NIO 的对比明细如表所示。 BIO NIO AIO 客户端 : I/O 线程数 1 : 1 N : 1 N : 0 I/O类型 同步阻塞 同步非阻塞 异步非阻塞 API使用难度 简单 复杂 一般 调试难度 简单 复杂 一般 可靠性 差 高 高 吞吐量 低 高 高 适用场景 适用于连接数量不多,并发量不高的场景。充分发挥易编程的优势。 适用于对连接数量以及稳定性、实时性有较高要求的场景,采用 NI...
- 下一篇
OPPO百万级高并发mongodb集群性能数十倍提升优化实践(下篇)
mongodb内核、wiredtiger存储引擎、rocksdb存储引擎相关源码分析详见(后续持续更新): https://github.com/y123456yz/reading-and-annotate-mongodb-3.6.1 前言 温馨提示:在进行本下篇优化文章阅读前,可以提前了解下《百万级高并发mongodb集群性能数十倍提升优化实践(上篇)》的问题背景及优化方法,这样可以更好的了解和学习本篇性能优化下篇的内容。 《百万级高并发mongodb集群性能数十倍提升优化实践(上篇)》地址:https://my.oschina.net/u/4087916/blog/3141909 2. 背景 线上某集群峰值TPS超过100万/秒左右(主要为写流量,读流量很低,读写流量做了主从读写分离,读流量走从节点,qps数百上千),峰值tps几乎已经到达集群上限,同时平均时延也超过100ms,随着读写流量的进一步增加,时延抖动严重影响业务可用性。该集群采用mongodb天然的分片模式架构,数据均衡的分布于各个分片中,添加片键启用分片功能后实现完美的负载均衡。集群每个节点流量监控如下图所示...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- 设置Eclipse缩进为4个空格,增强代码规范
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS7安装Docker,走上虚拟化容器引擎之路