SpringBoot源码解析(MVC,Tomcat,SPI机制)

Tomcat servlet 区别?

2021-05-26 23:34:04
Tomcat servlet 区别?
servlet是一个接口,Tomcat是一个servlet实现类。同理 Jetty也会实现 Servlet接口。

就像是 jdbc一样,Mysql ,Oracle 都会进行实现。

接口是一种规范,一种标注,仅此而已。

使用内嵌Tomcat servlet

(1)、web.xml 太老旧,不建议。
(2)、@WebServlet 注释,封装的看不到内部代码了,不建议。
(3)、Tomcat api(推荐,可以手动模拟真正的流程)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

SpringBoot创建Tomcat容器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 创建
    在这里插入图片描述

  • 启动
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

SpringMVC内嵌Tomcat启动

只要有类的地方,一定会对类创建对象。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建ContextLoaderListener

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建DispatcherServlet

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

关于 Root 与 Servlet

详情请点击官方文档

DispatcherServlet期望对其自身的配置WebApplicationContext进行扩展(plain的扩展 ApplicationContext)。WebApplicationContext有一个链接 ServletContext和Servlet与它相关联。它也与ServletContext 应用程序绑定在一起,以便应用程序可以使用静态方法RequestContextUtils来查找 WebApplicationContext是否需要访问它。

对于许多应用程序来说,拥有一个WebApplicationContext简单而且足够。也可能具有上下文层次结构,其中一个根WebApplicationContext 在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有其自己的子WebApplicationContext配置。有关上下文层次结构功能的更多信息,请参见的其他ApplicationContext功能。

根WebApplicationContext通常包含需要在多个Servlet实例之间共享的基础结构Bean,例如数据存储库和业务服务。这些Bean被有效地继承,并且可以在Servlet特定的子级中重写(即重新声明),该子级WebApplicationContext通常包含给定本地的Bean Servlet。下图显示了这种关系:
在这里插入图片描述

从以上关系可以得知,若 Controller 中不加入 @Controller 注解,则会浏览器访问不到,URL 映射不到方法。
从而报错:(此案例就是 Controller 层使用 @Service 代替 @Controller)
在这里插入图片描述

因为会找不到对应的ViewResolver、HandlerMapping,箭头关系是向下。例如:若标注了 @Service 就会把 Bean 放入 Root里面,那么它就失去了 ViewResolver、HandlerMapping 的能力。反过来说,只有标注了 @Controller,那么它才会拥有 ViewResolver、HandlerMapping 的能力。

总结:Controller 类必须标注有 @Controller,Service层则标注只要是 @Component 使 @Resource 能够引用到即可。
另外,在 @Service 层也可以使用 @Resource 引用到 Controller Bean。但这样做并不好。

Xml上下文 与 注解上下文

初始化有没有加载Bean?
(1)ClassPathXmlApplicationContext xml = new ClassPathXmlApplicationContext("xxx.xml");
有。因为有调用 refresh() 方法。在构造器中调用
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)AnnotationConfigWebApplicationContext anno = new AnnotationConfigWebApplicationContext();
没有。因为没有调用 refresh() 方法

注解替换 xml

自己来创建 DispatcherServlet,自己来添加 servlet。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

官网注解替换 xml

官方参考文档
在这里插入图片描述
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyWebApplicationInitializer implements WebApplicationInitializer {

@Override
public void onStartup(ServletContext servletContext) {// ServletContext是一个接口,tomcat、jetty 实现

// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);

// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<web-app>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>

<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>

</web-app>

Java SPI

可以加载 /META- INF/services/ 下面的类。
在这里插入图片描述
在这里插入图片描述

tomcat SPI

Tomcat在启动的时候,加载的工程所有目录(引用jar下的所有目录),加载 /META- INF/services/javax. xxx.xxx。
例如:下面会创建 SpringServletContainerInitializer 类。而它又实现了接口 ServletContainerInitializer 重写了 onStartup方法,则就会调用onStartup方法。就像是线程,实现 Runnable 接口重写 run方法,执行 run 方法一样。
在这里插入图片描述
当我们创建对象 SpringServletContainerInitializer,实现了接口 ServletContainerInitializer,重写了 onStartup方法,在调用 onStartup方法之前,发现在这个类上有注解 @HandlesTypes(WebApplicationInitializer.class)

@HandlesTypes:就是为了收集感兴趣的类。比如说:@HandlesTypes(WebApplicationInitializer.class) 注解,有 A、B、C三个类实现了 WebApplicationInitializer 接口,那么就会获取到 A、B、C三个类,并作为 onStartup 方法的第一个参数 @Nullable Set<Class<?>> webAppInitializerClasses。

在这里插入图片描述
此时,就会调用到我们自己的实现类的 onStartup 方法(因为我们实现了 WebApplicationInitializer 接口,会被感兴趣的类收集到)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyWebApplicationInitializer implements WebApplicationInitializer {

@Override
public void onStartup(ServletContext servletContext) {// ServletContext是一个接口,tomcat、jetty 实现

// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);

// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}

总结:Tomcat在启动的时候,加载的工程所有目录(引用jar下的所有目录),加载 /META- INF/services/javax. xxx.xxx。
并创建对象,调用 onStartup 方法,它上面有 @HandlesTypes(WebApplicationInitializer.class) 感兴趣的类收集器会收集到我们的类。
从而感兴趣的类作为第一个参数,循环(反射创建对象,并调用 onStartup 方法)。调用到我们的 MyWebApplicationInitializer.onStartup 方法。

Springboot SPI

再讲述一遍。
/META-INF/spring.factories 文件,加载类。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

把上面的串一下

SpringBoot.run 方法是为了启动 Tomcat,并启动(在上面我们已经分析到)。
Tomcat启动时,会有SPI机制,调用所有实现 WebApplicationInitializer 接口的 onStartup 方法(此时就会有 Servlet)。
SpringBootApplication注解为了实现自动装配,会有SPI机制,加载所有的 auto configuration。

自己 new Tomcat();

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

消息转换失败

来,让我们做一步,让它访问出错
在这里插入图片描述

添加消息转换器

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

消息转换器原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为子类 DelegatingWebMvcConfiguration 上有加注解 @Configuration,所以父类可以使用 @Bean 创建 Bean。

1
2
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

在这里插入图片描述
在这里插入图片描述

此时,就会调用我们自定义配置类的 configureMessageConverters 方法
在这里插入图片描述

2021-05-27 16:50:06 暂记那么多。