什么是Servlet容器
什么是 Web 服务器?
想要了解什么是 Servlet 容器,首先需要知道什么是 Web 服务器。
Web 服务器使用 HTTP 协议传输数据。在一般情况下,用户在浏览器(客户端)中键入 URL(例如 www.baidu.com/static.html ),并获取要读取的网页。所以服务器所做的就是向客户机发送一个网页。信息的交换采用指定请求和响应消息的格式的 HTTP 协议。
什么是 Servlet 容器
正如我们看到的,用户/客户端只能从服务器请求静态网页。如果用户希望根据自己的输入阅读网页,那么这还不够好。Servlet 容器的基本思想是使用 Java 动态生成服务器端的网页。所以 Servlet 容器本质上是与 Servlet 交互的 Web 服务器的一部分。
“Servlet 容器”是一个装载一堆 Servlet 对象的“器具”(容器),并且具备管理这些对象的功能。
什么是 Servlet
Servlet 是 javax.servlet 包中定义的接口。它声明了 Servlet 生命周期的三个基本方法:init()、service() 和 destroy()。它们由每个 Servlet Class(在 SDK 中定义或自定义)实现,并由服务器在特定时机调用.
- init() 方法在 Servlet 生命周期的初始化阶段调用。它被传递一个实现 javax.servlet.ServletConfig 接口的对象,该接口允许 Servlet 从 Web 应用程序访问初始化参数。
- service() 方法在初始化后对每个请求进行调用。每个请求都在自己的独立线程中提供服务。Web容器为每个请求调用 Servlet 的 service() 方法。service() 方法确认请求的类型,并将其分派给适当的方法来处理该请求。
- destroy() 方法在销毁 Servlet 对象时调用,用来释放所持有的资源。
从 Servlet 对象的生命周期中,我们可以看到 Servlet 类是由类加载器动态加载到容器中的。每个请求都在自己的线程中,Servlet 对象可以同时服务多个线程(线程不安全的)。当它不再被使用时,会被 JVM 垃圾收集。像任何Java程序一样,Servlet 在 JVM 中运行。为了处理复杂的 HTTP 请求,Servlet 容器出现了。Servlet 容器负责 Servlet 的创建、执行和销毁。
Servlet 容器和 Web 服务器如何处理一个请求的
- Web 服务器接收 HTTP 请求。
- Web 服务器将请求转发到 Servlet 容器。
- 如果对应的 Servlet 不在容器中,那么将被动态检索并加载到容器的地址空间中。
- 容器调用 init() 方法进行初始化(仅在第一次加载 Servlet 时调用一次)。
- 容器调用 Servlet 的 service() 方法来处理 HTTP 请求,即读取请求中的数据并构建响应。Servlet 将暂时保留在容器的地址空间中,可以继续处理其它 HTTP 请求。
- Web 服务器将动态生成的结果返回到浏览器/客户端。
JVM的作用
Servlet 允许 JVM 在处理每个请求时使用单独的 Java 线程,这是 Servlet 容器的一个主要优点。每个 Servlet 是一个 Java 类,具有响应 HTTP 请求的特殊元素。
Servlet 容器的主要功能是将请求转发到正确的 Servlet 进行处理,并在 JVM 处理完后将动态生成的结果返回到正确的位置。
在大多数情况下, Servlet 容器在单个 JVM 中运行,但是当容器需要多个 JVM 时,会有一些其它的解决方案。
Spring配置嵌入式Servlet容器
SpringBoot默认使用嵌入式的Servlet容器(Tomcat);
这个就是SpringBoot工程中pom文件依赖的tomcat。
如何定制和修改Servlet容器
我们在一般进行java开发的时候都是打成一个war包,然后部署到tomcat中,但是我们如果使用的是SpringBoot嵌入的Tomcat,该如何及进行定制,如果修改Tomcat的配置
修改和server有关的配置
直接在配置文件中进行修改
80 = |
在aotucoinfig中的web文件夹下有一个serverproperties的类,就是用于解析配置文件中的配置的
"server", ignoreUnknownFields = true) (prefix = |
- 我们修改通用servlet容器配置用过server来进行配置
80 =
/crud =
#server开头的都是进行通用配置 - tomcat配置
UTF-8 =
#server.tomcat.xxx 就是Tomcat的配置 - 编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
//使用WebMvcConfigurerAdapter可以扩展SpringMVC的功能
//@EnableWebMvc
public class MyMVCconfig extends WebMvcConfigurerAdapter {
//一定要将这个定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer()
{
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
configurableEmbeddedServletContainer.setPort(8080);
}
};
}
}
注册Servlet三大组件【Servlet、Filter、Listener】
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册Servlet
首先我们需要自己编写一个servlet
public class Myservlet extends HttpServlet { |
然后我们需要一个@Configuration
注解的类去注册一下这个servlet,利用这个类ServletRegistrationBean
|
这样访问项目地址/myservlet就可以获取到hello world了
注册Filter
首先我们需要实现一个Filter
public class MyFilter implements Filter { |
然后我们需要一个@Configuration
注解的类去注册一下这个filter,利用这个类FilterRegistrationBean
|
访问/hello,/myServlet请求就会被拦截
注册Listener
编写一个监听器
public class MyListener implements ServletContextListener { |
注册一个监听器,方式与前两个相同
|
相关设置都可以在这些类里面进行设置
DispatcherServlet,中心调度
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DispatcherServlet,而相关的配置在DispatcherServletAutoConfiguration中:
(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) |
DispatcherServlet也是通过上面的方式进行注册的。
SpringBoot支持其他Servlet容器
Springboot默认支持:
Tomcat
Jetty(长连接)
Undertow(不支持jsp)
之所以默认使用tomcat的原因是在pom文件中spring-boot-starter-web
中默认添加了tomcat的包,如果我们要加入其他的容器,我们需要先将原本有的tomcat排除掉
- 将默认的tomcat排除掉
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--这样将tomcat的依赖移除掉-->
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency> - 引入其他的servlet容器,引入jetty或者Undertows
<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId><!--替换成Undertows<artifactId>spring-boot-starter-Undertows</artifactId>-->
<groupId>org.springframework.boot</groupId>
</dependency>
SpringBoot装配嵌入式Servlet原理
嵌入式Servlet容器自动配置原理
其中在自动配置包中的web包中有EmbeddedServletContainerAutoConfiguration
类是进行servlet容器自动配置的
(Ordered.HIGHEST_PRECEDENCE) |
EmbeddedServletContainerFactory
(嵌入式的Servlet容器工厂)public interface EmbeddedServletContainerFactory {
//获取嵌入式的Servlet容器
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}
EmbeddedServletContainer
以Tomcat的创建为例,看一下Tomcat工厂是如何创建容器的
public class TomcatEmbeddedServletContainerFactory
extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware
{
//真正用来生产容器的方法
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
//创建一个tomcat的基本环节
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("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传进去,返回一个EmbeddedServletContainer,并且启动tomcat容器
return getTomcatEmbeddedServletContainer(tomcat);
}
}
嵌入式容器修改的配置是如何生效的
上面提到过,修改容器的配置有两种方式
1、修改配置文件
2、EmbeddedServletContainerCustomizer
我们发现其实serverporperties也是继承了EmbeddedServletContainerCustomizer,其实来两者本质上是相同的。
EmbeddedServletContainerCustomizer
定制器帮我们修改了servlet容器的配置
那么他的修改原理是什么呢?
在EmbeddedServletContainerAutoConfiguration
容器自动配置类加载的时候导入了一个组件EmbeddedServletContainerCustomizerBeanPostProcessor
:嵌入式Servlert容器后置处理器
public class EmbeddedServletContainerCustomizerBeanPostProcessor |
Servlet配置的步骤是:
1、SpringBoot根据导入的依赖情况,给容器中添加相应的嵌入式容器工厂如(TomcatEmbeddedServletContainerFactory)
2、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor
,只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer
,调用定制器的定制方法
SpriongBoot嵌入式Servlet容器启动原理
获取嵌入式的Servlet容器工厂:
SpringBoot启动运行run方法
执行
refreshContext(context)
:SpringBoot刷新IOC容器(创建IOC容器对象,并初始化容器,包括创建容器中每一个组件);如果是web应用就会创建AnnotationConfigEmbeddedWebApplicationContext
容器,否则创建AnnotationConfigApplicationContext
refresh(context)
刷新刚才创建好的IOC容器:
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.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
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();
}
}
}onRefresh()
:web的ioc容器重写了onRefresh方法web IOC容器会创建嵌入式的Servlet容器:
createEmbeddedServletContainer();
在EmbeddedWebApplicationContext
获取嵌入式得Servlet工厂:
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();//获取嵌入式Servlet工厂!
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());//使用容器工厂获取嵌入式的Servlet容器
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory()
.getBeanNamesForType(EmbeddedServletContainerFactory.class);//从ioc容器中获取EmbeddedServletContainerFactory 组件;**TomcatEmbeddedServletContainerFactory**创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
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));
}
return getBeanFactory().getBean(beanNames[0],
EmbeddedServletContainerFactory.class);
}EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory()从ioc容器中获取EmbeddedServletContainerFactory 组件,TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
嵌入式的Servlet容器创建对象并启动Servlet容器;先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来,IOC容器启动会创建嵌入式的Servlet容器
SringBoot使用外置Servlet容器
嵌入式的Servlet容器:应用为可执行的jar包
优点:简单。便携
缺点:默认不支持jsp,优化定制比较复杂(1、使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,2、自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】)
外置的servlet容器:外面安装Tomcat–>应用war包的形式打包
创建后并没有web.xml文件也没有webapp文件夹,需要我们手动进行创建
创建完成以后我们需要使用服务器启动我们的项目
准备完成以后我们点击运行就可以发现启动tomcat后就可以启动项目了,这里注意一下,这里所有的生成都是SpringBoot帮我们搞定的,如果我们想要自己去实现的话,需要注意这么几点
- 必须创建一个war项目;(利用idea创建好目录结构)
- 将嵌入式的Tomcat指定为provided;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency> - 必须编写一个SpringBootServletInitializer的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer {
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用主程序
return application.sources(SrpingbootWebApplication.class);
}
} - 启动服务器就可以使用。
SpringBoot外置容器原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;
我们首先需要从Servlet3.0的规范说起
在Servlet3.0以后在(Shared libraries / runtimes pluggability)有一个规范,规则是这样指定的
1、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例。
2、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
3、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
项目启动流程:
1、启动Tomcat
2、在org\springframework\spring-web\5.2.5.RELEASE\spring-web-5.2.5.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:
Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
3、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
.class) (WebApplicationInitializer |
4、每一个WebApplicationInitializer都调用自己的onStartup
5、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
6、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
public abstract class SpringBootServletInitializer implements WebApplicationInitializer { |
7、Spring的应用就启动并且创建IOC容器
这样整个应用就实现了外置的Tomcat接管内置容器。先启动Servlet容器再启动SpringBoot应用。