Spring Boot核心技术系统学习总结(六)web开发03
主要内容:
1.嵌入式Servlet容器配置修改
2.注册servlet三大组件
3.切换其他嵌入式Servlet容器
4.嵌入式Servlet容器自动配置原理
5.嵌入式Servlet容器启动原理
6.使用外部Servlet容器&JSP支持
7.外部Servlet容器启动SpringBoot应用原理
一.嵌入式Servlet容器配置修改
1.Spring Boot内嵌的tomcat配置依赖图:(快捷键ctrl+shift+alt+u查看依赖树)
内嵌tomcat依赖图:
2.如何定制修改自带的tomcat容器属性配置?
第一种方式:配置文件中配置容器属性,具体查看SpringBoot的Server配置文件ServerProperties.java
server.port=8081
server.servlet.context-path=/crud
修改tomcat属性配置:
server.tomcat.uri-encoding=UTF-8
配置tomcat源码:
void customizeTomcat(ServerProperties serverProperties,
TomcatEmbeddedServletContainerFactory factory) {
if (getBasedir() != null) {
factory.setBaseDirectory(getBasedir());
}
factory.setBackgroundProcessorDelay(Tomcat.this.backgroundProcessorDelay);
customizeRemoteIpValve(serverProperties, factory);
if (this.maxThreads > 0) {
customizeMaxThreads(factory);
}
if (this.minSpareThreads > 0) {
customizeMinThreads(factory);
}
int maxHttpHeaderSize = (serverProperties.getMaxHttpHeaderSize() > 0
? serverProperties.getMaxHttpHeaderSize() : this.maxHttpHeaderSize);
if (maxHttpHeaderSize > 0) {
customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize);
}
if (this.maxHttpPostSize != 0) {
customizeMaxHttpPostSize(factory, this.maxHttpPostSize);
}
if (this.accesslog.enabled) {
customizeAccessLog(factory);
}
if (getUriEncoding() != null) {
factory.setUriEncoding(getUriEncoding());
}
if (serverProperties.getConnectionTimeout() != null) {
customizeConnectionTimeout(factory,
serverProperties.getConnectionTimeout());
}
if (this.redirectContextRoot != null) {
customizeRedirectContextRoot(factory, this.redirectContextRoot);
}
if (this.maxConnections > 0) {
customizeMaxConnections(factory);
}
if (this.acceptCount > 0) {
customizeAcceptCount(factory);
}
if (!ObjectUtils.isEmpty(this.additionalTldSkipPatterns)) {
factory.getTldSkipPatterns().addAll(this.additionalTldSkipPatterns);
}
if (serverProperties.getError()
.getIncludeStacktrace() == ErrorProperties.IncludeStacktrace.NEVER) {
customizeErrorReportValve(factory);
}
}
第二种方式:在MvcConfig类中添加组件
二.注册servlet三大组件
1.Servlet三大组件(容器级别的过滤):servlet\listener\filter
2.容器配置类
package com.springboot.web.serverconfg;
import com.springboot.web.filter.MyFilter;
import com.springboot.web.listener.MyLisener;
import com.springboot.web.servlet.MyServlet;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
/**
* @author Jupiter
* @date 2019/6/22-20:28
* @description 关于内嵌容器的相关配置
*/
@Configuration
public class MyServerConfig {
//注册三大组件:Listener, Servlet, Filter
@Bean
public ServletListenerRegistrationBean myLister(){
//监听容器级别的上下文
ServletListenerRegistrationBean<MyLisener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyLisener());
return servletListenerRegistrationBean;
}
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return filterRegistrationBean;
}
@Bean
public ServletRegistrationBean myServletRegisteryBean(){
//设置过滤的请求,访问localhost:8089/crud/myServlet
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
//添加内嵌容器的属性,该bean的配置会覆盖application.properties的配置
EmbeddedServletContainerCustomizer containerCustomizer = new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8089);//application.properties的配置为server.port=8081,实际启动为8089
//如果application.properties配置了ContextPath,而这里没配置,那么application里配置的也无效
container.setContextPath("/crud");
}
};
return containerCustomizer;
}
}
自定义servlet:
public class MyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这是容器级别的过滤,不会走到mvc的拦截器登录认证,就会返回该响应
resp.getWriter().write("MyServlet!");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
自定义filter:
public class MyFilter implements javax.servlet.Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("到了过滤器");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
自定义lisener:
public class MyLisener implements javax.servlet.ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("监听到容器启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("监听到容器销毁");
}
}
3.Spring Boot的默认容器配置源码:Spring Boot在自动MVC配置之前,默认配置了前端控制器:DispatcherServlet
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
//静态内部类DispatcherServletRegistrationConfiguration
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
private final ServerProperties serverProperties;
private final WebMvcProperties webMvcProperties;
private final MultipartConfigElement multipartConfig;
//注册一个dispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
//dispatcherServlet默认拦截this.serverProperties.getServletMapping()所有请求
//默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp
//所以可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
}
}
三.切换其他嵌入式Servlet容器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<!-- 去除依赖内嵌tomcat容器 -- >
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<!-- 加入新的undertow容器 -- >
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
四.嵌入式Servlet容器自动配置原理(源码分析)
1.SpringBoot嵌入式Servlet容器自动配置类:EmbeddedServletContainerAutoConfiguration.java
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class) //导入bean后置处理器,在bean初始化后,未赋值前做工作
public class EmbeddedServletContainerAutoConfiguration {
/**
* 以tomcat为例,当使用tomcat作为容器时,就启用以下“嵌套”配置
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class }) //判断当前是否引入了Tomcat依赖
//当没有自定义的嵌入式Servlet工厂时,嵌入式容器生效
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
//给容器中添加Tomcat嵌入式容器工厂
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
}
2.创建的Tomcat嵌入式容器工厂实现了EmbeddedServletContainerFactory接口:
public class TomcatEmbeddedServletContainerFactory
extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
//其中TomcatEmbeddedServletContainerFactory重写了获取Servlet容器的方法
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
Tomcat tomcat = new 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容器,并启动Tomcat
return getTomcatEmbeddedServletContainer(tomcat);
}
3.前面分析了,BeanPostProcessorsRegistrar.class //导入bean后置处理器,在bean初始化后,未赋值前做工作
public class EmbeddedServletContainerCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
private List<EmbeddedServletContainerCustomizer> customizers;
//获取定义的定制器,这些容器定制器有两种方式创建:ServerProperties读取配置文件
//与在容器中加入嵌入式容器定制器@Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
//读取预设的容器创建属性
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
}
综上:SpringBoot嵌入式Servlet容器自动配置原理(步骤)如下:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的
EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会通知后置处理器;
EmbeddedServletContainerCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法,让配置的容器属性生效
五.嵌入式Servlet容器启动原理(什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat)
SpringIoc容器 ==> Servlet容器 ==> Mvc容器组件
详细源码分析:(嵌入式Servlet容器启动原理)
☆ 打断点位置:
1.获取容器工厂处:
2.获取容器位置:
步骤:
1)、SpringBoot应用启动运行 run方法
2)、refreshContext(context);SpringBoot刷新 IOC容器【创建 IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是 web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
3)、内嵌容器启动原理 3-SpringIoc容器刷新时 onrefresh方法调用了创建内嵌 Servlet容器工厂
4)、获取嵌入式的Servlet容器工厂:
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();从ioc容器中获取EmbeddedServletContainerFactory 组件;
TomcatEmbeddedServletContainerFactory创建,后置处理器监控到这个对象,就获取所有的定制器来先定制 Servlet容器的相关配置;
5)、使用容器工厂获取嵌入式的 Servlet容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
6)、嵌入式的 Servlet容器创建对象并启动 Servlet容器;先启动嵌入式的 Servlet容器,再将 ioc容器中剩下没有创建出的对象获取出来;
7)、IOC容器启动创建嵌入式的 Servlet容器
六.使用外部Servlet容器&JSP支持
1.嵌入式Servlet容器不支持JSP,优化定制复杂
2.先建一个SpringBoot应用(war),再生成web应用的文件结构目录:
3.整合外置Tomcat到IDEA,并部署web应用的步骤如下(注:这里的SpringBoot的版本号为:1.5.10.RELEASE):
七.外部Servlet容器启动SpringBoot应用原理
1.SpringBoot的内嵌Servlet容器的启动原理(jar包),执行SpringBoot的主程序类,启动ioc容器,创建嵌入式的Servlet容器
2.外置Servlet的启动(启动服务器->服务器启动SpringBoot应用,再启动Ioc容器)原理:
1)、servlet3.0规则:
服务器启动会创建当前web应用中,每一个jar包的ServletContainerInitializer实例;ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;同时,还可以支持@HandlerTypes,在应用启动的时候加入一些类组件
2)、流程:
|- 启动Tomcat
|- 根据Servlet3.0规范,创建org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer实例
|- SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法,并为这些WebApplicationInitializer类型的类创建实例;
|- 每一个WebApplicationInitializer都调用自己的onStartup方法;
《WebApplicationInitializer实现类SpringBootServletInitializer》
|- SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext,创建容器
|- Spring的应用就启动并且创建IOC容器,源码分析:
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容器
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);
}
}