有了对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() {
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
1.2 getWebServer方法
通过getWebServerFactory方法返回ServletWebServerFactory 后,会去创建对应的服务。 以Tomcat为例
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);
}
prepareContext(tomcat.getHost(), initializers);
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())) {
removeServiceConnectors();
}
});
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 {
@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;
}
}
@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;
}
}
@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();
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前缀的配置项
|