Spring Boot 20天入门(day5)
Springboot嵌入式Servlet容器
Springboot默认使用Tomcat作为嵌入式的Servlet容器。
Springboot嵌入式Servlet容器配置修改
1)、properties配置文件修改
// 修改请嵌入式Servlet容器启动端口号
server.port=9527
// 配置访问默认路径
server.context-path=/crud
2)、WebServerFactoryCustomizer(web服务器工厂定制器)
// 需要写在Spring的配置类中,并添加到容器中,重写customizer方法,实现自我定制
@SpringBootConfiguration
public class MyServerConfig {
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> customizer(){
return factory -> factory.setPort(9888);
}
}
注册Servlet三大组件(Servlet,Filter,Listener)
@SpringBootConfiguration
public class MyServerConfig {
//配置三大组件
// Servlet
@Bean
public ServletRegistrationBean<MyServlet> myServlet() {
ServletRegistrationBean<MyServlet> registrationBean = new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
return registrationBean;
}
// filter
@Bean
public FilterRegistrationBean<MyFIlter> myFiltern() {
FilterRegistrationBean<MyFIlter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFIlter());
registrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
return registrationBean;
}
// listener
@Bean
public ServletListenerRegistrationBean<MyLIstener> myListener() {
ServletListenerRegistrationBean<MyLIstener> listener = new ServletListenerRegistrationBean<>();
listener.setListener(new MyLIstener());
return listener;
}
}
Springboot帮我们自动配置SpringMVC的时候,自动注册SpringMVC的前端控制器:DispatchServlet
@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());
// 默认拦截 : / 所有请求,包括静态资源 ,但不拦截jsp请求,/*会拦截
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
切换嵌入式Servlet容器
以下以jetty为例:
首先将tomcat的依赖排除,然后引入jetty的依赖,其他容器也是如此
<dependencies>
<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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-jetty</artifactId>
</dependency>
嵌入式Servlet容器自动配置原理
Springboot 通过运行主程序类的main()方法来启动Springboot应用,应用启动后调用ServletWebServerApplicationContext这个类中的createWebServer()
方法:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
该方法最终能获得一个与当前应用所导入的Servlet类型相匹配的web服务工厂定制器,假设你导入的Servlet依赖是Tomcat:
那么最终会生成tomcat web
服务工厂定制器,定制Servlet容器配置,并通过上述代码中的getWebServer方法创建对应的Servlet容器,并启动。
this.webServer = factory.getWebServer(getSelfInitializer());
然后代码会执行来到EmbeddedWebServerFactoryCustomizerAutoConfiguration(嵌入式web服务工厂定制器自动配置类)
:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
该自动配置类和其他的一样,会标注一个注解,用于从配置文件中获取配置信息。
// 从配置文件中获取前缀为server的配置信息
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {}
然后,根据导入的配置,该配置类会自动创建相应类型的容器工厂定制器,以Tomcat为例:
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration(proxyBeanMethods = false)
// 导入的依赖是Tomcat,创建Tomcat的定制器
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
这样,Tomcat的定制器就创建完成了,对应的服务配置类也被添加到容器中。
然后,Springboot会调用一个WebServerFactoryCustomizerBeanPostProcessor
(web服务工厂定制器组件的后置处理器)的类,该类负责在容器配置之前完成初始化操作:
该类会先从容器中拿到配置好的嵌入式web服务工厂定制器:
@SuppressWarnings({ "unchecked", "rawtypes" })
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
经过调试,改方法从IOC容器中拿到5个定制器,包括前面创建的TomcatWebServerFactoryCustomizer
定制器:
获得所有的定制器后,后置处理器会调用定制器的customize
方法来对嵌入式Servlet容器进行配置(默认或自定义的配置)
@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
配置器的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());
}
Tomcat的WebServer工厂类创建Tomcat对象实例,进行属性配置,引擎设置等等:
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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);
return getTomcatWebServer(tomcat);
}
到此,嵌入式的Servlet容器就配置完成了。
嵌入式Servlet容器启动原理
上面说到,嵌入式的Servlet容器配置完成了,接下来会生成tomcat server对象,在构造函数里面调用一个我们熟悉的initialize()
方法
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();、
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), 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 shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
在initialize()
方法中,tomcat初始化完毕,会调用start()
方法。
这样,嵌入式的tomcat容器就被启动起来了。
总结
1、Spring Boot 根据导入的依赖信息,自动创建对应的web服务工厂定制器;
2、web服务工厂定制器组件的后置处理器获取所有类型为web服务工厂定制器的组件(包含实现WebServerFactoryCustomizer接口,自定义的定制器组件),依次调用customize()定制接口,定制Servlet容器配置;
3、嵌入式的Servlet容器工厂创建tomcat容器,初始化并启动容器。
以上…