文章目录
- 1. 如何定制和修改Servlet容器的相关配置
- 2. 注册Servlet三大组件
- 2.1 Servlet
- 2.2 Filter
- 2.3 Listener
- 3. 使用其他的嵌入式容器
- 4. 嵌入式Servlet容器自动配置原理
- 5. 配置修改原理
- 6. 嵌入式Servlet容器启动原理
- 7. 使用外置的Servlet容器
- 7.1 步骤
- 7.2 原理
1. 如何定制和修改Servlet容器的相关配置
SpringBoot 默认使用Tomcat作为嵌入式的容器。
- 修改和server相关的配置(相关配置都在ServerProperties 类中)
server.port=8081
server.tomcat.uri-encoding=UTF-8
server.servlet.context-path=/crud
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
- 编写一个
EmbeddedServletContainerCustomizer
,2.0以后改为WebServerFactoryCustomizer
:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
@Configuration //实现WebMvcConfigurer接口可以来扩展SpringMVC的功能
public class MyMvcConfig implements WebMvcConfigurer {
//编写WebServerFactoryCustomizer来修改Servlet容器的配置
@Bean
public WebServerFactoryCustomizer webServletFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override //修改相关配置
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8082);
}
};
}
}
代码方式的配置会覆盖配置文件的配置
2. 注册Servlet三大组件
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册三大组件用以下方式
2.1 Servlet
编写Servlet,继承javax.http.HttpServlet
,重载方法
//自定义Servlet
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello Servlet");
}
}
注册servlet
@Configuration
public class MyServerConfig {
//注册自己的servlet
@Bean
public ServletRegistrationBean myServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
return servletRegistrationBean;
}
...
}
2.2 Filter
编写Filter,实现javax.servlet.Filter 接口,实现相关方法
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter.....");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
注册Filter
@Configuration
public class MyServerConfig {
//注册filetr
@Bean
public FilterRegistrationBean myFilter() {
FilterRegistrationBean register = new FilterRegistrationBean(new MyFilter());
//设置拦截路径
register.setUrlPatterns(Arrays.asList("/myServlet","/"));
return register;
}
...
}
2.3 Listener
自定义Listener,有很多不同的Listener,这里写的是监听容器初始化和销毁的Listener
//自定义监听容器创建销毁的listener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed...");
}
}
注册listener
@Configuration
public class MyServerConfig {
//注册Listener
@Bean
public ServletListenerRegistrationBean myListener() {
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(new MyListener());
return servletListenerRegistrationBean;
}
...
}
SpringBoot 自动配置SpringMvc的时候,自动的注册SpringMVC的前端控制器DIspatcherServlet
, DispatcherServletAutoConfiguration
中:
@Bean(
name = {"dispatcherServletRegistration"}
)
@ConditionalOnBean(
value = {DispatcherServlet.class},
name = {"dispatcherServlet"}
)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
//默认拦截的路径是/,拦截所有请求包括静态资源,但是不包括jsp,/*的话,会拦截jsp
//可以通过spring.mvc.servlet.path来修改
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
3. 使用其他的嵌入式容器
SpringBoot默认使用的是Tomcat,并且支持Jetty(使用与长连接,比如通话,聊天)、Undertow(不支持jsp)、Netty
如果要换成其他的就把Tomcat的依赖排除掉,然后引入其他嵌入式Servlet容器的以来,如Jetty,Undertow,Netty
使用jetty
<!--引入web模板-->
<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>
<!--引入其他Web容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
使用Undertow
<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>
<!--引入其他Web容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
4. 嵌入式Servlet容器自动配置原理
查看web容器自动配置类
2.0以下是:EmbeddedServletContainerAutoConfiguration,2.0以下是ServletContainer,2.0以上是WebServer
ServletWebServerFactoryAutoConfiguration
:嵌入式的web服务器自动配置类
@Configuration(
proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
//看这里
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
EmbeddedTomcat.class
@Configuration(
proxyBeanMethods = false
)
//判断当前是否引入了Tomcat依赖;
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
/**
*判断当前容器没有用户自己定义ServletWebServerFactory:嵌入式的web服务器工厂;
*作用:创建嵌入式的web服务器
*/
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedTomcat {
...
}
ServletWebServerFactory
:嵌入式的web服务器工厂
@FunctionalInterface
public interface ServletWebServerFactory {
//获取嵌入式的servlet容器
WebServer getWebServer(ServletContextInitializer... initializers);
}
嵌入式servlet容器工厂(ServletWebServerFactory 实现类):
WebServer:嵌入式Servlet容器
嵌入式容器工厂和容器的对应,是依靠相应的@ConditionalOnClass
相应的注解实现的,比如Tomcat
以TomcatServletWebServerFactory
为例,下面是TomcatServletWebServerFactory类
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
//创建Tomcat
Tomcat tomcat = new Tomcat();
//配置Tomcat的基本环境,(tomcat的配置都是从本类获取的,tomcat.setXXX)
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);
}
//将配置好的Tomcat传入进去,返回一个WebServer;并且启动Tomcat服务器
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
我们对嵌入式容器的配置修改是怎么生效的?
5. 配置修改原理
EmbeddedServletContainerCustomizer
:定制器帮我们修改了Servlet容器的配置
在像容器中注册ServletWebServerFactoryAutoConfiguration
时,也注册了BeanPostProcessorsRegistrar
BeanPostProcessorsRegistrar
:后置处理器注册器(给容器注入一些组件)
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
public BeanPostProcessorsRegistrar() {
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
}
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (this.beanFactory != null) {
//给容器注册了这两个组件
this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class);
this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class);
}
}
...
}
webServerFactoryCustomizerBeanPostProcessor
:嵌入式Servlet容器定制器的后置处理器,在bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
....
//在Bean初始化之前
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//判断添加的Bean是不是WebServerFactory类型的,是就执行postProcessBeforeInitialization
if (bean instanceof WebServerFactory) {
this.postProcessBeforeInitialization((WebServerFactory)bean);
}
return bean;
}
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
customizer.customize(webServerFactory);
});
}
...
}
使用配置文件来配置Servlet容器,底层也是用的EmbeddedServletContainerCustomizer
嵌入式servlet容器定制器来完成配置的。
总结:
- SpringBoot根据导入的依赖情况,给容器中添加相应的XXXServletWebServerFactory
- 容器中某个组件要创建对象就会惊动后置处理器
webServerFactoryCustomizerBeanPostProcessor
,只要是嵌入式的是Servlet容器工厂,后置处理器就会工作 - 后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法给工厂添加配置
6. 嵌入式Servlet容器启动原理
什么时候创建嵌入式的servlet容器工厂?什么时候获取嵌入式的servlet的容器并启动tomcat?
获取嵌入式的Servlet容器工厂步骤:
- SpringBoot应用启动运行run方法
refreshContext(context)
SpringBoot刷新IOC容器(创建IOC容器对象,并初始化容器,创建容器中的每一 个组件),根据当前环境创建IOC容器
- refresh(context) 刷新刚才创建好的ioc容器,和spring中的一样
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
- onRefresh() web的ioc容器重写了onRefresh方法,查看
ServletWebServerApplicationContex
t类的onRefresh
方法,在方法中调用了this.createWebServer()
,创建web容器
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
**createWebServer
**中,获取嵌入式的web容器工厂并且创建Servle容器对象
5. 接下来就是上面的上面的相关配置流程,在创建web容器工厂时会触发webServerFactoryCustomizerBeanPostProcessor
6. 使用容器工厂获取嵌入式的Servlet容器
7. 嵌入式的Servlet容器创建对象并启动Servlet容器
8. 嵌入式的Servlet容器启动后,再将ioc容器中剩下没有创建出的对象获取出来(Controller,Service等)
也就是说,IOC容器启动的时候,创建并启动Servlet容器
7. 使用外置的Servlet容器
使用嵌入式的Servlet容器
- 优点:方便、快捷
- 缺点:默认不支持JSP,优化定制比较复杂(使用定期器,在配置文件中修改配置,或者使用
WebServerFactoryCustomizer
,也可以自定义WebServerFactory
)
7.1 步骤
- 将项目的打包方式改为war
- 编写一个类继承**
SpringBootServletInitializer
**,并重写configure方法,调用参数的sources方法,将springboot启动类传过去
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
- 将嵌入式的tomcat的依赖范围改为provided
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
.....
</dependencies>
- 创建web应用的目录结构,
webapp
目录以及里面的WEB-INF目录和web.xml
添加webapp目录,和WEB-INF/web.xml文件 - 最后就可以把项目打包成war放到tomcat中了, 在IDEA中可以这样配置
在创建SpringBoot项目时,使用Spring Initializr创建,选择打包方式为war,1,2,3步骤会自动配置。
7.2 原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包::启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器
- Servlet3.0标准规定,应用启动时,扫描所有jar包中
META-INF/services/javax.servlet.ServletContainerInitializer
文件中指定的类并加载 - 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
- 在spring-web-xxx.jar包中的
META-INF/services
下有javax.servlet.ServletContainerInitializer
这个文件,文件中的类是:
org.springframework.web.SpringServletContainerInitializer
对应的类:
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
....
}
-
SpringServletContainerInitializer
将@HandlesTypes(WebApplicationInitializer.class)
标注的所有这个类型的类都传入到onStartup
方法的Set<Class<?>>
,为这些WebApplicationInitializer类型的类创建实例; - 每一个WebApplicationInitializer都调用自己的onStartup方法
WebApplicationInitializer
的实现类- 我们的
SpringBootServletInitializer
的类会被创建对象,并执行onStartup方法 -
SpringBootServletInitializer
实例执行onStartup
的时候会createRootApplicationContext
;创建容器
public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
if (rootApplicationContext != null) {
servletContext.addListener(new SpringBootServletInitializer.SpringBootContextLoaderListener(rootApplicationContext, servletContext));
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
createRootApplicationContext
:创建容器
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//调用configure方法,自定义的SpringBootServletInitializer子类重写了这个方法,将SpringBoot的主程序类传入了进来
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
application.setRegisterShutdownHook(false);
//调用run方法,启动spring应用
return this.run(application);
}
- Spring的应用就启动并且创建IOC容器
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新IOC容器
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
启动Servlet容器,再启动SpringBoot应用