文章目录
DispatcherServlet及相关组件注入时机
Springmvc核心是它的DispatcherServlet;
DispatcherServlet继承关系 :
DispatcherServlet是一个servlet,它的生命周期受Tomcat容器控制
Tomcat容器在servlet实例化后会调用每个servlet的init方法进行初始化
DispatcherServlet的init方法在父类HttpServletBean中实现,并定义HttpServletBean#initServletBean方法留给子类扩展,默认在FrameworkServlet实现并调用FrameworkServlet#initWebApplicationContext方法创建Spring容器,再在FrameworkServlet#initWebApplicationContext方法中中调用DispatcherServlet#onRefresh,完成DispatcherServlet的组件注入。
总结: DispatcherServlet#onRefresh
方法会在DispatcherServlet初始化的时候被Tomcat调用
DispatcherServlet#onRefresh源码 :
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);//初始化9个组件
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
//GSCM 2024/1/5 为DispatcherServlet设置初始值,如handlerMappings、ViewResolvers、HandlerAdapters
protected void initStrategies(ApplicationContext context) {
//初始化文件上传解析器
//默认赋值 org.springframework.web.multipart.support.StandardServletMultipartResolver
initMultipartResolver(context);
//初始化国际化语言解析器
//默认赋值 org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
initLocaleResolver(context);
//初始化主题解析器
//默认赋值 org.springframework.web.servlet.theme.FixedThemeResolver
initThemeResolver(context);
//初始化处理映射器
initHandlerMappings(context);
//初始化处理适配器
initHandlerAdapters(context);
//初始化异常处理解析器(@ControllerAdvice)
//默认赋值 org.springframework.web.servlet.HandlerExceptionResolver接口子类
initHandlerExceptionResolvers(context);
//初始化视图转换器
//默认赋值 org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
initRequestToViewNameTranslator(context);
//初始化视图解析器
//默认赋值 org.springframework.web.servlet.ViewResolver接口子类
initViewResolvers(context);
//初始化flash管理器
//默认赋值 org.springframework.web.servlet.support.SessionFlashMapManager
initFlashMapManager(context);
}
initHandlerMappings(context) 方法内部 :
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 首先从容器中获取HandlerMapping类型,赋值给handlerMappings;代码略,默认获取为空
......
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
// 使用默认,默认策略不改动的情况下为: BeanNameUrl, RequestMapping, RouterFunction
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
......
}
getDefaultStrategies:
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
if (defaultStrategies == null) {
try {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
//加载 DispatcherServlet 同级包中的 DispatcherServlet.properties
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
//获取接口名作为key
String key = strategyInterface.getName();
//在 DispatcherServlet.properties 中获取对应的 value 值
String value = defaultStrategies.getProperty(key);
if (value != null) {
// 如果有多个默认值,则以逗号分割为数组
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
// 通过反射构建相应类型的对象实例,并添加进策略列表中
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);// 注册进容器,走bean创建的生命周期
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return Collections.emptyList();
}
getDefaultStrategies就是通过SPI机制,从DispatcherServlet.properties
扫描出来对应接口名称的类,MVC默认定义了三个HandlerMapping实现类:
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
再将扫描出来的类注册进容器;
HandlerMapping
HandlerMapping 定位是通过 url,匹配到合适的 Handler,并将 Handler 和 拦截器链捆绑在 HandlerExecutionChain 对象中,返回给调用方。定义了一个接口,根据请求找到对应的处理器对象 :
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
HandlerMapping分为两个分支来处理
- AbstractHandlerMethodMapping:处理 url 与 Method 级别 handler 的映射
- AbstractUrlHandlerMapping:处理 url 与 类级别 Handler 的映射(例如 Controller 接口的实现类,或者是 HttpRequestHandler 接口的实现类)
它们又统一继承于 AbstractHandlerMapping:
RequestMappingHandlerMapping
RequestMappingHandlerMapping是Spring MVC中最常用的HandlerMapping实现之一。它使用@RequestMapping
注解来确定请求处理器对象。
一个简单的控制器示例:
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable int id, Model model) {
User user = userRepository.findById(id);
model.addAttribute("user", user);
return "user";
}
}
RequestMappingHandlerMapping会扫描所有@Controller注解标记的类,并将所有带有@RequestMapping注解的方法添加到HandlerMapping中。在请求到达服务器时,RequestMappingHandlerMapping将匹配请求路径和请求方法,并将请求映射到适当的处理器方法。
RequestMappingHandlerMapping实现了InitializingBean
接口,因此会该类实例化(DispatcherServlet#onRefresh#initHandlerMappings
方法)后会自动执行它的 afterPropertiesSet
方法 ,主要是扫描@Controller标注的类,解析url对应处理方法的映射关系,把解析结果封装成 RequestMappingInfo
对象注册到父类AbstractHandlerMethodMapping的内部类MappingRegistry中,MappingRegistry类两个主要的属性:
//GSCM 2024/1/10 registry属性,保存着容器内请求路径映射的RequestMappingInfo对应的HandlerMethod
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();// key为RequestMappingInfo,value为HandlerMethod
// 与上面registry参数相似,不过此属性一个url映射可能对应着多个处理器
// key为映射的url路径,value为RequestMappingInfo;保存着所有的controller映射路径;register方法设置值
private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
afterPropertiesSet方法包含默认的方法参数处理器、初始化返回值处理器的设置
:
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
/**
* 就是@ControllerAdvice注解相关的方法,用来做全局异常统一处理的
* 功能:主要就是解析@ControllerAdvice注解的类,将标注有@RequestMapping,@ModelAttribute,@InitBinder的方法放入缓存中,以当前bean为key。
*/
initControllerAdviceCache();
/**
* 初始化参数解析器HandlerMethodArgumentResolver,并把所有的参数解析器都放入
* HandlerMethodArgumentResolverComposite实例中
*/
if (this.argumentResolvers == null) {
//GSCM 2024/1/10 设置默认的方法参数处理器
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
/**
* 也是初始化参数解析器HandlerMethodArgumentResolver,并把所有的参数解析器都放入
* HandlerMethodArgumentResolverComposite实例中
*/
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
/**
* 初始化返回值处理器集合HandlerMethodReturnValueHandler,并把所有的返回值处理器都放入
* HandlerMethodReturnValueHandlerComposite中
*/
if (this.returnValueHandlers == null) {
//GSCM 2024/1/10 设置默认的方法返回值处理器
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
SimpleUrlHandlerMapping
它允许指定URL模式和Handler的映射关系。
使用SimpleUrlHandlerMapping的控制器示例:
public class MySimpleUrlHandlerMapping extends SimpleUrlHandlerMapping {
public MySimpleUrlHandlerMapping() {
Properties mappings = new Properties();
mappings.setProperty("/hello", "helloController");
setMappings(mappings);
}
}
在这个示例中,我们创建了一个名为MySimpleUrlHandlerMapping的自定义HandlerMapping类。我们通过创建一个Properties对象并将请求路径"/hello"映射到控制器名为"helloController"来设置URL映射。
当请求到达服务器时,SimpleUrlHandlerMapping将查找请求路径并将其与已注册的URL模式进行匹配。如果找到匹配项,则返回关联的处理程序对象。
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mav = new ModelAndView("hello");
mav.addObject("message", "Hello, World!");
return mav;
}
}
创建了一个名为HelloController的控制器类,并实现了Controller接口。handleRequest方法将返回一个名为"hello"的视图,并将一个名为"message"的字符串添加到模型中。
将自定义的MySimpleUrlHandlerMapping注册进容器:
@Configuration
public class HandlerMappingConfig {
@Bean
public SimpleUrlHandlerMapping mySimpleUrlHandlerMapping() throws Exception {
MySimpleUrlHandlerMapping handlerMapping = new MySimpleUrlHandlerMapping();
Map<String, Object> urlMap = new HashMap<>();
urlMap.put("/hello", new HelloController());
handlerMapping.setUrlMap(urlMap);
}
}
SimpleUrlHandlerMapping重写了父类ApplicationContextAware:
//重写父类的方法,注册到父类的map中
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
HandlerMethod
SpringMVC 应用启动时会搜集并分析每个 Web 控制器方法,从中提取对应的 "<请求匹配条件,控制器方法>“ 映射关系,形成一个映射关系表保存在一个RequestMappingHandlerMapping的bean 中(详见HandlerMapping)。
然后在客户请求到达时,调用DispatcherServlet#getHandler
遍历HandlerMapping,调用每个HandlerMapping#getHandler
方法返回一个HandlerExecutionChain
(下文解释)
RequestMappingHandlerMapping 处理的主要逻辑在AbstractHandlerMethodMapping#lookupHandlerMethod
,此方法找到请求处理的真实HandlerMethod
lookupHandlerMethod方法逻辑 :
从映射关系表(mappingRegistry属性
)找到相应的控制器方法去处理该请求。在 RequestMappingHandlerMapping 中保存的每个 ”<请求匹配条件,控制器方法>" 映射关系对儿中, "请求匹配条件" 通过 RequestMappingInfo包装和表示
,而 "控制器方法"则通过 HandlerMethod 来包装和表示。
需要Debug调试AbstractHandlerMethodMapping#lookupHandlerMethod
一个 HandlerMethod 对象,可以认为是对如下信息的一个包装 :
信息名称 | 介绍 |
---|---|
Object bean | Web 控制器方法所在的 Web 控制器 bean 。可以是字符串,代表 bean 的名称; 也可以是 bean 实例对象本身。 |
Class beanType | Web 控制器方法所在的 Web 控制器 bean 的类型, 如果该 bean 被代理,这里记录的是被代理的用户类信息 |
Method method Web | 控制器的指向方法 |
Method bridgedMethod | 被桥接的 Web 控制器方法 |
MethodParameter[] parameters | Web 控制器方法的参数信息: 所在类所在方法,参数,索引,参数类型 |
HttpStatus responseStatus | 注解 @ResponseStatus 的 code 属性 |
String responseStatusReason | 注解 @ResponseStatus 的 reason 属性 |
HandlerInterceptor组件
HandlerInterceptor包含三个方法 :
-
preHandle
是在找到处理handler对象的HandlerMapping之后,HandlerAdapter调度handler之前执行。 -
postHandle
是在HandlerAdapter调度handler之后,DispatcherServlet渲染视图之前执行,可以通过ModelAndView来向视图中添加一些信息等,preHandle返回false不执行postHandle。 -
afterCompletion
是在渲染视图结束后执行,主要可以用来进行事后的资源清理。无论 是否发生异常/preHandle返回false 都会执行
其中postHandle和afterCompletion方法是反顺序执行的。也就是说第一个拦截器会最后一个执行。关于HandlerInterceptor的执行顺序我们可以在HandlerExecutionChain类中找到。
完整示例 :
配置自己的Interceptor进容器,需要按照下示例方式
实现WebMvcConfigurer接口,添加@Configuration注解(可免除),在配置类中,重写addIntercepters方法,添加要拦截的url以及url白名单(需要排除拦截的url)
@Component
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private CustomInterceptor customInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor) // 添加拦截器
.addPathPatterns("/**") // 配置拦截请求url( ** 表示拦截所有请求url)
.excludePathPatterns("/hello"); // 排除某些不需要拦截的请求url(即带有/hello请求不会被拦截)
}
}
请求的HandlerInterceptor设置时机 :
- 请求进来到达AbstractHandlerMapping#getHandler
- 调用getHandlerInternal返回HandlerMethod
- 再调用getHandlerExecutionChain返回当前HandlerMethod的HandlerExecutionChain
WebMvcConfigurer
重要的方法 :
addInterceptors
:从该方法名就可以了解到该方法是添加拦截器,即将拦截器交给IOC去执行,拦截器需要拦截的路径以及需要排除拦截的路径在该方法中配置。addResourceHandlers
:该方法的作用是配置静态资源路径。即某些请求需要读取某个路径下的静态资源内容,需要配置该静态资源的路径,通过该方法可以统一给这些请求配置指定静态资源路径 。
addResourceHandlers 示例:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**") // 配置需要添加静态资源的请求url
.addResourceLocations("classpath:/mydata/"); //配置静态资源路径
}
将自己的 HandlerInterceptor 添加进Spring容器 :
@Component
public class CustomInterceptor implements HandlerInterceptor {
//在Controller执行之前调用,如果返回false,controller不执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("---------CustomInterceptor.preHandle--------");
return true;
}
//controller执行之后,且页面渲染之前调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("---------CustomInterceptor.postHandle--------");
}
//页面渲染之后调用,一般用于资源清理操作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("---------CustomInterceptor.afterCompletion--------");
}
}
到此就完成定义自己的一个HandlerInterceptor。
HandlerExecutionChain
HandlerExecutionChain就是对HandlerMethod、HandlerInterceptor的包装。
参数解析器HandlerMethodArgumentResolver
处理器方法参数解析器接口,用来解析请求,得到目标方法需要的参数,主要有 2 个方法:
supportsParameter
:是否能够解析 parameter 指定的参数resolveArgument
:通过请求和 parameter 参数解析得到参数的值
public interface HandlerMethodArgumentResolver {
// 判断当前解析器是否能处理这个parameter这个参数,也就是说是否能够将请求中的数据转换为parameter指定的参数的值
boolean supportsParameter(MethodParameter parameter);
// 从http请求中解析出控制器需要的参数的值
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
实现类 :
其中抽象类AbstractNamedValueMethodArgumentResolver
实现了基本的resolveArgument
方法,并留resolveName
方法给子类调用,方法概要 :
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 获取给定方法参数的命名值。
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
// namedValueInfo.name 是参数的名称字符串,不过该字符串可能是个表达式,需要进一步解析为
// 最终的参数名称,下面的 resolveStringValue 语句就是对该名字进行表达式求值,从而得到解析后的
// 控制器方法参数名称,此名称是从请求上下文中获取相应参数值的关键信息
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);// 要取得参数名称
.......
// 根据控制器方法参数名称从请求上下文中尝试分析得到该参数的参数值,子类实现
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);// 取得值
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
//子类实现,参为null时但处理方法
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
......
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
注册时机 :详见 DispatcherServlet及相关组件注入时机
中的initHandlerAdapters
方法
调用时机
DispatcherServlet#doDispatch
-> ha.handle(processedRequest, response, mappedHandler.getHandler()); // 取得HandlerAdapter并调用以RequestMappingHandlerAdapter为例
-> RequestMappingHandlerAdapter#invokeHandlerMethod
-> ServletInvocableHandlerMethod#invokeAndHandle(webRequest, mavContainer)
-> ServletInvocableHandlerMethod#invokeForRequest
-> InvocableHandlerMethod#getMethodArgumentValues
HandlerAdapter
主要使用的是RequestMappingHandlerAdapter
RequestMappingHandlerAdapter创建时机 :
(DispatcherServlet#onRefresh#initHandlerAdapters(context))
其中包含一个属性 :
private HandlerMethodArgumentResolverComposite argumentResolvers;
HandlerMethodArgumentResolverComposite 中包含着真实的参数解析器:
private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
RequestMappingHandlerAdapter同样实现了InitializingBean
并重写afterPropertiesSet
方法 :
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
/**
* 就是@ControllerAdvice注解相关的方法,用来做全局异常统一处理的
* 功能:主要就是解析@ControllerAdvice注解的类,将标注有@RequestMapping,@ModelAttribute,@InitBinder的方法放入缓存中,以当前bean为key。
*/
initControllerAdviceCache();
/**
* 初始化参数解析器HandlerMethodArgumentResolver,并把所有的参数解析器都放入
* HandlerMethodArgumentResolverComposite实例中
*/
if (this.argumentResolvers == null) {
//GSCM 2024/1/10 设置默认的方法参数处理器
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
/**
* 也是初始化参数解析器HandlerMethodArgumentResolver,并把所有的参数解析器都放入
* HandlerMethodArgumentResolverComposite实例中
*/
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
/**
* 初始化返回值处理器集合HandlerMethodReturnValueHandler,并把所有的返回值处理器都放入
* HandlerMethodReturnValueHandlerComposite中
*/
if (this.returnValueHandlers == null) {
//GSCM 2024/1/10 设置默认的方法放回置处理器
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
getDefaultArgumentResolvers
方法定义默认参数处理器 getDefaultInitBinderArgumentResolvers
方法定义默认参数处理器
HttpMessageConverter
内容协商
一个URL的资源服务端可以有多种响应形式,即MIME(Media Type)媒体类型。但客户端只需要一种,这就要求客户端和服务端之间有一种机制,能确保服务端响应的是客户端想要的,这就是内容协商。
内容协商通常有两种方式,第一是服务端将可用列表发给客户端,客户端选择之后服务端再发送过来,这种方式会多一次网络交互,而且普通用户不太可能了解技术性的选项,所以这种方式一般不用。第二种方式是常用的,客户端发送请求时指明需要的MIME,比如HTTP首部的Accept;服务端根据客户端的要求返回对应的内容形式,并在响应头中说明,比如Content-Type。详见下表:
请求头 | 请求头说明 | 响应头 | 响应头说明 |
---|---|---|---|
Accept | 告诉服务端需要的MIME | Content-Type | 告诉客户端响应的媒体类型 |
Accept-Language | 告诉服务端需要的语言 | Content-Language | 告诉客户端响应的语言 |
Accept-Charset | 告诉服务端需要的字符集 | Content-Charset | 告诉客户端响应的字符集 |
Accept-Encoding | 告诉服务端需要的压缩方式 | Content-Encoding | 告诉客户端响应的压缩方式 |
一个请求的示例 :
首先解释一下q,权重的意思,最高为1,最低为0,默认是1。
Accept:*/*表示可以是任何MIME资源,其他的比如text/plain,text/html等。
Accept-Encoding:压缩方式可以是gzip,deflate,br。服务端向客户端发送的资源可通过压缩减少传输量。
Accept-Language:中文的权重最高。这里浏览器可以根据操作系统的语言或者浏览器本身的语言设置来选择,但能否协商成功还要看服务端是否支持多语言。
再看个响应首部的例子:
Content-Encoding:说明压缩的方式是gzip。
Content-Type:表示MIME是html文本,字符集是utf-8
Set-Cookie : 针对Cookie 的操作;略
Content-Charset:告诉客户端,服务器端响应的字符集;
Content-Encoding:告诉客户端,服务器端响应的压缩方式(gzip)。
其他相关知识点记录
SpringBoot中DispatcherServlet的注册
第一步:SpringBoot自动配置会将DispatcherServletAutoConfiguration
配置类扫描注册进容器,当前配置类会新建DispatcherServlet
和DispatcherServletRegistrationBean
两个重要类进Spring容器。
第二步:创建并配置Tomcat服务,并关联SpringBoot上下文。
第三步:Tomcat服务创建阶段,通过ServletContainerInitializer#onStartup注册DispatcherServlet。
SpringBoot启动断点到ServletWebServerApplicationContext#onRefresh
:
protected void onRefresh() {
super.onRefresh();
try {
//GSCM 2024/1/10 创建要启动的服务Tomcat/Reactive
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
进入createWebServer
:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 创建WebServer
// 在默认的配置下,这里获取到的是TomcatServletWebServerFactory对象
ServletWebServerFactory factory = getWebServerFactory();
// 创建出Web的容器
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
以Tomcat为例,ServletWebServerFactory factory = getWebServerFactory()
执行返回的实现类为TomcatServletWebServerFactory
;
第一次执行getWebServerFactory会创建TomcatServletWebServerFactory进容器。注册此容器时,会被动注册DispatcherServletAutoConfiguration
中配置的的DispatcherServlet
和DispatcherServletRegistrationBean
两个类进Spring容器。
此时的factory.getWebServer(getSelfInitializer())中getSelfInitializer()
方法会获取将当前的Spring容器传给 getWebServer 方法initializers参数包含了当前SpringBoot容器
,但是getSelfInitializer方法目前并不会调用(相当于用lamuda生成一个ServletContextInitializer实例,实现方法就是selfInitialize),后面讲解。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 从容器获取所有的ServletContextInitializer对象,调用onStartup方法做初始化
// 自然也会调用DispatcherServletRegistrationBean的onStartup方法
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
先进入getWebServer 方法:
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();// 创建Tomcat服务器
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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);//配置tomcat
return getTomcatWebServer(tomcat);
}
此方法创建并设置tomcat基础配置,配置工作路径等相关参数,并将当前上下文添加进上一步创建的Tomcat中重点注意的方法是prepareContext
,进入prepareContext:
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();//创建Tomcat上下文
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldPatterns(context);
WebappLoader loader = new WebappLoader();
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
context.addLifecycleListener(new StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);// 将上下文添加进tomcat
// 配置Context
configureContext(context, initializersToUse);
postProcessContext(context);
}
prepareContext(tomcat.getHost(), initializers)
会创建一个TomcatStarter
实例并放入Tomcat上下文。并将传入的initializers
作为成员变量存入实例中。
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
// TomcatStarter是ServletContainerInitializer接口的实现类
// 在前面介绍过ServletContainerInitializer的onStart方法会在StandardContext的启动阶段调用
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
// 把starter添加到TomcatEmbeddedContext等待启动时调用
context.addServletContainerInitializer(starter, NO_CLASSES);
for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
for (Valve valve : this.contextValves) {
context.getPipeline().addValve(valve);
}
for (ErrorPage errorPage : getErrorPages()) {
org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
tomcatErrorPage.setLocation(errorPage.getPath());
tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
context.addErrorPage(tomcatErrorPage);
}
for (MimeMappings.Mapping mapping : getMimeMappings()) {
context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
}
configureSession(context);
new DisableReferenceClearingContextCustomizer().customize(context);
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}
}
这里创建了一个TomcatStarter
此类实现了servlet规范的ServletContainerInitializer接口,并且将之前获取的Spring容器作为依赖,并将TomcatStarter与Tomcat关联起来。自此SpringBoot的相关配置完成了。
TomcatStarter实现了servlet的接口ServletContainerInitializer。
class TomcatStarter implements ServletContainerInitializer {
.......
private final ServletContextInitializer[] initializers;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
.......
}
TomcatStarter会在Tomcat容器初始化时候被Tomcat调用其onStartup方法。
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
// 调用ServletContextInitializer
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
.......
}
}
}
在Tomcat启动时候,启动阶段会再来调用TomcatStarter#onStartup
。
此方法会再调用被TomcatStarter依赖的ServletContextInitializer接口。重点关注的是ServletWebServerApplicationContext
类,此类就是当前SpringBoot的容器。
ServletWebServerApplicationContext#onStartup会进入selfInitialize(上面提到过此方法):
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 从容器获取所有的ServletContextInitializer对象,调用onStartup方法做初始化
// 自然也会调用DispatcherServletRegistrationBean的onStartup方法
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
此时的getSelfInitializer()
方法会将spring容器中实现spring自定义ServletContextInitializer接口是实现类查出来。
重要的类是DispatcherServletRegistrationBean
,上面提到过此类的注册时机,不再解析。
此类是实现了ServletContextInitializer接口的:
DispatcherServletRegistrationBean的onStartup方法在父类RegistrationBean实现,直接断点到RegistrationBean#onStartup
,最终会调用到ServletRegistrationBean#addRegistration
:
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
// 把servlet添加到Context
// 这个servlet就是DispatcherServlet对象
return servletContext.addServlet(name, this.servlet);
}
最终servletContext.addServlet(name, this.servlet) 将DispatcherServlet注册进Tomcat
Servlet的请求转发和包含/RequestDispatcher
Request#getRequestDispatcher()
包含两个重要方法:请求转发和请求包含。
一个请求跨多个Servlet时,需要使用请求转发和请求包含。
示例伪代码 :
public void requestDispatcher() throws Exception {
Request request = new Request(null);
Response response = new Response();
// 首先需要获得一个RequestDispatcher 对象
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/forward");
requestDispatcher.forward(request,response);// 调用forward()方法进行请求转发
requestDispatcher.include(request,response);// 调用include()方法进行请求包含
response.sendRedirect("/redirect");// 调用sendRedirect()方法进行重定向
}
- 请求包含: rd.include( request , response);
源码学习知识点博客记录
HandlerAdapter实现类及RequestMappingHandlerAdapter调用过程
RequestMappingHandlerMapping注入流程
MVC重要组件及DispatcherServlet执行过程浅析
Handler、HandlerMapping和HandlerAdapter作用及区别
MVC中HttpMessageConverter内容协商流程