IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot原理讲述 -> 正文阅读

[Java知识库]SpringBoot原理讲述

有了对springMVC的理解:SpringMVC原理讲述
1、Tomact启动
2、解析web.xml文件
3、DispatcherServlet实例化
4、DispatcherServlet对象.init方法,创建spring容器
5、接收请求,处理请求

springBoot中也使用了tomcat,只是和springMVC顺序不一样而已
用springBoot是先创建spring容器,再启动tomcat。

在springBoot中,比如说默认情况下使用的是tomcat,也可以使用Jetty、Undertow
只需要修改pom.xml文档中的依赖就可以。

一、springBoot是如何选择使用Tomcat还是Jetty

在springApplication的run方法中
在这里插入图片描述
在这里插入图片描述
创建完spring容器之后,会去启动对应的webServer
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里就会创建对应的服务(tomcat/jetty)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1 getWebServerFactory方法

protected ServletWebServerFactory getWebServerFactory() {
	// 获取spring容器里面ServletWebServerFactory类型的bean的名字(注意这里拿的不是bean对象) 
	//可能执行到这里的时候,对应的bean对象还没有创建出来,这里只是根据bean的定义来找到符合这个bean类型的所有名字
	String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
	//没有拿到ServletWebServerFactory类型的名字,报错
	if (beanNames.length == 0) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
					+ "ServletWebServerFactory bean.");
	}
	//拿到多个ServletWebServerFactory的名字,报错
	if (beanNames.length > 1) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
					+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
	}
	//getbean,真正把bean对象创建出来
	//基于性能的考虑,并不会在第一行代码的时候就把所有ServletWebServerFactory类型的bean都创建出来,只是拿到名字。
	return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

1.2 getWebServer方法

通过getWebServerFactory方法返回ServletWebServerFactory 后,会去创建对应的服务。
以Tomcat为例
在这里插入图片描述

public WebServer getWebServer(ServletContextInitializer... initializers) {
	if (this.disableMBeanRegistry) {
		Registry.disableRegistry();
	}
	/*new出tomcat对象*/
	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进行一些配置*/
	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);
	}
	prepareContext(tomcat.getHost(), initializers);
	/*创建tomcat*/
	return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
	return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
	Assert.notNull(tomcat, "Tomcat Server must not be null");
	this.tomcat = tomcat;
	this.autoStart = autoStart;
	this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
	/*初始化*/
	initialize();
}
private void initialize() throws WebServerException {
	synchronized (this.monitor) {
		try {
			addInstanceIdToEngineName();
			Context context = findContext();
			context.addLifecycleListener((event) -> {
				if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
					// Remove service connectors so that protocol binding doesn't
					// happen when the service is started.
					removeServiceConnectors();
				}
			});

			// 启动Tomcat
			this.tomcat.start();
			rethrowDeferredStartupExceptions();
			try {
				ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
			}catch (NamingException ex) {}
			startDaemonAwaitThread();
		}catch (Exception ex) {
			stopSilently();
			destroySilently();
			throw new WebServerException("Unable to start embedded Tomcat", ex);
		}
	}
}

1.3 ServletWebServerFactory类型的bean定义

代码看到这里,我们认为在默认的情况下,在使用springBoot时就应该有一个ServletWebServerFactory类型的bean,不然在getWebServerFactory方法中会报错。

