1.背景
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器。
2.如何定制和修改Servlet容器的相关配置
1.修改与server相关的配置
server.port=8081
server.context-path=/crud
server.tomcat.uri-encoding=UTF-8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
2.编写一个EmbeddedServletContainerCustomizer,2.0以后改为WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
@Configuration
public class MySerConfig {
//配置嵌入式的Servlet的服务器
@Bean
//定制嵌入式的Servlet容器相关的规则
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8083);
}
};
}
代码方式的配置会覆盖配置文件的配置
3.注册Servlet的三大组件
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册三大组件用下面的方式:
3.1 Servlet
先编写一个Servlet类:
public class MyServlet extends HttpServlet {
//处理get请求
@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 MyServlet");
}
}
向容器中添加ServletRegistrationBean:
@Configuration
public class MySerConfig {
//注册三大组件
//注册Servlet
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean registrationBean=new ServletRegistrationBean(new MyServlet(),"/myServlet");
return registrationBean;
}
3.2 Filter
先自定义一个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 process");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
向容器中添加FilterRegistrationBean:还是在上面的MySerConfig中
@Bean
//注册Filter
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean=new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
3.3 Listener
自定义一个Listener:
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized....web应用启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed....当前Web项目销毁");
}
}
向容器中添加ServletListenerRegistrationBean:还是在上面的MySerConfig中
@Bean
//注册Listener
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> myListenerServletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return myListenerServletListenerRegistrationBean;
}
3.4 前端控制器
SpringBoot帮我们自动注册SpringMVC的前端控制器DispatcherServlet,查看DispatcherServletAutoConfiguration:
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
//查看这个getPath,是/
//默认拦截: /所有请求,包静态资源,但是不拦截JSP请求
//可以通过server.servletPath来修改SpringMVC前端控制器默认拦截器请求路径
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
4.替换为其他嵌入式Servlet容器
SpringBoot默认使用的是Tomcat。如果要换成其他的就把Tomcat的依赖排除掉,然后引入其他嵌入式Servlet容器的以来,如Jetty,Undertow。
替换为Jetty:
<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>
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</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>
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
5.嵌入式Servlet容器自动配置原理
2.0以下是:ServletWebServerFactoryAutoConfiguration,嵌入式的web服务器自动配置
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
//导入BeanPostProcessorsRegistrar:Spring注解版,给容器中导入一些组件
//在这个里面导入WebServerFactoryCustomizerBeanPostProcessor:
//后置处理器:bean初始化前后(创建完对象,还没赋值)执行初始化工作
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
ServletWebServerFactoryConfiguration.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);
}
工厂实现类:
WebServer:嵌入式的web服务器实现
以TomcatServletWebServerFactory为例,下面是TomcatServletWebServerFactory类
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
//创建一个Tomcat
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);
//将配置好的Tomcat传入进去,返回一个WebServer;并且启动Tomcat服务器
return this.getTomcatWebServer(tomcat);
}
6.配置修改原理
ServletWebServerFactoryAutoConfiguration在向容器中添加web容器时还添加了一个组件:
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class
...})
BeanPostProcessorsRegistrar:后置处理器注册器(也是给容器注入一些组件)
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
//注册下面两个组件:
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.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);
}
}
}
WebServerFactoryCustomizerBeanPostProcessor:
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
...
//在Bean初始化之前
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//判断bean是不是WebServerFactory
if (bean instanceof WebServerFactory) {
this.postProcessBeforeInitialization((WebServerFactory)bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
//这个部分就是调用自定义的WebServerFactoryCustomizer的customizer方法
((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
customizer.customize(webServerFactory);
});
}
总结:
-
SpringBoot根据导入的依赖情况,给容器中添加相应的XXXServletWebServerFactory
-
容器中某个组件要创建对象就会惊动后置处理器 WebServerFactoryCustomizerBeanPostProcessor只要是嵌入式的是Servlet容器工厂,后置处理器就会工作;
-
后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法给工厂添加配置
7.嵌入式Servlet容器启动原理
首先设置断点,进入Debug模式进行调控,下面是它的部分调用栈:
1.SpringBoot应用启动运行run方法
从main方法进入,调用run方法,直到
2.上面的153行,创建IOC容器对象,根据当前环境创建
3.第156行(第一个步骤),刷新IOC容器
4.(一直调用),刷新IOC容器的272行,onRefresh();web的IOC容器重写了onRefresh方法,查看ServletWebServerApplicationContext类的onRefresh方法,方法中调用了this.createWebServer()创建Web容器。
95行获取嵌入式Web容器工厂:
进入这个方法,可以看到这个获得tomcatServletWebServerFactory:
5.接下来就是在创建Web容器工厂的时候会触发webServerFactoryCustomizerBeanPostProcessor
6.96行(第四步)使用容器工厂获取嵌入式的Servlet容器。
7.嵌入式Servlet容器创建对象并启动Servlet容器(从上面我们知道获得是TomcatServletWebServerFactory,所以调用的是它的getWebServer,这个源码上面章节有,可以看到在这里的是创建Tomcat并启动)。
8.嵌入式Servlet容器启动后,再将IOC容器中剩下没有创建出的对象全取出来(Controller,Service等)。
8.使用外置的Servlet容器
嵌入式Servlet容器:应用打成可执行的jar
优点:简单、便携;
缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义 WebServerFactoryCustomizer】,自己编写嵌入式Servlet容器的创建工厂 );
外置的Servlet容器:外面安装Tomcat---应用war包的方式打包;
8.1 使用方法
1.创建一个项目,将项目的打包方式改为war
2.创建成功后,会发现没有webapp目录,这个需要创建。在idea中点击:
选择Moudles,创建webapp(点击Web项目下的Web,一直执行就会出现webapp)和web.xml(可以看到图中有web.xml创建的提醒标志,这个web.xml就是位于创建的webapp目录下WEB-INF):
3.编写一个类继承SpringBootServletInitializer,并重写configure方法,调用参数source方法SpringBoot启动类传递过去然后返回:
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Demo1Application.class);
}
}
4.然后把tomcat的依赖范围改为provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
5.最后可以把项目打包成war包放在Tomcat中,这个可以在IDEA中配置:要记得选择Deployment将项目的war放进入
6.在IDEA中使用Spring Initializer创建选择打包方式为war包,2,3步骤会自动创建。
如果启动tomcat,报了一大堆错误,不妨把Tomcat改为更高的版本试试,如果你项目中的Filter是继承了HttpFilter,请使用tomcat9版本,9以下好像没有HttpFilter
8.2 原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;
servlet3.0(Spring注解版): 查看8.2.4 Shared libraries / runtimes pluggability的规则:
(1)服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:
(2)ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为 javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
(3)还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
流程:
1.启动Tomcat容器
2.在spring-web-xxx.jar包中的META-INF/services下有javax.servlet.ServletContainerInitializer这个文件,文件中的类是:
对应的类是:
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
3.SpringServletContainerInitializer将@HandlesTypes(WebAplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>集合中。为这些WebApplicationInitializer类型创建实例。
4.每一个WebApplicationInitializer都调用自己的onStartup方法。
5.WebApplicationInitializer的实现类:
6.相当于我们的SpringBootServletInitializer的类被创建,并执行onStartup方法(因为我们自己编写的继承了这个)
7.SpringBootServletInitializer实例执行的时候会执行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);
//启动Spring应用
return this.run(application);
}
8.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);
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应用