SpringBoot学习05-[SpringBoot的嵌入式Servlet容器]

嵌入式Servlet容器

SpringBoot包含对嵌入式Tomcat、Jetty、Undertow等服务器的支持。大多数开发人员使用适当的“”启动器“” 来获取完全配置的实例。默认情况下,嵌入式服务器在port上监听HTTP请求8080

servlet容器-嵌入式servlet容器配置修改

在这里插入图片描述

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

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

添加实现了WebServerFactoryCustomizer接口的bean来进行修改

这儿的泛型添加的tomcat的ConfigurableTomcatWebServerFactory

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory> {
    @Override
    public void customize(ConfigurableTomcatWebServerFactory server) {
        server.setPort(8088);
    }
}

在这里插入图片描述

servlet容器-注册servlet三大组件

  • servlet三大组件
  • servlet
  • 监听器:listenser
  • 过滤器:filter

应该如何注册呢?

  • servlet3.0规范提供的注解方式进行注册
  • springboot提供的注册方式
servlet3.0规范提供的注解方式进行注册

@WebServlet:注册servlet的注解
@WebListener:注册监听器的注解
@WebFilter:注册过滤器的注解

  • 以webservlet进行演示
  • 定义一个servlet
@WebServlet(name = "HelloServlet",urlPatterns = "/HelloServlet")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("hello servlet");
    }
}
  • 在springboot启动类上加上@ServletComponentScan,让springboot可以扫描到serverlet的bean
@SpringBootApplication
@ServletComponentScan
public class MyApplication {
    public static void main(String[] args) {
       SpringApplication.run(MyApplication.class,args);
    }
}
springboot提供的注册方式
  • 自定义一个servlet
public class BeanServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("bean servlet");
    }
}
  • 定义一个bean注册的配置类
    可以使用ServletRegistrationBean、ServletListenerRegistrationBean、FilterRegistrationBean分别来管理servlet、监听器、过滤器
@Configuration
public class RegistryBeanConfigration {

    @Bean
    public ServletRegistrationBean myServlet(){
        ServletRegistrationBean<Servlet> registrationBean = new ServletRegistrationBean<>();
        //设置相应的servlet
        registrationBean.setServlet(new BeanServlet());
        //设置名称
        registrationBean.setName("beanServlet");
        //添加映射规则
        registrationBean.addUrlMappings("/beanServlet");
        return registrationBean;
    }
}

  • 测试
    在这里插入图片描述

servlet容器-切换到其他servlet容器

srpingboot默认服务器是tomcat服务器
在这里插入图片描述

  • 排除内嵌tomcat
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--排除内嵌tomcat-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
  • 引入Jetty依赖
    <!--引入jetty依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
  • 启动项目
    在这里插入图片描述
  • 测试
    在这里插入图片描述

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

内嵌servelet自动配置类:ServletWebServerFactoryAutoConfiguration

  • 问题:
  • 为什么可以根据配置依赖自动使用servlet容器?
  • 怎么根据配置文件中的server.xxx以及实现了WebServerFactoryCustomizer的类去设置serverlet容器配置?
  • 嵌入式servlet容器如何启动?

ServletWebServerFactoryAutoConfiguration配置类源码分析

//启用配置文件的属性类,那么所有的配置信息都会绑定到ServerProperties.class
@EnableConfigurationProperties(ServerProperties.class)

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
//只要依赖了任何一个servlet容器,它就会生效
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
//启用配置文件的属性类,那么所有的配置信息都会绑定到ServerProperties.class
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
为什么可以根据配置依赖自动使用servlet容器?
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
  • 通过@imort导入Embeddel类
  • 每个Embeddel类 中,都配置了相应的@ConditionalOnClass,会根据当前servlet容器启动器依赖判断classpath是否存在对应的类,如果存在就使用对应的sevlet容器,比如tomcat:
@Configuration(proxyBeanMethods = false)
  //只要添加了tomcat的场景启动器 则该注解才会匹配,如果没有对应的tomcat场景启动器,该注解就不会匹配
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {
怎么根据配置文件中的server.xxx以及实现了WebServerFactoryCustomizer的类去设置serverlet容器配置?
  • ServletWebServerFactoryAutoConfiguration类
	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
			ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
			ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
		return new ServletWebServerFactoryCustomizer(serverProperties,
				webListenerRegistrars.orderedStream().collect(Collectors.toList()),
				cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
	}

在这里插入图片描述

ServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer,说明它也是定制servlet容器的

  • ServletWebServerFactoryCustomizer类
    根据配置文件中server.xxx来进行定制servlet容器
	@Override
	public void customize(ConfigurableServletWebServerFactory factory) {
		PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
		map.from(this.serverProperties::getPort).to(factory::setPort);
		map.from(this.serverProperties::getAddress).to(factory::setAddress);
		map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
		map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
		map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
		map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
		map.from(this.serverProperties::getSsl).to(factory::setSsl);
		map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
		map.from(this.serverProperties::getCompression).to(factory::setCompression);
		map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
		map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
		map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
		map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
		for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
			registrar.register(factory);
		}
		if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
			factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
		}
	}
  • 实现了WebServerFactoryCustomizer接口的类如何进行配置呢?
	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}

在这里插入图片描述
TomcatServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer,说明它也是定制servlet容器的

  • TomcatServletWebServerFactoryCustomizer也重写了customize方法
	@Override
	public void customize(TomcatServletWebServerFactory factory) {
		ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
		if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
			factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
		}
		if (tomcatProperties.getRedirectContextRoot() != null) {
			customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
		}
		customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
		factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
	}
  • 怎么让所有的WebServerFactoryCustomizer bean一一调用呢?
  • BeanPostProcessorsRegistrar
    在这里插入图片描述
  • 注册WebServerFactoryCustomizerBeanPostProcessor为bean
	@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
			  //注册了WebServerFactoryCustomizerBeanPostProcessor bean
					WebServerFactoryCustomizerBeanPostProcessor.class,
					WebServerFactoryCustomizerBeanPostProcessor::new);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
		}

  • WebServerFactoryCustomizerBeanPostProcessor
    实现了BeanPostProcessor,在spring初始化bean的时候会被调用
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      // 判断当前创建的bean是不是webserverfactofy
        if (bean instanceof WebServerFactory) {
            this.postProcessBeforeInitialization((WebServerFactory)bean);
        }

        return bean;
    }

当前对应的Embeddedxxx 启用时,就会在里面配置一个WebServerFactory类型的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;
		}

spring bean初始化前就会调用postProcessBeforeInitialization方法从而执行customize方法

  • 调用getCustomizers()方法
  • 调用getWebServerFactoryCustomizerBeans()方法,获取所有实现了WebServerFactoryCustomizer接口的bean(获取自定义的、ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer)
  • 在invoke方法中循环调用所有实现了WebServerFactoryCustomizer接口 的bean,并调用customize()方法进行一一定制
   private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        ((LambdaSafe.Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).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;
    }

    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
        return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
    }
嵌入式servlet容器如何启动?
  • TomcatServletWebServerFactory
  • 自动配置根据不同的依赖,启动对应一个Enbenddedxxx,然后配置一个对应的servlet工厂类,比如TomcatServletWebServerFactory
  • 在springboot应用启动的时候,就会调用refresh方法,onRefresh方法,调用getWebServer,创建servlet容器并且启动

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());
        Iterator var4 = this.serverLifecycleListeners.iterator();

        while(var4.hasNext()) {
            LifecycleListener listener = (LifecycleListener)var4.next();
            tomcat.getServer().addLifecycleListener(listener);
        }

        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 var8 = this.additionalTomcatConnectors.iterator();

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

        this.prepareContext(tomcat.getHost(), initializers);
        //启动方法在getTomcatWebServer
        return this.getTomcatWebServer(tomcat);
    }
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
    }

  • TomcatWebServer类
    在TomcatWebServer中调用initialize方法进行启动
    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
        synchronized(this.monitor) {
            try {
                this.addInstanceIdToEngineName();
                Context context = this.findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                        this.removeServiceConnectors();
                    }

                });
                this.tomcat.start();
                this.rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                } catch (NamingException var5) {
                }

                this.startDaemonAwaitThread();
            } catch (Exception var6) {
                this.stopSilently();
                this.destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", var6);
            }

        }
    }

