Chapter5: SpringBoot与Web开发2

news/2024/5/20 21:25:51

接上一篇 Chapter4: SpringBoot与Web开发1

10. 配置嵌入式Servlet容器

SpringBoot默认采用Tomcat作为嵌入的Servlet容器;查看pom.xml的Diagram依赖图:
在这里插入图片描述
那么如何定制和修改Servlet容器的相关配置? 下面给出实操方案。

10.1 application.properties配置

SpringBoot全局配置文件中修改与Server相关的配置属性都封装在ServerProperties类中。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerPropertiesimplements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {/*** Server HTTP port.*/private Integer port;/*** Network address to which the server should bind to.*/private InetAddress address;/*** Context path of the application.*/private String contextPath;/*** Display name of the application.*/private String displayName = "application";@NestedConfigurationPropertyprivate ErrorProperties error = new ErrorProperties();/*** Path of the main dispatcher servlet.*/private String servletPath = "/";// ...}    

修改配置属性

server.port=8082
##springboot 2.0之后,配置为 server.servlet.context-path
#server.servlet.context-path=/boot1
#springboot 2.0之前,配置为 server.context-path
server.context-path=/boot1
# 通用的Servlet容器配置
#server.xxx
#tomcat的配置
# server.tomcat.xxx

10.2 Java代码方式配置

编写一个嵌入式Servlet容器定制器EmbeddedServletContainerCustomizer的实现类,来修改Servlet容器的配置。ConfigurableEmbeddedServletContainer提供了setXXX进行配置属性注入.

// 配置嵌入式的Servlet容器定制器
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {return new EmbeddedServletContainerCustomizer() {// 定制嵌入式的Servlet容器相关的规则@Overridepublic void customize(ConfigurableEmbeddedServletContainer container) {container.setPort(8083);}};
}

重启应用,从启动日志中查看,应用是从8083端口启动的。
在这里插入图片描述

10.3 注册Servlet三大Web组件

Web的三大组件: Servlet、Filter、Listener

由于SpringBoot默认是以jar包方式的嵌入式Servlet容器来启动SpringBoot的Web应用,没有web.xml文件配置。SpringBoot提供了ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBean来实现三大组件的注册。
在这里插入图片描述

(1)注册Servlet

首先自定义Servlet

public class MyServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().println("hello, my Servlet");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().println("hello, my Servlet");}
}

再使用ServletRegistrationBean注册自定义的servlet,并设置匹配的uri.

@Configuration
public class MyServerConfig {// 注册三大组件// 注册servlet@Beanpublic ServletRegistrationBean myServlet() {ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");System.out.println("regist servlet");return servletRegistrationBean;}
}

启动应用后,浏览器访问 /myServlet
在这里插入图片描述
(2)注册Filter

创建自定义Filter

public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("init my filter");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("my filter process...");// 放行filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {System.out.println("destory my filter");}
}

FilterRegistrationBean注册自定义Filter

@Configuration
public class MyServerConfig {// ....// 注册filter@Beanpublic FilterRegistrationBean myFilter() {// 启动类上加 @ServletComponentScan注解,会扫描到@WebFilter注解标识的filter类 MyWebFilter; filter实例名称如果重复只会有一个生效FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new MyFilter());// 拦截指定请求filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet")); return filterRegistrationBean;}
}

测试过滤器,查看启动日志和请求/hello日志
在这里插入图片描述
在这里插入图片描述
查看销毁日志
在这里插入图片描述

(3)注册Listener

创建自定义Listener

public class MyListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent servletContextEvent) {System.out.println("MyListener.contextInitialized...web启动");}@Overridepublic void contextDestroyed(ServletContextEvent servletContextEvent) {System.out.println("MyListener.contextDestroyed....web销毁");}
}

ServletListenerRegistrationBean注册自定义Listener

@Configuration
public class MyServerConfig {//  ...// 注册listener@Beanpublic ServletListenerRegistrationBean myListener() {ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();servletListenerRegistrationBean.setListener(new MyListener());return servletListenerRegistrationBean;}
}

测试Listener,查看启动日志
在这里插入图片描述
查看销毁日志
在这里插入图片描述
SpringBoot自动配置SpringMVC组件时,其中自动注册的SpringMVC前端控制器(dispatcherServlet)使用了ServletRegistrationBean去注册。

