文档章节

SpringBoot有啥高科技?是怎么做到XML零配置的?

王念博客
 王念博客
发布于 01/08 22:27
字数 2386
阅读 8.7K
收藏 21

前言:刚毕业我就接触到了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的生命周期发生变化会触发相应的事件

通过这个监听器进来的会通过createWebApplicationContext获取ConfigurableWebApplicationContext

具体代码:  ContextLoader ->configureAndRefreshWebApplicationContext方法

  protected void configureAndRefreshWebApplicationContext
        (ConfigurableWebApplicationContext wac, ServletContext sc) {
    //添加ServletContext
	wac.setServletContext(sc);
	String configLocationParam = sc.getInitParameter("contextConfigLocation");
   //添加Spring*.xml
	wac.setConfigLocation(configLocationParam);
    customizeContext(sc, wac);
	//读取配置加载,刷新Spring上下文
    wac.refresh();
  }

DispatcherServlet 用来接收SpringMVC所有请求的servlet程序,会注册到ServletContext中。

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/*");
    }
}

我们发现这个自定义类会实现WebApplicationInitializer接口

onStartup方法会拿到tomcat传过来的ServletContext(如果是jetty 就是jetty提供的   如果是tomcat 就是 tomcat提供的)

AnnotationConfigWebApplicationContext是继承了上面的ConfigurableWebApplicationContext ,用来实例化IOC容器,也就是做Spring上下文刷新的,

它不仅支持扫描@Configuration注解,任何@Compnent注解的类或者 按照JSR-330注解的类都被支持 。

AppConfig类上会加入@ComponentSan注解,扫描指定包下所有的业务层和控制层的bean

而我们之前的xml配置的DispatcherServlet是通过new DispatcherServlet()出来的

 

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方法的时候会把所有实现的类集合传给你。

具体代码:   SpringServletContainerInitializer->onStartup

@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方法

大家看到这里是不是就大概清楚SpringBoot是怎么才能做到零配置的。

 

3.SpringBoot具体是怎么配置的SpringMvc

在Spring代码中有一个DispatcherServletAutoConfiguration静态类,声明了一个DispatcherServlet的Bean

具体代码:DispatcherServletAutoConfiguration

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上下文

这样DispatcherServlet被作为一个普通Bean被实例化并注册到IOC容器中。

在调用刷新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(Servlet容器初始化器)所有实现类的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;
   }
   //拿到Tomcat传过来的ServletContext进行注册
   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/


 

© 著作权归作者所有

王念博客
粉丝 209
博文 122
码字总数 94978
作品 0
浦东
程序员
私信 提问
加载中

评论(1)

SpringBoot_总结_01_配置详解

一、入口类和@SpringBootApplication SpringBoot项目通常有一个名为*Application的入口类,入口方法为此类的main方法。 1. @SpringBootApplication @SpringBootApplication注解是一个组合注解...

shirayner
2018/07/24
0
0
springboot 开发入门,及问题汇总

1 . springboot简单介绍(http://projects.spring.io/spring-boot/) 现在的web项目几乎都会用到spring框架,而要使用spring难免需要配置大量的xml配置文件,而springboot的出现解 决了这一问...

崔江昆
2015/03/18
9.6W
30
微服务 SpringBoot 2.0(三):启动剖析之@SpringBootApplication

我原以为就一个注解,背后竟然还有3个 —— Java面试必修 引言 前面两章我们先后认识了SpringBoot和它的极简配置,为新手入门的学习降低了门槛,会基本的使用后,接下来我们将进一步认识Spr...

阿郎_
2018/09/24
0
0
SpringBoot+Mybatis集成搭建

本博客介绍一下SpringBoot集成Mybatis,数据库连接池使用alibaba的druid,使用SpringBoot微框架虽然集成Mybatis之后可以不使用xml的方式来写sql,但是用惯了xml的其实也可以用xml来实现的,实...

smileNicky
2019/02/10
0
0
springboot日志输出

大家好请教一个springboot日志控制的问题,springboot默认使用logbak,我也使用了他的这个,有几个疑问的地方 1.在yml文件中可以指定 logbac-boot.xml文件地址,如果在yml上配置了log文件输出...

樱木花道VS康
2018/05/30
866
0

没有更多内容

加载失败,请刷新页面

加载更多

如何从Joomla垃圾箱中删除文章

Joomla允许您删除文章,但是除非您采取其他步骤,否则它不会永久删除它们。 Joomla的垃圾桶类似于PC和Mac的垃圾桶。将项目发送到垃圾桶是可以撤消的操作。 在这个简短的教程中,我将向您展示...

六艺网络专注于Joomla
19分钟前
31
0
图解kubernetes命令执行核心实现

K8s中的命令执行由apiserver、kubelet、cri、docker等组件共同完成, 其中最复杂的就是协议切换以及各种流拷贝相关,让我们一起来看下关键实现,虽然代码比较多,但是不会开发应该也能看懂,祝你...

8小时
24分钟前
23
0
sh和bash之间的区别 - Difference between sh and bash

问题: When writing shell programs, we often use /bin/sh and /bin/bash . 在编写shell程序时,我们经常使用/bin/sh和/bin/bash 。 I usually use bash , but I don't know what's the d......

技术盛宴
36分钟前
39
0
spring - 使用profile来管理环境信息

程序一般都会有开发环境、测试环境以及线上环境,这些环境下程序运行依赖的基础一般不同,例如在有数据源访问的程序中,开发时可能使用了嵌入式数据库,而到测试环境上会使用独立的mysql,正...

閒散人員
39分钟前
18
0
如何实现项目流程自动化

多人协作复杂的任务,团队成员间的分工和沟通就非常必要。现在Zoho projects 项目管理软件中,配合使用蓝图功能,讲让工作事半功倍。 蓝图功能可以解决繁琐的邮件沟通问题,并使任务更加有序...

Zoho云服务
42分钟前
35
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部