1. 修改配置信息
- 配置信息
#servlet容器的修改
server.port=80888
在配置文件中修改服务器端口,配置文件绑定的配置类是
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
@Override
public 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()));
}
}
- 使用代码
EmbeddedServletContainerCustomizer
扩展
实现EmbeddedServletContainerCustomizer接口,将扩展类注册到容器中;
/**
* 代码扩展servlet容器配置
*/
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
//使用匿名内部类,创建一个实现类
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8888);
}
};
}
两种方式的原理都是一致的,ServerProperties也是实现了EmbeddedServletContainerCustomizer类,将配置文件的内容赋值到container中。
2. 注册servlet三大组件
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册三大组件用以下方式
package cn.mh.sb.webcrud.servlets;
/**
* 用于注册servlet三大组件
*
* @author: mahao
* @date: 2019/11/24
*/
@Configuration
public class Config {
//注册servlet
@Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(new MyServlet(), "/myservlet");
return bean;
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean() {
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
servletListenerRegistrationBean.setListener(new MyListener());
return servletListenerRegistrationBean;
}
}
3. 使用其他servlet容器
移除tomcat启动器,引入jetty启动器;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--移除tomcat依赖-->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入jetty依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
4. 嵌入式servlet容器配置原理
内置的容器配置类是 EmbeddedServletContainerAutoConfiguration
类,
@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)//如果满足条件,则加载这个EmbeddedTomcat表示是tomcat的类
public static class EmbeddedTomcat {
//注册TomcatEmbeddedServletContainerFactory是工厂类。
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
EmbeddedServletContainerAutoConfiguration里面有三个静态类,分别是三个内嵌的servlet容器,会根据引入的依赖去选择加载那个内嵌的服务器容器,这里是使用tomcat;
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
TomcatEmbeddedServletContainerFactory的方法getEmbeddedServletContainer()
是获取servlet容器的:
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
//设置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);
//获取servlet容器;
return getTomcatEmbeddedServletContainer(tomcat);
}
//将tomcat封装成TomcatEmbeddedServletContainer
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat 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;
initialize();//重要额初始化方法
}
//初始化方法
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
// yet
removeServiceConnectors();
// Start the server to trigger initialization listeners
this.tomcat.start();//启动了tomcat容器,所以会在获取容器的时候,启动服务。
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
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 shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
通过上面的分析,我们好奇的是tomcat的什么时候会被触发启动,也就是什么时候调用TomcatEmbeddedServletContainerFactory的方法getEmbeddedServletContainer()
的方法,通过在这个方法上打断点分析,而且还有,我们配置的 EmbeddedServletContainerCustomizer
什么时候会被解析去获取参数。先观察什么时候会被启动服务器;
简单的就是:
//EmbeddedWebApplicationContext类,他是AbstractApplicationContext子类,他重写了父类的refresh()方法,这个方法是执行在所有bean初始化结束后,供给子类实现的。
@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.
//EmbeddedWebApplicationContext重写了这个方法,去创建并且启动服务器容器;
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();
}
}
}
//EmbeddedWebApplicationContext类重写的方法;
@Override
protected void onRefresh() {
super.onRefresh();
try {
//创建内嵌的servlet容器
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
//创建的方法
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
//从spring容器中获取containerFactory工厂,进而去调用容器创建和初始化。
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();
}
上面分析了servlet容器的创建过程,知道servlet容器是ioc创建的时候,留下的钩子方法 onRefresh()
方法,让子类去实现自定义的扩展功能。但是我们仍然不知道,我们自己写的servlet容器的扩展配置类是如何被加载和使用的,我们需要连接spring的一个组件 BeanPostProcessor
,这个是在bean初始化完成前后会执行的方法,而且是在 onrefresh()
方法之前,所以我们研究一下;
在servlet容器的配置类上有一个注解,@Import
是导入BeanPostProcessorsRegistrar
要去注册的类,我们查看下registerBeanDefinitions
方法:
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
registerBeanDefinitions()
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
//注册一个后置处理bean,EmbeddedServletContainerCustomizerBeanPostProcessor
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
查看这个类的方法:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ConfigurableEmbeddedServletContainer) {
//执行配置类赋值
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
//发现没有,是遍历所有的容器中的EmbeddedServletContainerCustomizer,然后取出来执行他们接口中定义的方法;getCustomizers()就是从BeanFactory中取出EmbeddedServletContainerCustomizer类型的组件。
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
//看他们的customer方法,就是我们用来实现的。
/**
* 代码扩展servlet容器配置
*/
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
//使用匿名内部类,创建一个实现类
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8888);
}
};
}
//对于servlet容器默认的配置类,ServerProperties,他也是EmbeddedServletContainerCustomizer的子类;
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
配置步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的
EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;
EmbeddedServletContainerCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法
启动步骤:
1)、SpringBoot应用启动运行run方法
2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一
个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:
AnnotationConfigApplicationContext
3)、refresh(context);刷新刚才创建好的ioc容器;
4)、 onRefresh(); web的ioc容器重写了onRefresh方法
5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();
6)、获取嵌入式的Servlet容器工厂:
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件
TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
8)、嵌入式的Servlet容器创建对象并启动Servlet容器;