DispatcherServletAutoConfiguration查看源码:

/ 默认拦截所有请求, 包括静态资源,但是不拦截jsp请求; /*会拦截jsp请求。

可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的uri.

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {// 注册dispatcherServletServletRegistrationBean registration = new ServletRegistrationBean(// 设置拦截的uri使用通配符匹配 / /*dispatcherServlet, this.serverProperties.getServletMapping());registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());if (this.multipartConfig != null) {registration.setMultipartConfig(this.multipartConfig);}return registration;
}

也可以使用注解注册三大Web组:@WebServlet, @WebFilter, @WebListener.

10.4 支持其他Servlet容器

上面提到SpringBoot默认支持的嵌入式Servlet容器是tomcat, 也就是只要你引入了Web模块, 就会使用tomcat容器。SpringBoot也支持其他Servlet容器, 比如Jetty, Undertow。
在这里插入图片描述
接口ConfigurableEmbeddedServletContainer提供了支持tomcat, jetty,undertow的实现。
在这里插入图片描述

(1)改造成Jetty容器

首先要排除tomcat的依赖, 再引入Jetty的启动器依赖。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
</dependency><!--引入其他web容器 Jetty-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

查看启动日志, 启动的容器是Jetty.

[2023-05-18 22:00:30.030] [org.eclipse.jetty.server.handler.ContextHandler$Context] [main] [2221] [INFO ] Initializing Spring FrameworkServlet 'dispatcherServlet'
[2023-05-18 22:00:30.030] [org.springframework.web.servlet.FrameworkServlet] [main] [489] [INFO ] FrameworkServlet 'dispatcherServlet': initialization started
[2023-05-18 22:00:30.030] [org.springframework.web.servlet.FrameworkServlet] [main] [508] [INFO ] FrameworkServlet 'dispatcherServlet': initialization completed in 7 ms
[2023-05-18 22:00:30.030] [org.eclipse.jetty.server.AbstractConnector] [main] [288] [INFO ] Started ServerConnector@53125718{HTTP/1.1,[http/1.1]}{0.0.0.0:8082}
[2023-05-18 22:00:30.030] [org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainer] [main] [155] [INFO ] Jetty started on port(s) 8082 (http/1.1) // 启动Jetty容器
[2023-05-18 22:00:30.030] [org.springframework.boot.StartupInfoLogger] [main] [57] [INFO ] Started SpringBoot02ConfigApplication in 4.428 seconds (JVM running for 5.0)

(2)改造成undertow容器

排除tomcat的依赖, 再引入undertow的启动器依赖。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
</dependency><!--引入其他web容器 undertow-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

查看启动日志

[2023-05-18 22:05:21.021] [org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainer] [main] [160] [INFO ] Undertow started on port(s) 8082 (http) // 启动undertow容器
[2023-05-18 22:05:21.021] [org.springframework.boot.StartupInfoLogger] [main] [57] [INFO ] Started SpringBoot02ConfigApplication in 4.293 seconds (JVM running for 4.863)

10.5 嵌入式Servlet容器自动配置原理

步骤:

  • SpringBoot根据导入的依赖情况,自动配置类给容器中添加相应的Servlet容器工厂EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】, 获取对应的servlet容器EmbeddedServletContainer【TomcatEmbeddedServletContainer】

  • 容器中某个组件要创建对象就会惊动后置处理器进行初始化工作EmbeddedServletContainerCustomizerBeanPostProcessor 只要是嵌入式的Servlet容器工厂,后置处理器就会工作;

  • 在后置处理器中,从容器中获取所有的EmbeddedServletContainerCustomizer定制器,调用定制器的customize定制方法设置配置属性, ServerProperties#customize设置servlet相关配置.

10.5.1 定制器配置

嵌入式Servlet容器自动配置类 EmbeddedServletContainerAutoConfiguration
导入BeanPostProcessorsRegistrar 给容器中导入一些组件;在其registerBeanDefinitions方法中注册了EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器,用于bean初始化前后执行指定工作;

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class) // Bean后置处理器注册器
public class EmbeddedServletContainerAutoConfiguration {// ....public static class BeanPostProcessorsRegistrarimplements ImportBeanDefinitionRegistrar, BeanFactoryAware {private ConfigurableListableBeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {if (beanFactory instanceof ConfigurableListableBeanFactory) {this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;}}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {if (this.beanFactory == null) {return;}// 注册嵌入式Servlet容器定制器Bean的后置处理器, 在定制器实例化过程中进行初始化工作.registerSyntheticBeanIfMissing(registry,"embeddedServletContainerCustomizerBeanPostProcessor",EmbeddedServletContainerCustomizerBeanPostProcessor.class);registerSyntheticBeanIfMissing(registry,"errorPageRegistrarBeanPostProcessor",ErrorPageRegistrarBeanPostProcessor.class);}private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,String name, Class<?> beanClass) {if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);beanDefinition.setSynthetic(true);registry.registerBeanDefinition(name, beanDefinition);}}}
}

嵌入式Servlet容器定制器后置处理器的初始化方法如下:

public class EmbeddedServletContainerCustomizerBeanPostProcessorimplements BeanPostProcessor, BeanFactoryAware {private ListableBeanFactory beanFactory;private List<EmbeddedServletContainerCustomizer> customizers;@Overridepublic void setBeanFactory(BeanFactory beanFactory) {Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "+ "with a ListableBeanFactory");this.beanFactory = (ListableBeanFactory) beanFactory;}// 之前处理@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {// 如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件if (bean instanceof ConfigurableEmbeddedServletContainer) {// 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {return bean;}private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {// 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {customizer.customize(bean);}}private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {if (this.customizers == null) {// Look up does not include the parent contextthis.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(this.beanFactory// 从容器中获取所有这种类型的组件:EmbeddedServletContainerCustomizer// 定制Servlet容器,可以给容器中添加一个EmbeddedServletContainerCustomizer类型的组件。.getBeansOfType(EmbeddedServletContainerCustomizer.class,false, false).values());Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);this.customizers = Collections.unmodifiableList(this.customizers);}return this.customizers;}}

嵌入式servlet容器定制器接口提供了customize方法给我们进行定制化处理, 帮助我们修改Servlet容器的配置。

public interface EmbeddedServletContainerCustomizer {/*** Customize the specified {@link ConfigurableEmbeddedServletContainer}.* @param container the container to customize*/void customize(ConfigurableEmbeddedServletContainer container);}