servlet容器-使用外部servlet容器

  • 外部servlet容器
    • 服务器 安装tomcat 配置环境变量
    • 部署: war包—>运维—>tomcat webapp startup.sh 启动
    • 开发:将开发绑定本地tomcat
  • 内嵌servlet容器
    • 部署:jar—>运维—java -jar 启动

SpringBoot使用外部servlet

  • 修改pom.xml的打包方式,修改为war包形式
    在这里插入图片描述
    设置tomcat依赖设置scope为provided,让内嵌tomcat不参与打包
     <!--让它不参与打包部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
<type>pom</type> //用在父子类模块中,一般使用在父模块中,此时它相当于一个一个依赖管理(Maven Parent POM)文件
<optional>true</optional> //用在父模块中,此时子模块不会继承父模块的该依赖,true:不传递,false:传递
<scope>provided</scope> //让依赖不参与打包
  • 设置tomcat的启动类
    目的是tomcat启动的时候调用configure方法来启动springboot的启动类
/**
 * 当tomcat启动时就会调用configure方法,去启动springboot的启动类
 */
public class TomcatStartSpringBoot extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(MyApplication.class);
    }
}
  • 添加本地tomcat
  • 测试-使用本地tomcat启动
    在这里插入图片描述

在这里插入图片描述

servlet容器-外部servlet启动SpringBoot原理

tomcat -->web.xml—filter servlet listener
tomcat不会主动去启动springboot应用,所以tomcat启动的时候肯定调用了SpringBootServletInitializer 的configure方法

public class TomcatStartSpringBoot extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(MyApplication.class);
    }
}

启动原理

在这里插入图片描述
当servlet容器启动的时候,就会去META-INF/service/文件下去找javax.servlet.ServletContainerInitializer文件
在这里插入图片描述
这个文件肯定绑定了一个ServletContainerInitializer

org.springframework.web.SpringServletContainerInitializer

当servlet容器启动的时候就会去该文件夹中找到ServletContainerInitializer的实现类SpringServletContainerInitializer从而创建它实例(SPI机制),并且调用onStartup方法

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = Collections.emptyList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            initializers = new ArrayList(webAppInitializerClasses.size());
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        ((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (((List)initializers).isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort((List)initializers);
            var4 = ((List)initializers).iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}
  • @HandlesTypes({WebApplicationInitializer.class})
  • @HandlesTypes({WebApplicationInitializer.class})传入的类为ServletContainerInitializer感兴趣的类
  • 容器会自动在classpath中找到WebApplicationInitializer 会传入到onStartup方法的webAppInitializerClasses 参数中,也包括了之前自定义的TomcatStartSpringBoot
    在这里插入图片描述
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements 
  • 启动WebApplicationInitializer的onStartup方法
    循环调用所有WebApplicationInitializer实例的onstartup方法
 if (((List)initializers).isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort((List)initializers);
            var4 = ((List)initializers).iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                //循环调用所有WebApplicationInitializer实例的onstartup方法
                initializer.onStartup(servletContext);
            }

  • 调用SpringBootServletInitializer onStartup方法
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.setAttribute("logging.register-shutdown-hook", false);
        this.logger = LogFactory.getLog(this.getClass());
        //创建对对对 ,这个方法里就会去调用configure方法
        WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
        if (rootApplicationContext != null) {
            servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }
  • createRootApplicationContext
    在这里插入图片描述
    当调用configure方法的时候,就会调用我们自己的configure方法(为了更好的理解才去父类里看),因为TomcatStartSpringBoot是继承了SpringBootServletInitializer又继承了WebApplicationInitializer(我们自己定义的TomcatStartSpringBoot也是一个WebApplicationInitializer)
    在这里插入图片描述
public class TomcatStartSpringBoot extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(MyApplication.class);
    }
}

createRootApplicationContext
调用build方法,就会根据传入的SpringBoot的启动类来构建一个springapplication
在这里插入图片描述

  • SpringApplicationBuilder
    public SpringApplication build(String... args) {
        this.configureAsChildIfNecessary(args);
        this.application.addPrimarySources(this.sources);
        return this.application;
    }

最后再调用run方法来启动springboot
在这里插入图片描述
启动springboot

   protected WebApplicationContext run(SpringApplication application) {
        return (WebApplicationContext)application.run(new String[0]);
    }

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值