SpringBoot的嵌入式servlet容器

1、SpringBoot默认的servlet容器

SpringBoot默认的servlet容器是tomcat,如下图所示:

在这里插入图片描述

2、嵌入式servlet配置修改

2.1、通过全局配置文件修改

1、可以通过 server.xxx 来进行 web 服务配置,没有带服务器名称的则是通用配置,如下所示:
2、通过带了具体的服务器名称则是单独对该服务器进行设置,比如 server.tomcat.xxx 就是专门针对 tomcat 的配置。

server.port=8081
server.servlet.context-path=/tomcat

2.2、通过WebServerFactoryCustomizer接口的Bean修改

我们创建一个类实现于WebServerFactoryCustomizer接口,在里面的配置会优先于全局配置的配置,且会互补,代码如下所示:

package cool.ale.component;

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(8090);
    }
}

3、注册servlet三大组件

首先我们需要知道servlet的三大组件都是什么
servlet的三大组件是:servlet、listener、filter
我们有两种方式进行注册

3.1、servlet3.0规范提供的注解方式注册

我们需要先创建一个类,导入这几个注解其中之一即可。

@WebServlet
@WebListener
@WebFilter

然后在启动类中加入可以扫描以上注解的注解@ServletComponentScan,如下所示:

package cool.ale;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class SpringbootServletApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootServletApplication.class, args);
    }

}

然后再完善一下刚才创建的类,如下所示,启动SpringBoot后,即可访问。

package cool.ale.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "HelloServlet",urlPatterns = "/HelloServlet")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello servlet!");
    }
}

3.2、SpringBoot提供的servlet三大组件注册

可以将 Servlet、Filter、Listener注册为相应的Spring Bean
可以使用以下三个Bean注册:ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean,将这三个类其中一个注册到配置文件中并返回即可。

先创建一个servlet类,如下所示:

package cool.ale.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class BeanServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello beanServlet!");
    }
}

然后创建一个配置类,返回上面ServletRegistrationBean类,并将我们刚才创建的servlet类配置进去,如下所示:

package cool.ale.config;

import cool.ale.servlet.BeanServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebMvcConfiguration {

    @Bean
    public ServletRegistrationBean myServlet(){
        // 声明一个 servlet 注册器 Bean
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
        // 设置相应的 servlet
        servletRegistrationBean.setServlet(new BeanServlet());
        // 设置名称
        servletRegistrationBean.setName("BeanServlet");
        // 添加映射规则
        servletRegistrationBean.addUrlMappings("/BeanServlet");
        return servletRegistrationBean;
    }

}

直接启动访问即可。

4、切换其它嵌入式servlet容器

SpringBoot包含对嵌入式Tomcat、Jetty和Undertow服务器的支持
tomcat(默认的嵌入式servlet容器)
Jetty(一般用于长链接,比如socket等)
Undertow(一般用于无阻塞式、响应式等)

一般我们需要切换成其它两个servlet容器时,需要操作以下两个步骤
1、排除掉tomcat服务器,在下面图中选中

在这里插入图片描述

然后代码中会对应如下所示:

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

2、在pom文件中加上jetty的依赖或者undertow的依赖,如下所示:

<!-- jetty的依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

<!-- undertow的依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

启动即可。

5、嵌入式servlet容器自动配置原理

我们在进行源码跟踪之前,先要提出几个问题,以方便我们寻求我们去跟踪源码的目的。
1、为什么可以根据配置的依赖自动使用对应的servlet容器。
2、怎么根据配置文件中server.xxx 以及 继承WebServerFactoryCustomizer接口时可以去设置servlet容器的一些参数。
3、怎么让所有的WebServerFactoryCustomizer Bean一一配合使用?
4、嵌入式的tomcat容器是如何启动的。
下来我们就根据这几个问题一起来进入servlet自动配置类来看看原理。
ServletWebServerFactoryAutoConfiguration。

先分析一下该类的注解

@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
// 只要依赖了任意一个servlet容器,都会依赖该类
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
// 启用了 server.xxx 的配置绑定到了当前类上,具体可分析ServerProperties.class
@EnableConfigurationProperties({ServerProperties.class})
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
}

5.1、为什么可以根据配置的依赖自动使用对应的servlet容器?

由于 ServletWebServerFactoryAutoConfiguration 类导入了三个类,如下所示:

@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
}

在上面导入的这几个类中,我们可以随意的点击一个进去,就比如,我点击EmbeddedTomcat.class进到实现,这块注解写着当有Tomcat.class类存在于IOC容器中,tomcat的配置将生效。

@Configuration(
        proxyBeanMethods = false
    )
    // 依赖于Servlet.class, Tomcat.class, UpgradeProtocol.class三个类
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedTomcat {
        EmbeddedTomcat() {
        }
}

5.2、怎么根据配置文件中server.xxx设置servlet容器的一些参数?

在ServletWebServerFactoryAutoConfiguration类中我们看到了如下方法,ServletWebServerFactoryCustomizer 类也是实现了WebServerFactoryCustomizer类,用于servlet容器的自动化配置,代码如下所示:

@Bean
// 通过WebServerFactoryCustomizer接口的Bean修改servlet配置
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
    return new ServletWebServerFactoryCustomizer(serverProperties, (List)webListenerRegistrars.orderedStream().collect(Collectors.toList()));
}