那么我们对嵌入Servlet式容器的配置修改是怎么生效呢?

嵌入式的Servlet容器配置修改通过ServerProperties实现,ServerProperties也是定制器(它实现了嵌入式Servlet容器定制器接口EmbeddedServletContainerCustomizer)。
在这里插入图片描述
在application.properties文件中修改server.xxx配置时,就是通过定制器的customize方法进行定制化处理,从而达到修改Servlet容器的配置效果。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerPropertiesimplements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {/*** Server HTTP port.*/private Integer port;/*** Network address to which the server should bind to.*/private InetAddress address;/*** Context path of the application.*/private String contextPath;// ServerProperties#customize 修改servlet容器配置@Overridepublic void customize(ConfigurableEmbeddedServletContainer container) {if (getPort() != null) {container.setPort(getPort());}if (getAddress() != null) {container.setAddress(getAddress());}if (getContextPath() != null) {container.setContextPath(getContextPath());}if (getDisplayName() != null) {container.setDisplayName(getDisplayName());}if (getSession().getTimeout() != null) {container.setSessionTimeout(getSession().getTimeout());}container.setPersistSession(getSession().isPersistent());container.setSessionStoreDir(getSession().getStoreDir());if (getSsl() != null) {container.setSsl(getSsl());}if (getJspServlet() != null) {container.setJspServlet(getJspServlet());}if (getCompression() != null) {container.setCompression(getCompression());}container.setServerHeader(getServerHeader());if (container instanceof TomcatEmbeddedServletContainerFactory) {getTomcat().customizeTomcat(this,(TomcatEmbeddedServletContainerFactory) container);}if (container instanceof JettyEmbeddedServletContainerFactory) {getJetty().customizeJetty(this,(JettyEmbeddedServletContainerFactory) container);}if (container instanceof UndertowEmbeddedServletContainerFactory) {getUndertow().customizeUndertow(this,(UndertowEmbeddedServletContainerFactory) container);}container.addInitializers(new SessionConfiguringInitializer(this.session));container.addInitializers(new InitParameterConfiguringServletContextInitializer(getContextParameters()));}
}

