文章摘要(AI生成)
在使用servlet开发web项目时,我们的web.xml文件通常会做如下配置:<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/x
在使用servlet开发web项目时,我们的web.xml文件通常会做如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">
<!--注册Servlet-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.demo.servlet.HelloServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>hi</servlet-name>
<servlet-class>com.demo.servlet.HiServlet</servlet-class>
</servlet>
<!--Servlet的请求路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>hi</servlet-name>
<url-pattern>/hi</url-pattern>
</servlet-mapping>
</web-app>
这时候我们在代码开发时,url和我们的servlet就是1对1的关系,这时候我们每写一个servlet在web.xml中都要做相应的配置,导致web.xml内容过于繁多,不利于团队分组开发。而且一个Servlet类负责处理的请求数量非常有限。
那么如何简化上述问题,就是我们现有遇到的web框架所要解决的:即通过一个servlet,处理所有web请求,由框架为我们完成请求的分发,路由到对应的处理方法。即:
MVC的核心-DispatherServlet
使用spring mvc配置时,我们的web.xml只需要配置一个servlet映射对,就可以完成所有请求的映射处理:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-context.xml</param-value>
</context-param>
</web-app>
那么DispatcherServlet
都做了哪些操作来帮助我们完成请求的处理和返回呢?大致的想,会有如下几步:
- 初始化对应的监听器和过滤器
- 初始化我们的请求处理类,注册请求映射
- 跟据请求找到对应的请求处理方法
- 执行请求处理方法
- 对返回的结果进行视图渲染
- 返回给客户端
其实可以分为三个阶段:环境初始化,请求映射注册,请求调用处理
环境初始化
web容器初始化
我们首先看下DispatcherServlet的继承关系:
其中HttpServletBean
、FrameworkServlet
和DispatcherServlet
的主要功能如下:
类 | 功能 |
---|---|
HttpServletBean |
实现servlet初始化方法init ,子类可以通过实现initServletBean 方法初始化servlet容器 |
FrameworkServlet |
实现了initServletBean 方法,完成web容器的创建,并提供了容器的刷新和事件监听方法;将GET、POST、DO、DELTE请求的处理统一 |
DispatcherServlet |
实现请求路由、请求渲染,异常解析等和处理请求相关的具体功能 |
查看FrameworkServlet
的initServletBean
,我们可以看到,WebApplicationContext
容器的初始化交由initWebApplicationContext
方法完成:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
//web容器已存在
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 容器还没有初始化
if (cwac.getParent() == null) {
// 获取父web容器
cwac.setParent(rootContext);
}
//完成容器初始化
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//如果web容器为空,则根据自定义的web容器名称查找
wac = findWebApplicationContext();
}
if (wac == null) {
// 还没有查找到,则创建一个web容器
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 如果没有监听到容器完成事件,则手动调用容器更新方法
onRefresh(wac);
}
if (this.publishContext) {
// 将容器设置servlet的属性,方便之后取用容器中的bean和其他配置
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 如果容器id已经定义,则使用定义的容器id
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
//否则生成一个默认的容器id
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//ContextRefreshListener用来监听web容器初始化完成后的全局事件消息,用来完成servlet的后续初始化操作
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//web环境下需要设置servlet特有的环境变量
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
上面代码可以看到的第一点是:web环境的初始化在spring中交给了web容器WebApplicationContext
来实现,其类图为:
可见XmlWebApplicationContext
其实就是我们applicationContext
的另一种实现,如果说ClassPathXmlApplicationContext
时java应用的容器实现的话,那么XmlWebApplicationContext
就是java web应用的容器实现。
而第二个点则是我们的web容器相比较于之前的容器则多了如下几个操作:
- web环境变量的设置
- web容器下自定义刷新:网页主题的国际化
- 容器完成事件的监听:初始化文件请求、国际化、网页主题、请求、视图等相关解析器
Servlet初始化
在监听到容器初始化完成的事件后,DispatcherServlet主要做了如下操作:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
上述步骤分别为:
- 文件上传请求的解析支持
- 国际化解析支持
- 网页主题解析支持(使用参考:https://www.baeldung.com/spring-mvc-themes)
- 初始化类-请求映射关系
- 初始化请求-映射适配器
- 错误视图解析支持
- 初始化请求-视图翻译器
- 初始化视图解析器
- 重定向属性管理(使用参考:https://www.baeldung.com/spring-web-flash-attributes)
其中我们首要关心的是请求如何分发到我们的。
这里我们就要注意一下,这几个初始化方法的实现了:他们本质都是读取了DispatcherServlet.properties
文件中对应键值对中的类,通过web容器完成对象创建后,完成了默认映射器的设置(参考getDefaultStrategies
方法)。而DispatcherServlet.properties
的相关配置如下:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
请求映射注册
HandlerMapping
默认是由RequestMappingHandlerMapping
实现的,我们查看RequestMappingHandlerMapping
的类图:
可以看到映射关系的注册是在servlet初始化时,我们通过bean工厂创建RequestMappingHandlerMapping
时,bean初始化会调用afterPropertiesSet
方法完成bean后置操作。而AbstractHandlerMethodMapping
则在此为我们绑定了请求和处理方法的映射。
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
//获取到所有已注册的bean名称
for (String beanName : getCandidateBeanNames()) {
//扫描bean名称非SCOPED_TARGET_NAME_PREFIX开头的bean,进行处理
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
//打印全部请求映射
handlerMethodsInitialized(getHandlerMethods());
}
processCandidateBean
主要做了这些事情:
- 扫描方法的
@RequestMapping
注解,并根据注解中的属性创建具体的请求信息- 将类上的
@RequestMapping
信息拼接到方法的请求信息中
- 将类上的
- 将所有带有
@RequestMapping
注解的Method和请求信息存入到集合中 - 遍历集合,注册所有请求-方法映射关系
如果我们debug对应的web应用,则可以在初始化后可以看到对应的mapping注册信息:
请求调用处理
在我们实际访问URL,后台进行解析时,会调用DispatherServlet
的doService
方法进行处理,其处理流程如下:
在上述请求过程中,实际处理请求的是我们的请求映射适配器,我们请求注册时的映射为RequestMappingHandlerMapping
,所以会查找到其对应的默认适配器RequestMappingHandlerMapping
。RequestMappingHandlerMapping
在bean初始化时,主要做了如下操作:
- 控制器切面处理(可用作全局异常处理)
- 设置参数解析器(获取注解中声明的参数值)
- 绑定参数解析器(获取参数的实际值并绑定注解值)
- 设置返回值处理器
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
//设置默认的参数解析器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
//设置默认的参数绑定解析器
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
//设置返回值处理器
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
而在请求处理时,其handler方法则主要通过反射进行调用:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
//如果需要在session中串行执行invokeHandlerMethod。
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
//获取session,添加互斥锁
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
//没有可用的 HttpSession -> 不需要互斥锁
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
//根本不需要在当前session中串行执行......
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//请求调用处理
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//设置响应码
setResponseStatus(webRequest);
//更新视图中的请求处理状态
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取当前请求中的入参
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//调用对应的方法执行,获取返回结果
return doInvoke(args);
}
评论区