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/

 

以上是 SpringBoot有啥高科技?怎么支持SpringMvc的? 的全部内容, 来源链接: utcz.com/z/512520.html

回到顶部