自定义嵌入式Servlet容器定制器配置

@Configuration
public class MyServerConfig {// 配置嵌入式的Servlet容器@Beanpublic EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {return new EmbeddedServletContainerCustomizer() {// 定制嵌入式的Servlet容器相关的规则@Overridepublic void customize(ConfigurableEmbeddedServletContainer container) {// 修改Servlet容器http端口container.setPort(8082);}};}
}

10.5.2 嵌入式Servlet容器初始化(自动启动原理)

EmbeddedServletContainerFactory 嵌入式Servlet容器工厂提供了创建嵌入式Servlet容器的方法。

public interface EmbeddedServletContainerFactory {/*** Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.* Clients should not be able to connect to the returned server until* {@link EmbeddedServletContainer#start()} is called (which happens when the* {@link ApplicationContext} has been fully refreshed).* @param initializers {@link ServletContextInitializer}s that should be applied as* the container starts* @return a fully configured and started {@link EmbeddedServletContainer}* @see EmbeddedServletContainer#stop()*  获取嵌入式Servlet容器*/EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers);}

嵌入式Servlet容器工厂接口提供了3个实现类,支持tomcat、Jetty和Undertow容器。
在这里插入图片描述
EmbeddedServletContainer 嵌入式Servlet容器
在这里插入图片描述
EmbeddedServletContainer容器提供了启动和停止Servlet容器的方法。

public interface EmbeddedServletContainer {/*** Starts the embedded servlet container. Calling this method on an already started* container has no effect.* @throws EmbeddedServletContainerException if the container cannot be started*/void start() throws EmbeddedServletContainerException;/*** Stops the embedded servlet container. Calling this method on an already stopped* container has no effect.* @throws EmbeddedServletContainerException if the container cannot be stopped*/void stop() throws EmbeddedServletContainerException;/*** Return the port this server is listening on.* @return the port (or -1 if none)*/int getPort();
}

思考: 什么时候创建嵌入式Servlet容器工厂?什么时候获取嵌入式Servlet容器并启动Tomcat?

嵌入式Servlet容器初始化步骤(以tomcat容器为例):

1)SpringBoot应用启动运行run方法, 创建IOC容器对象.

// org.springframework.boot.SpringApplication#run方法
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;FailureAnalyzers analyzers = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);Banner printedBanner = printBanner(environment);// 创建IOC容器context = createApplicationContext();analyzers = new FailureAnalyzers(context);prepareContext(context, environment, listeners, applicationArguments,printedBanner);// 刷新IOC容器【初始化IOC容器,实例化容器中的每一个组件】refreshContext(context);afterRefresh(context, applicationArguments);listeners.finished(context, null);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}return context;}catch (Throwable ex) {handleRunFailure(context, listeners, analyzers, ex);throw new IllegalStateException(ex);}
}

如果是Web应用创建AnnotationConfigEmbeddedWebApplicationContext容器;

如果不是Web应用创建AnnotationConfigApplicationContext容器。

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";
// org.springframework.boot.SpringApplication#createApplicationContext方法
protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {contextClass = Class.forName(this.webEnvironment// AnnotationConfigEmbeddedWebApplicationContext? DEFAULT_WEB_CONTEXT_CLASS : // AnnotationConfigApplicationContext                              DEFAULT_CONTEXT_CLASS);}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, "+ "please specify an ApplicationContextClass",ex);}}//  返回IOC容器对象return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

2)refreshContext(context): SpringBoot刷新IOC容器【初始化IOC容器,实例化容器中的每一个组件】;

// org.springframework.boot.SpringApplication#refreshContext方法
private void refreshContext(ConfigurableApplicationContext context) {// refresh(context) 刷新刚才创建好的IOC容器;refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}
}protected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);// AbstractApplicationContext#refresh方法((AbstractApplicationContext) applicationContext).refresh();
}

(3)AbstractApplicationContext#refresh()中, onRefresh()实现获取嵌入式Servlet容器。

// org.springframework.context.support.AbstractApplicationContext#refresh方法
@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// AbstractApplicationContext#onRefresh中实现获取嵌入式Servlet容器onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.// 启动嵌入式Servlet容器后,再将IOC容器中剩下没有创建的组件进行bean实例化。finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}
}// AbstractApplicationContext#onRefresh()方法,在子类IOC容器中重写onRefresh()
protected void onRefresh() throws BeansException {// For subclasses: do nothing by default.
}

