一.springboot内置tomcat服务器.
- 方法一:修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】);
server.port=8081
server.context‐path=/crud
server.tomcat.uri‐encoding=UTF‐8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
- 方法二:编写一个EmbeddedServletContainerCustomizer,2.0以后改为WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer() {
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8088);
}
};
}
二.springboot配置servlet三大组件 Servlet、Filter、Listener
1.Servlet
- 自定义servlet
package com.wcy.springboot.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
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("这是servlet请求");
}
}
- 把自定义servlet添加到容器
//添加servlet容器
@Bean //添加到容器
public ServletRegistrationBean myServlet(){
ServletRegistrationBean<Servlet> registrationBean = new ServletRegistrationBean<>(new Myservlet(),"/myservlet");
registrationBean.setLoadOnStartup(1);//设置启动顺序
return registrationBean;
}
2.Filter
- 自定义Filter过滤器
package com.wcy.springboot.servlet.filter;
import javax.servlet.*;
import java.io.IOException;
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("被拦截");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
- 注册过滤器到容器
//添加过滤器
@Bean
public FilterRegistrationBean myFiter(){
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/*","/myservlet"));//转为集合
return registrationBean;
}
3.Listener
- 自定义监听器
package com.wcy.springboot.servlet.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("容器初始化加载");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("容器关闭时执行");
}
}
- 把监听器添加到容器
//添加监听器
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<EventListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>();
return servletListenerRegistrationBean;
}
三.切换springboot内置的服务器
tomcat、jetty、undertow三个内置服务器
点击ConfigurableWebServerFactory按Ctrl H可以查看这个类的实现了哪些类的树,三大服务器就继承了ConfigurableWebServerFactory,所以配置tomcat也就配置其他两个服务器的配置。
Tomcat:
直接引入web.start时默认就是tomcat
<!--引入web模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
切换jetty
排除掉tomacat依赖 导入jetty依赖
<!--引入web模块-->
<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>
<!--引入jetty服务器-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
切换undertow
<!--引入web模块-->
<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>
<!--引入undertow服务器-->
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
三.嵌入式servlet原理
1.查看web容器自动配置类
2.0以下是:EmbeddedServletContainerAutoConfiguration
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 {
public ServletWebServerFactoryAutoConfiguration() {
}
查看嵌入式tomcat
@Configuration(
proxyBeanMethods = false
)
//判断当前是否引入了Tomcat依赖;
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
/**
*判断当前容器没有用户自己定义ServletWebServerFactory:嵌入式的web服务器工厂;
*作用:创建嵌入式的web服务器
*/
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedTomcat {
EmbeddedTomcat() {
}
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll((Collection)connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers().addAll((Collection)contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers().addAll((Collection)protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
ServletWebServerFactory:嵌入式的web服务器工厂
查看ServletWebServerFactory 然后按 ctrl H查看实现类情况
所以通过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);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);//启动tomcat服务器
}
我们对嵌入式容器的配置修改是怎么生效的?
那就要看ServletWebServerFactoryAutoConfiguration(嵌入式服务器自动配置)导入了ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,作用:后置处理器注册器(也是给容器注入一些组件)
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);
}
}
查看其中一个组件:
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容器进行属性赋值;
((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
customizer.customize(webServerFactory);
});
}
关于配置文件是如何设置的,参考EmbeddedWebServerFactoryCustomizerAutoConfiguration类,最后还是使用上面的方
总结
- SpringBoot根据导入的依赖情况,给容器中添加相应的XXXServletWebServerFactor
- 容器中某个组件要创建对象就会惊动后置处理器 webServerFactoryCustomizerBeanPostProcesso
只要是嵌入式的是Servlet容器工厂,后置处理器就会工作 - 后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法给工厂添加配置
嵌入式Servlet容器启动原理
- SpringBoot应用启动运行run方法
- 153行,创建IOC容器对象,根据当前环境创建
- 156行,刷新IOC容器
- 刷新IOC容器中272行,onRefresh();web的ioc容器重写了onRefresh方法,查看ServletWebServerApplicationContext类的onRefresh方法,在方法中调用了this.createWebServer();方法创建web容器
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
98行获取嵌入式的web容器工厂
- 接下来就是上面的上面的相关配置流程,在创建web容器工厂时会触发webServerFactoryCustomizerBeanPostProcessor
- 然后99行使用容器工厂获取嵌入式的Servlet容器
- 嵌入式的Servlet容器创建对象并启动Servlet容器;
- 嵌入式的Servlet容器启动后,再将ioc容器中剩下没有创建出的对象获取出来(Controller,Service等);
四。使用外置服务器(tomcat)
在创建项目时使用Spring Initializr创建选择打包方式为war,1,2,3步骤会自动配置就和以前一样只不过方式变成war的方式
外置服务器是通过这个类来启动springboot启动类的
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(HelloSpringBootWebApplication.class);
}
}
1.创建war包方式
2.创建webapp及其下方的xml文件
3.添加服务器
外置服务器的原理:
- Servlet3.0标准ServletContainerInitializer扫描所有jar包中METAINF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载
- 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;(其实就是在外置服务器启动的时候顺便被该注解的类加载 springboot刚好就写了这个方法)
package com.wcy;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootWebJspApplication.class);
}
}
- 在spring-web-xxx.jar包中的METAINF/services下有javax.servlet.ServletContainerInitializer这个文件
org.springframework.web.SpringServletContainerInitializer
- 点击其对应的类 会加载WebApplicationInitializer这个类
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
-
SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例
-
每一个WebApplicationInitializer都调用自己的onStartup方法
-
点击WebApplicationInitializer按住atrl +H
我们就可以看到springboot写的ServletInitialize类 -
相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
-
ServletInitialize的父类SpringBootServletInitializer中的createRootApplicationContext就会确定服务器
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, null);
builder.environment(environment);
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
builder = configure(builder);
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(!application.getSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return run(application);
}
- Spring的应用就启动并且创建IOC容器(就和springboot启动内置服务器一样了)
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新IOC容器
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}