当我们点击 ServletWebServerFactoryCustomizer 类看到它里面的customize方法,是使用了jdk8的新特性的方法,从配置文件里面拿出相应的配置参数来修改默认值:

public void customize(ConfigurableServletWebServerFactory factory) {
    PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
    ServerProperties var10001 = this.serverProperties;
    var10001.getClass();
    map.from(var10001::getPort).to(factory::setPort);
    var10001 = this.serverProperties;
    var10001.getClass();
    map.from(var10001::getAddress).to(factory::setAddress);
    Servlet var5 = this.serverProperties.getServlet();
    var5.getClass();
    map.from(var5::getContextPath).to(factory::setContextPath);
    var5 = this.serverProperties.getServlet();
    var5.getClass();
    map.from(var5::getApplicationDisplayName).to(factory::setDisplayName);
    var5 = this.serverProperties.getServlet();
    var5.getClass();
    map.from(var5::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
    var5 = this.serverProperties.getServlet();
    var5.getClass();
    map.from(var5::getSession).to(factory::setSession);
    var10001 = this.serverProperties;
    var10001.getClass();
    map.from(var10001::getSsl).to(factory::setSsl);
    var5 = this.serverProperties.getServlet();
    var5.getClass();
    map.from(var5::getJsp).to(factory::setJsp);
    var10001 = this.serverProperties;
    var10001.getClass();
    map.from(var10001::getCompression).to(factory::setCompression);
    var10001 = this.serverProperties;
    var10001.getClass();
    map.from(var10001::getHttp2).to(factory::setHttp2);
    var10001 = this.serverProperties;
    var10001.getClass();
    map.from(var10001::getServerHeader).to(factory::setServerHeader);
    var5 = this.serverProperties.getServlet();
    var5.getClass();
    map.from(var5::getContextParameters).to(factory::setInitParameters);
    map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
    Iterator var3 = this.webListenerRegistrars.iterator();

    while(var3.hasNext()) {
        WebListenerRegistrar registrar = (WebListenerRegistrar)var3.next();
        registrar.register(factory);
    }

}

当然,类似于下面这个方法,和上面的结构一样,是用来配置tomcat的参数,代码如下,也可以点击进去看相应的 customize 方法

@Bean
@ConditionalOnClass(
    name = {"org.apache.catalina.startup.Tomcat"}
)
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
    return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}

5.3、怎么让所有的WebServerFactoryCustomizer Bean一一配合使用?

我们都知道,当我们创建了一个类实现了 WebServerFactoryCustomizer 接口之后,在这个类里面就可以去设置相应的servlet容器的参数配置,但是这些参数和全局配置是互补的,所以下面需要研究一下为什么是互补的,其实重点就在BeanPostProcessorsRegistrar类上,这个类就是我们的自动配置文件导入进来的第一个类,代码如下所示:

@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
}

当我们点击进去之后,我们发现 BeanPostProcessorsRegistrar.class 这个类实现了 ImportBeanDefinitionRegistrar 接口,这个接口会提供一个方法registerBeanDefinitions,并提供Bean注册器使我们去注册Bean,代码如下:

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    if (this.beanFactory != null) {
        this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class, WebServerFactoryCustomizerBeanPostProcessor::new);
        this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
    }
}

依照上面代码我们发现,注册了 WebServerFactoryCustomizerBeanPostProcessor.class 类,WebServerFactoryCustomizerBeanPostProcessor.class 类又实现了BeanPostProcessor,这个类在Bean初始化的时候提供了两个方法postProcessBeforeInitialization、postProcessAfterInitialization,我们看一下下面两个方法,就可以看出互补的地方:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
	// 每一个实现了 WebServerFactoryCustomizer 接口的类都会走到这里,所以是互补的
    if (bean instanceof WebServerFactory) {
    	// 当是 WebServerFactory 的子类的时候,调用 postProcessBeforeInitialization 方法
        this.postProcessBeforeInitialization((WebServerFactory)bean);
    }

    return bean;
}

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
}

进到 postProcessBeforeInitialization 方法去看一下,回调相应的customize方法,代码如下:

private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
	// 调用 getCustomizers() 方法,获取到所有实现 WebServerFactoryCustomizer 的Bean
    ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).
    // 这里的invoke() 方法就会拿到刚才获取到的所有实现 WebServerFactoryCustomizer 的Bean的数组去循环调用其对应的customize方法
    invoke((customizer) -> {
        customizer.customize(webServerFactory);
    });
}

private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
    if (this.customizers == null) {
        this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
        this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
        this.customizers = Collections.unmodifiableList(this.customizers);
    }

    return this.customizers;
}

5.4、嵌入式的tomcat容器是如何启动的?

在我们程序.run的时候,会去加载Spring Ioc,加载Ioc的时候就会去调用下面的方法去启动tomcat。

// TomcatServletWebServerFactory
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }

    Tomcat tomcat = new Tomcat();
    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    this.customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    this.configureEngine(tomcat.getEngine());
    Iterator var5 = this.additionalTomcatConnectors.iterator();

    while(var5.hasNext()) {
        Connector additionalConnector = (Connector)var5.next();
        tomcat.getService().addConnector(additionalConnector);
    }

    this.prepareContext(tomcat.getHost(), initializers);
    return this.getTomcatWebServer(tomcat);
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值