(4)onRefresh() , IOC容器EmbeddedWebApplicationContext重写了onRefresh()方法,实现创建嵌入式Servlet容器。

@Override
protected void onRefresh() {super.onRefresh();try {// 创建嵌入式Servlet容器createEmbeddedServletContainer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start embedded container",ex);}
}

5)web的IOC容器会创建嵌入式的Servlet容器工厂; 从IOC容器中获取EmbeddedServletContainerFactory容器工厂组件;

// EmbeddedWebApplicationContext#createEmbeddedServletContainer()方法
private void createEmbeddedServletContainer() {EmbeddedServletContainer localContainer = this.embeddedServletContainer;ServletContext localServletContext = getServletContext();if (localContainer == null && localServletContext == null) {// 获取嵌入式Servlet容器工厂EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();// 获取嵌入式Servlet容器,并启动嵌入式Servlet容器(tomcat)this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());}else if (localServletContext != null) {try {getSelfInitializer().onStartup(localServletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context",ex);}}initPropertySources();
}

getEmbeddedServletContainerFactory()方法获取IOC容器中的嵌入式Servlet容器工厂对象。

// EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory()方法
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {// Use bean names so that we don't consider the hierarchyString[] beanNames = getBeanFactory()// 从Bean工厂中获取嵌入式servlet容器工厂bean名称.getBeanNamesForType(EmbeddedServletContainerFactory.class);if (beanNames.length == 0) {throw new ApplicationContextException("Unable to start EmbeddedWebApplicationContext due to missing "+ "EmbeddedServletContainerFactory bean.");}if (beanNames.length > 1) {throw new ApplicationContextException("Unable to start EmbeddedWebApplicationContext due to multiple "+ "EmbeddedServletContainerFactory beans : "+ StringUtils.arrayToCommaDelimitedString(beanNames));}//从IOC容器中获取并返回嵌入式Servlet容器工厂实例对象return getBeanFactory().getBean(beanNames[0],EmbeddedServletContainerFactory.class);
}

那么IOC容器中的嵌入式Servlet容器工厂实例对象是什么时候实例化的呢?

在Servlet容器自动配置类EmbeddedServletContainerAutoConfiguration中有实例化嵌入式Servlet容器工厂的方法,比如 TomcatEmbeddedServletContainerFactory

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {/*** Nested configuration if Tomcat is being used.*/@Configuration@ConditionalOnClass({ Servlet.class, Tomcat.class })@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedTomcat {// 当容器中没有EmbeddedServletContainerFactory实例时就创建Servlet容器工厂。@Beanpublic TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {return new TomcatEmbeddedServletContainerFactory();}}
}	

(6)BeanPostProcessorsRegistrar注册后置处理器 EmbeddedServletContainerCustomizerBeanPostProcessor

TomcatEmbeddedServletContainerFactory容器工厂对象创建,后置处理器发现是嵌入式Servlet容器工厂,就获取所有的定制器先定制Servlet容器的相关配置。

// EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar
// BeanPostProcessorsRegistrar#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {if (this.beanFactory == null) {return;}registerSyntheticBeanIfMissing(registry,// 注册嵌入式servlet容器定制器后置处理器                                   "embeddedServletContainerCustomizerBeanPostProcessor",EmbeddedServletContainerCustomizerBeanPostProcessor.class);registerSyntheticBeanIfMissing(registry,"errorPageRegistrarBeanPostProcessor",ErrorPageRegistrarBeanPostProcessor.class);
}

在后置处理器的前置方法中判断,如果是嵌入式Servlet容器对象就初始化Servlet容器。通过获取所有Servlet容器定制器,调用其定制方法初始化Servlet容器属性配置。

public class EmbeddedServletContainerCustomizerBeanPostProcessorimplements BeanPostProcessor, BeanFactoryAware {private ListableBeanFactory beanFactory;private List<EmbeddedServletContainerCustomizer> customizers;@Overridepublic void setBeanFactory(BeanFactory beanFactory) {Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "+ "with a ListableBeanFactory");this.beanFactory = (ListableBeanFactory) beanFactory;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {// 如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件if (bean instanceof ConfigurableEmbeddedServletContainer) {// 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {return bean;}private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {// 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {customizer.customize(bean);}}private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {if (this.customizers == null) {// Look up does not include the parent contextthis.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(this.beanFactory// 从容器中获取所有这种类型的组件:EmbeddedServletContainerCustomizer// 定制Servlet容器,可以给容器中添加一个EmbeddedServletContainerCustomizer类型的组件。.getBeansOfType(EmbeddedServletContainerCustomizer.class,false, false).values());Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);this.customizers = Collections.unmodifiableList(this.customizers);}return this.customizers;}
}

(7) 通过嵌入式Servlet容器工厂获取嵌入式Servlet容器对象(默认 tomcat)

EmbeddedWebApplicationContext#createEmbeddedServletContainer()方法中containerFactory.getEmbeddedServletContainer(getSelfInitializer()); 实际调用的是子类的实现。
在这里插入图片描述
(8)获取tomcat容器并启动tomcat

在嵌入式Servlet容器工厂TomcatEmbeddedServletContainerFactory中,继承了EmbeddedServletContainerFactory接口的getEmbeddedServletContainer方法,可以获取tomcat容器。

public class TomcatEmbeddedServletContainerFactoryextends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {// 获取tomcat容器工厂@Overridepublic EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {// 创建一个tomcat对象Tomcat tomcat = new Tomcat();File baseDir = (this.baseDirectory != null ? this.baseDirectory: createTempDir("tomcat"));// 配置tomcat容器的基本信息tomcat.setBaseDir(baseDir.getAbsolutePath());Connector connector = new Connector(this.protocol);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传进去,返回一个嵌入式Servlet容器 (tomcat)return getTomcatEmbeddedServletContainer(tomcat);}protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {// 返回一个嵌入式Servlet容器 (tomcat)return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);}public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {Assert.notNull(tomcat, "Tomcat Server must not be null");this.tomcat = tomcat;this.autoStart = autoStart;// 初始化tomcatinitialize();}private void initialize() throws EmbeddedServletContainerException {TomcatEmbeddedServletContainer.logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));synchronized (this.monitor) {try {addInstanceIdToEngineName();try {// Remove service connectors to that protocol binding doesn't happen// yetremoveServiceConnectors();// Start the server to trigger initialization listeners// 启动tomcat服务器this.tomcat.start();// We can re-throw failure exception directly in the main threadrethrowDeferredStartupExceptions();Context context = findContext();try {ContextBindings.bindClassLoader(context, getNamingToken(context),getClass().getClassLoader());}catch (NamingException ex) {// Naming is not enabled. Continue}// Unlike Jetty, all Tomcat threads are daemon threads. We create a// blocking non-daemon to stop immediate shutdownstartDaemonAwaitThread();}catch (Exception ex) {containerCounter.decrementAndGet();throw ex;}}catch (Exception ex) {throw new EmbeddedServletContainerException("Unable to start embedded Tomcat", ex);}}}	
}

11. 使用外置的Servlet容器

  • 嵌入式Servlet容器:应用打成可执行的jar包

    优点:简单、便携;

    缺点:默认不支持Jsp、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);

  • 外置的Servlet容器,外面安装Tomcat:应用打成war包

11.1 步骤

1)必须创建一个web项目,war包方式
在这里插入图片描述
在这里插入图片描述

