目录
4.SpringBoot的嵌入式Servlet容器
Spring 默认的Servlet容器是:Tomcat, 当前SpringBoot 2.3.6 的版本是对应 tomcat9
1.嵌入式Servlet容器配置修改
- 1.通过全局配置文件修改
- 如果带了具体的服务器名称则是单独对该服务器进行设置,比如 server.tomcat.xxx 就是专门针对tomcat的配置
- 可以通过server.xxx 来进行web服务配置, 没有带服务器名称的则是通用配置
server.port=8080
server.servlet.context-path=/tomcat
- 2.通过WebServerFactoryCustomizer的Bean修改
- 修改server.xxx 配置的相关内容
- 会跟配置文件形成互补
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setPort(8088);
factory.setContextPath("/customTomcat");
}
}
2.注册servlet三大组件
- servlet listener filter
servlet3.0规范提供的注解方式注册
@WebServlet
@WebListener
@WebFilter
1.声明servlet 及映射
@WebServlet(name="HelloServlet",urlPatterns = "/HelloServlet")
/*@WebListener
@WebFilter*/
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("hello servlet!");
}
}
2.加上@ServletComponentScan才会扫描那3个注解
@SpringBootApplication
@ServletComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SpringBoot提供的注册
使用ServletRegistrationBean,FilterRegistrationBean以及ServletListenerRegistrationBean
@Configuration
public class MyWebMvcConfigurer {
@Bean
public ServletRegistrationBean myServlet(){
// 声明一个servlet注册器Bean
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
// 设置相应的servlet
servletRegistrationBean.setServlet(new BeanServlet());
// 设置名字
servletRegistrationBean.setName("BeanServlet");
// 添加映射规则
servletRegistrationBean.addUrlMappings("/BeanServlet");
return servletRegistrationBean;
}
}
3.切换其他嵌入式Servlet容器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--1.排除tomcat-->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--2.依赖jetty
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>-->
<!--2.依赖undertow
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>-->
4.嵌入式Servlet容器自动配置原理
- ServletWebServerFactoryAutoConfiguration servlet容器自动配置类
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 只要依赖任意一个servlet容器都会存在该来ServletRequest
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 启用servet.xxx的所有的配置信息绑定到ServerProperties
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
1.为什么可以根据配置的依赖自动使用对应的servlet容器?
通过@Import 导入Embeddedxxxx
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
每个Embeddedxxxx 中都配置了相应的@ConditionalOnClass,会根据当前servlet容器start依赖判断classpath是否存在对应的类, 如果存在就使用对应的servlet容器。 比如EmbeddedTomcat:
2.怎么根据配置文件中server.xxx 以及 WebServerFactoryCustomizer 去设置servlet容器属性?
- ServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer ,说明它也是定制servlet容器的
- Servlet容器配置文件通用定制器
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
根据配置文件中server.xxx 来进行定制servlet容器
ServletWebServerFactoryCustomizer类中的customize方法
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
// if(serverProperties.getPort()!=null){
// factory.setPort(serverProperties.getPort())
//}
map.from(this.serverProperties::getPort).to(factory::setPort);
map.from(this.serverProperties::getAddress).to(factory::setAddress);
map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
map.from(this.serverProperties::getSsl).to(factory::setSsl);
map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
map.from(this.serverProperties::getCompression).to(factory::setCompression);
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
}
- TomcatServletWebServerFactoryCustomizer Tomcat配置文件定制器
- 根据配置文件中servet.tomcat.xxxx 定制servlet容器
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
TomcatServletWebServerFactoryCustomizer 类中的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());
}
怎么让所有的WebServerFactoryCustomizer Bean一一调用的
BeanPostProcessorsRegistrar实现了ImportBeanDefinitionRegistrar 会提供一个方法,并且提供BeanDefinitionRegistar 让我们去注册bean
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
实现了BeanPostProcessor接口,并实现了两个方法在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) {
((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
customizer.customize(webServerFactory);
});
}
当对应Embeddedxxxx 启用时, 就会在里面配置一个WebServerFactory 类型的一个Bean, 负责创建对应的容器和启动
TomcatServletWebServerFactory 是WebServerFactory类型的一个Bean
我们具体看一下初始化之后调用的方法
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
// 调用getCustomizers()
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
- 1.调用getCustomizers()
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
- 2.getWebServerFactoryCustomizerBeans() 就获取了所有实现了WebServerFactoryCustomizer接口的Bean
- 获取 自定义的,和ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
//从spring容器中拿到类型为WebServerFactoryCustomizer的bean
//包括自动配置类中的,也包括我们自己定制化的
return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
- 3.在invoke方法中循环调用所有实现了WebServerFactoryCustomizer接口的Bean的customize方法进行一一定制
3.嵌入式servlet容器是怎么启动的
TomcatServletWebServerFactory
- 自动配置中根据不同的依赖, 启动对应一个Embeddedxxxx, 然后配置一个对应的servlet容器工厂类, 比如tomcat:TomcatServletWebServerFactory
- 在springboot应用启动的时候 , 就会调用容器refresh方法, onRefresh , 调用getWebServer, 创建servlet及启动(spring源码)
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
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);
return this.getTomcatWebServer(tomcat);
}
getTomcatWebServer方法
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
}
TomcatWebServer构造方法会调用this.initialize(),initialize方法中调用this.tomcat.start();启动tomcat
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
this.initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
synchronized(this.monitor) {
try {
this.addInstanceIdToEngineName();
Context context = this.findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
this.removeServiceConnectors();
}
});
this.tomcat.start();
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
} catch (NamingException var5) {
}
this.startDaemonAwaitThread();
} catch (Exception var6) {
this.stopSilently();
this.destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", var6);
}
}
}
5.使用外部Servlet容器
- 外部servlet容器
- 服务器、本机 安装tomcat 环境变量...
- 部署: war---运维--->tomcat webapp startup.sh 启动
- 开发: 将开发绑定本地tomcat
- 开发 、 运维 服务器配置 war
- 内嵌servlet容器:
- 部署: jar---> 运维---java -jar 启动
使用:
1. 下载tomcat服务
2.设置当前maven项目的打包方式
<!--打包方式 默认是jar-->
<packaging>war</packaging>
3.让tomcat相关的依赖不参与打包部署 ,因为外置tomcat服务器已经有这些jar包
<!--让它不参与打包部署-->
<dependency>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
<scope>provided</scope>
</dependency>
4. 为了让它支持springboot需要加上: 才能启动springboot应用
// 当tomcat启动时就会调用configure方法, 从而在springboot启动类的基础启动springboot
// 什么时候调用?
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
5. 在idea中运行
6.外部Servlet容器启动SpringBoot应用原理
tomcat---> web.xml--filter servlet listener 3.0+
tomcat不会主动去启动springboot应用 ,, 所以tomcat启动的时候肯定调用了SpringBootServletInitializer的SpringApplicationBuilder , 就会启动springboot
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder (SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
servlet3.0 规范官方文档: 8.2.4
大概: 当servlet容器启动时候 就会去META-INF/services 文件夹中找到javax.servlet.ServletContainerInitializer, 这个文件里面肯定绑定一个ServletContainerInitializer. 当servlet容器启动时候就会去该文件中找到ServletContainerInitializer的实现类,从而创建它的实例调用onstartUp (SPI规范,数据库驱动也用到了)
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
- @HandlesTypes(WebApplicationInitializer.class).
- @HandlesTypes传入的类为ServletContainerInitializer感兴趣的
- 容器会自动在classpath中找到 WebApplicationInitializer 会传入到onStartup方法的webAppInitializerClasses参数中
- Set> webAppInitializerClasses 这里面也包括之前自己定义的TomcatStartSpringBoot
SpringServletContainerInitializer 的onStartup方法
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// 如果不是接口 不是抽象 跟WebApplicationInitializer有关系 就会实例化
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
// 排序
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
SpringBootServletInitializer的onstartup方法
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) {
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);
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
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);
return this.run(application);
}
在createRootApplicationContext方法中builder = this.configure(builder);
而我们自己定义的TomcatStartSpringBoot重写了configure方法,就会来到我们自己定义的继承的SpringBootServletInitializer的configure方法,将Springboot启动类传入到builder.source
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
// 调用SpringApplication application = builder.build(); 就会根据传入的Springboot启动类来构建一个SpringApplication
public SpringApplication build(String... args) {
this.configureAsChildIfNecessary(args);
this.application.addPrimarySources(this.sources);
return this.application;
}
// 调用 return run(application); 就会帮我启动springboot应用
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext)application.run(new String[0]);
}
它就相当于我们的
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
其实这2个实现类就是帮我创建ContextLoaderListener 和DispatcherServlet
listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--全局参数:spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-core.xml</param-value>
</context-param>
<!--前端调度器servlet-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--设置配置文件的路径-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--设置启动即加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
5.SpringBoot作为单体Web应用的使用
如果需要动态展示Springmvc的数据到页面上需要使用模板引擎技术:
SpringBoot提供以下模板引擎技术的支持:
以Freemarker为例
1.添加freemarker的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.设置freemakrer的全局配置
#如果在生产环境可以设置true
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.suffix=.html
3.添加freemarker的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<#list usernames as username>
<h1>${username}</h1>
</#list>
</body>
</html>
4.对应的控制器
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("index")
public String index(Model model){
model.addAttribute("username","fztx");
return "index";
}
@RequestMapping("list")
public String list(Model model){
List<String> list= Arrays.asList("fztx","zhangsan","lisi");
model.addAttribute("usernames",list);
return "list";
}
}