依然以Tomcat为例,在springBoot源码中是否有某个地方定义了对应类型的bean呢?

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {

		/*TomcatServletWebServerFactory bean对象*/
		@Bean
		TomcatServletWebServerFactory tomcatServletWebServerFactory(
				ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
				ObjectProvider<TomcatContextCustomizer> contextCustomizers,
				ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
			factory.getTomcatConnectorCustomizers()
					.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatContextCustomizers()
					.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatProtocolHandlerCustomizers()
					.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}

	}

	/**
	 *ServletWebServerFactory bean对象
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedJetty {

		@Bean
		JettyServletWebServerFactory JettyServletWebServerFactory(
				ObjectProvider<JettyServerCustomizer> serverCustomizers) {
			JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
			factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}
	}

	/**
	 * UndertowServletWebServerFactory  bean对象
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedUndertow {

		@Bean
		UndertowServletWebServerFactory undertowServletWebServerFactory(
				ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
				ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
			UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
			factory.getDeploymentInfoCustomizers()
					.addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}

		@Bean
		UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(
				ServerProperties serverProperties) {
			return new UndertowServletWebServerFactoryCustomizer(serverProperties);
		}
	}
}

在ServletWebServerFactoryConfiguration 类中,3个类型的bean都定义了,不会报错么?

可以看到,每个类型的bean都是定义在EmbeddedXXX 这个内部类里面,每次只会解析一个内部类的内容。

怎么决定是否需要解析对应的内部类里面的内容呢?
首先需要判断对应的内部类是否符合条件(也就是ConditionalOnClass注解对应的内容
每个对应的条件都是不一样的。
Tomcat:@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })

jeety:@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })

Undertow:@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })

1.4 @ConditionalOnClass注解底层原理

到这里,可能会有个问题。
平时在使用springBoot的时候,在整个项目里面,可能没有jeety的依赖,也没有Undertow的依赖,执行到注解这里的时候不会报错么?

首先我们了解到的现状就是:虽然没有对应的jeety/Undertow依赖,springBoot也正常使用运行了,这是为什么呢?

报错分为两部分:编译报错和运行报错。
编译报错:指的是自己的项目编译,使用的springBoot.jar(jar包里面是class文件)是已经编译好的,在使用ServletWebServerFactoryConfiguration 这个类的时候就是个class,是不需要再次编译下这个类。

运行报错:运行的时候,先拿到对应的内部类,通过反射拿到类上面的注解,那就需要加载注解里面的class类,那这个时候就会报错?
可以先想下个问题:通常在spring中,我们都会指定一个扫描路径,这里路径下面会存在很多类,那么对应spring来说,扫描主要是做什么?
是不是去找扫描包下面的某个类是否是个bean?
那如何判断某个类是否是bean,是否有component注解呢?是需要把类加载到JVM中,在利用反射是否存在注解,如果包下面有100个类就需要把这100个类加载到JVM中,最后只找到了1个类是Spring的bean就把它创建出来,其余99个类都是陪跑的,什么都没做,但是已经被加载到JVM中了。
很显然,这并不符合JVM设计的规则,对于JVM来说,只有类在真正需要被使用的时候才去加载。如果spring真的是上述方式去扫描的,那就打破了JVM延时加载类的策略。
那spring中到底是怎么做的呢?其实在spring中使用了一种技术ASM。ASM是一种可以读取字节码,操作字节码的工具,字节码是有一定格式的,什么内容代表类,什么内容代表方法,什么内容表示属性,什么内容表示类上面的信息,相当于Util,ASM可以找到某个class文件,判断类名是什么,里面提供了什么方法,内置很多API可以调用。
那么就可以使用ASM来读取字节码,判断类上面是否存在某个注解。这个过程是不需要加载类的,是直接读取class文件内容的。

有了ASM这个工具,再回到springBoot选择ServletServer代码中,

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedUndertow {

@Bean
UndertowServletWebServerFactory undertowServletWebServerFactory(
	ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
	ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
		UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
		factory.getDeploymentInfoCustomizers()		.addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
		return factory;
	}

首先先把对应的类拿到,然后找到类上面的注解信息。拿到@ConditionalOnClass的信息(Servlet.class, Undertow.class, SslClientAuthMode.class),相当于读取字符串,然后在利用classLoad直接去加载

try{
applicationContext.getClassLoader.loadClass("Undertow.class")
}catch(ClassNotFoundException e){
	e.printStrackTrace();
	/*没有找到符合的类,可以返回false,代表无法不存在*/
	return false;
}

二、springBoot零配置

从上述描述中,可以看到,在ServletWebServerFactoryConfiguration 中,将tomcat、jeety、undertow都定义了,那么怎么做到默认tomcat呢?
需要默认使用tomcat需要保证,在spring容器中,有tomcat的依赖,没有jeety、undertow的依赖,这又是怎么做到的呢?
使用start机制,在我们使用springboot时,需要引入对应的依赖,
在这里插入图片描述
可以点击去看下,发现里面定义了默认tomcat
在这里插入图片描述
再点进去
在这里插入图片描述
里面就是tomcat相关的包了
在这里插入图片描述

2.1 替换默认启动服务

可以在外层定义默认启动内容。比如:

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
         <exclusions>
            <exclusion>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-starter-jeety</artifactId>
            </exclusion>
         </exclusions>
 </dependency>

2.2 替换tomcat默认启动端口号

springboot中默认启动的端口号是8080,那这个8080又是在哪里定义的呢?tomcat是8080,那么jeety、undertow默认端口号是多少呢?
其实默认都是8080。
在springBoot中不是说哪个ServletServer的端口号是8080,而是Servlet服务器默认端口号是8080.
可以看下具体类:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们来看下tomcat启动的具体类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,目前的8080是写死在代码中的。
平时我们自己项目的时候,都会去该端口号,比如:
在这里插入图片描述
这种是怎么生效的呢?其实使用的是bean的后置处理器(BeanPostProcessor,在创建bean的过程中,可以利用后置处理器做一些想针对bean做的操作)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
相当于会读取server前缀的配置项
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 18:38:23  更:2022-07-20 18:41:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:07:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码