完善web应用目录,指定web.xml; Web Resource目录:

D:\Develops\IdeaProjects\study-spring-boot\spring-boot-atguigu\spring-boot-02-web-jsp\src\main\webapp

在这里插入图片描述

2)将嵌入式的Tomcat指定为provided;provided意味着打包的时候可以不用打包进去,外部容器(Web Container)会提供。该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了exclude的动作。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope>
</dependency>

3)必须编写一个SpringBootServletInitializer的子类,重写configure方法指定SpringBoot应用的主启动类。

public class ServletInitializer extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {// 传入SpringBoot应用的主程序return applicationBuilder.sources(SpringBoot02WebJspApplication.class);}}

4)如果要支持Jsp,在application.properties文件添加mvc相关配置。

# jsp视图: /webapp/WEB-INF/xxx.jsp
# jsp视图文件前缀
spring.mvc.view.prefix=/WEB-INF/
# jsp视图文件后缀 
spring.mvc.view.suffix=.jsp
# 静态资源目录 resources/static/
spring.mvc.static-path-pattern=/static/**

比如添加src/main/webapp/WEB-INF/success.jsp视图

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>来到success页面</title></head><body><h1>${msg}</h1></body>
</html>

添加handler

@Controller
public class HelloController {@GetMapping("/hello")public String hello(Model model) {model.addAttribute("msg", "succeed");return "success";}
}

5)配置外部web容器(tomcat),启动服务器就可以使用。
在这里插入图片描述
6)测试,访问 /hello
在这里插入图片描述

11.2 原理

Jar包:执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器。

War包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动IOC容器。

外部Servlet容器启动原理 :

  • Servlet3.0标准,ServletContainerInitializer扫描所有jar包中META-INF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载;
  • 加载spring-web-xxx包下的ServletContainerInitializer;
  • 扫描@HandlesTypes(WebApplicationInitializer.class)
  • 加载SpringBootServletInitializer并运行onStartup方法;
  • 加载@SpringBootApplication主类,启动IOC容器;

具体规则:

  • 服务器启动(Web应用启动)会创建当前Web应用里面每一个jar包里面ServletContainerInitializer实例;

  • ServletContainerInitializer的实现放在spring-web-xxx.jar包的/META-INF/services/javax.servlet.ServletContainerInitializer文件,内容就是ServletContainerInitializer的实现类的全限定名,比如:SpringServletContainerInitializer
    在这里插入图片描述

  • 应用启动的时候加载@HandlesTypes指定的类组件。

详细流程:

  • 启动配置的外部Tomcat

  • 加载org/springframework/spring-web/5.3.7/spring-web-5.3.7.jar!/META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的ServletContainerInitializer实例 SpringServletContainerInitializer组件;

    public interface ServletContainerInitializer {void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
    }
    

    SpringServletContainerInitializer将@HandlesTypes({WebApplicationInitializer.class})

    标注所有该类型的类都传入到onStartup方法的Set<Class<?>> webAppInitializerClasses中, 并

    为这些WebApplicationInitializer类型的类创建实例。最后依次调用WebApplicationInitializer#onStartup方法初始化;

    // 扫描所有WebApplicationInitializer组件
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();if (webAppInitializerClasses != null) {initializers = new ArrayList<>(webAppInitializerClasses.size());for (Class<?> waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,// no matter what @HandlesTypes says...if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {/* 将@HandlesTypes扫描到的WebApplicationInitializer组件实例化并添						  * 加到initializers中*/initializers.add((WebApplicationInitializer)// 实例化WebApplicationInitializer组件
    ReflectionUtils.accessibleConstructor(waiClass).newInstance());}catch (Throwable ex) {throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);}}}}if (initializers.isEmpty()) {servletContext.log("No Spring WebApplicationInitializer types detected on classpath");return;}servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");AnnotationAwareOrderComparator.sort(initializers);for (WebApplicationInitializer initializer : initializers) {// 依次执行所有WebApplicationInitializer#onStartup方法initializer.onStartup(servletContext);}}}// WebApplicationInitializer接口
    public interface WebApplicationInitializer {void onStartup(ServletContext servletContext) throws ServletException;
    }
    

    执行到自定义WebApplicationInitializer组件,ServletInitializer#onStartup方法,其实是父类的SpringBootServletInitializer#onStartup;

    // 自定义WebApplicationInitializer组件
    public class ServletInitializer extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {// 传入SpringBoot应用的主程序到SpringApplicationBuilderreturn applicationBuilder.sources(SpringBoot02WebJspApplication.class);}}
    

在这里插入图片描述

// WebApplicationInitializer组件
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {// SpringBootServletInitializer#onStartup@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {servletContext.setAttribute(LoggingApplicationListener.REGISTER_SHUTDOWN_HOOK_PROPERTY, false);// Logger initialization is deferred in case an ordered// LogServletContextInitializer is being usedthis.logger = LogFactory.getLog(getClass());// 创建web的IOC容器WebApplicationContext rootApplicationContext = 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");}}
}
  • SpringServletContainerInitializer实例执行SpringBootServletInitializer#onStartup时会调用

    createRootApplicationContext方法创建IOC容器。

    并通过自定义WebApplicationInitializer组件重写的configure方法获取builder对象,再通过builder.build()获取SpringApplication对象。

    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {SpringApplicationBuilder builder = createSpringApplicationBuilder();builder.main(getClass());ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);if (parent != null) {this.logger.info("Root context already created (using as parent).");servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);builder.initializers(new ParentContextApplicationContextInitializer(parent));}builder.initializers(new ServletContextApplicationContextInitializer(servletContext));builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());// 执行的是子类的configure,将应用主启动类添加到builder并返回SpringApplicationBuilderbuilder = configure(builder);builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));// 通过SpringApplicationBuilder#build创建应用实例 SpringApplicationSpringApplication application = builder.build();// ...省略// 运行SpringApplication,application通过builder添加了主启动类,会启动主启动类return run(application);
    }
    

在这里插入图片描述
最后执行run(application)启动Spring应用;后面就和SpringBoot应用主程序入口启动一样的步骤了。

// org.springframework.boot.web.servlet.support.SpringBootServletInitializer#run
protected WebApplicationContext run(SpringApplication application) {return (WebApplicationContext) application.run();
}
  • 先启动Servlet容器,再启动SpringBoot应用。
    在这里插入图片描述

https://www.xjx100.cn/news/344553.html

相关文章

微信小程序通过 node 连接 mysql——方法,简要原理,及一些常见问题

前言 博主自己在22年夏天根据课程要求做了一个小程序连接阿里云服务器的案例&#xff0c;在最近又碰到了相应的需求。 原参考文章&#xff1a;微信小程序 Node连接本地MYSQL_微信小程序nodejs连接数据库_JJJenny0607的博客-CSDN博客 ,还请多多支持原作者&#xff01; 第二次…

128. 最长连续序列【中等】

题目 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&#xff1a; 输入&#xff1a;nums [100,4,200,1,3,2] 输出&#xff1…

第六章 社会主义发展及其规律

一. 单选题&#xff08;共40题&#xff0c;60分&#xff09; 1. (单选题)全部马克思主义学说的核心和理论结论是( ) A. 科学社会主义 2. (单选题)科学社会主义的直接理论来源是( ) C. 19世纪初期以圣西门、傅立叶、欧文为代表的空想社会主义 3. (单选题)社会主义实现…

Redis如何做到内存高效利用?过期key删除术解析!

大家好&#xff0c;我是小米&#xff0c;一个热衷于分享技术的小伙伴。今天我要和大家探讨一个关于 Redis 的话题&#xff1a;删除过期key。在使用 Redis 进行数据存储和缓存时&#xff0c;我们经常会遇到过期数据的处理问题。接下来&#xff0c;我将为大家介绍为什么要删除过期…

STL --- 2、容器 (8)priority_queue

目录 1、std::priority_queue的特点 2、std::priority_queue常用api 3、std::priority_queue应用场景 4、std::priority_queue实例 std::priority_queue是一个STL容器&#xff0c;它是一个优先队列&#xff0c;每个元素都有一个权值&#xff0c;优先级高的元素排在队列的前…

OpenGL之纹理

文章目录 什么是纹理加载与创建纹理stb_image.h加载并生成纹理 纹理环绕方式纹理过滤多级渐远纹理 纹理单元 什么是纹理 我们已经了解到&#xff0c;我们可以为每个顶点添加颜色来增加图形的细节&#xff0c;从而创建出有趣的图像。但是&#xff0c;如果想让图形看起来更真实&a…

dvwa靶场通关(一)

第一关&#xff1a;Brute force low 账号是admin&#xff0c;密码随便输入 用burp suite抓包 爆破得出密码为password 登录成功 Medium 中级跟low级别基本一致&#xff0c;分析源代码我们发现medium采用了符号转义&#xff0c;一定程度上防止了sql注入&#xff0c;采用暴力破…

Android 12.0默认开启无障碍服务权限和打开默认apk无障碍服务

1.概述 在12.0的系统rom定制化开发中,在第三方app开发中,需要开启无障碍服务功能,就不需要在代码中开启无障碍服务了, 为了简便就需要在系统中开启无障碍服务,来实现开启无障碍服务功能 2. 默认开启无障碍服务权限和打开默认apk无障碍服务核心代码 frameworks/base/core…