欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

MVC如何简化请求处理?
(last modified May 6, 2023, 5:46 PM )
by
侧边栏壁纸
  • 累计撰写 178 篇文章
  • 累计创建 62 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

MVC如何简化请求处理?

橙序员
2023-05-05 / 0 评论 / 2 点赞 / 353 阅读 / 2,712 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(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类负责处理的请求数量非常有限。

image-20230505112439233

那么如何简化上述问题,就是我们现有遇到的web框架所要解决的:即通过一个servlet,处理所有web请求,由框架为我们完成请求的分发,路由到对应的处理方法。即:

image-20230505112625901

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都做了哪些操作来帮助我们完成请求的处理和返回呢?大致的想,会有如下几步:

  1. 初始化对应的监听器和过滤器
  2. 初始化我们的请求处理类,注册请求映射
  3. 跟据请求找到对应的请求处理方法
  4. 执行请求处理方法
  5. 对返回的结果进行视图渲染
  6. 返回给客户端

其实可以分为三个阶段:环境初始化,请求映射注册,请求调用处理

环境初始化

web容器初始化

我们首先看下DispatcherServlet的继承关系:

image-20230505114439966

其中HttpServletBeanFrameworkServletDispatcherServlet的主要功能如下:

功能
HttpServletBean 实现servlet初始化方法init,子类可以通过实现initServletBean方法初始化servlet容器
FrameworkServlet 实现了initServletBean方法,完成web容器的创建,并提供了容器的刷新和事件监听方法;将GET、POST、DO、DELTE请求的处理统一
DispatcherServlet 实现请求路由、请求渲染,异常解析等和处理请求相关的具体功能

查看FrameworkServletinitServletBean,我们可以看到,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来实现,其类图为:

image-20230505113904632

可见XmlWebApplicationContext其实就是我们applicationContext的另一种实现,如果说ClassPathXmlApplicationContext时java应用的容器实现的话,那么XmlWebApplicationContext就是java web应用的容器实现。

而第二个点则是我们的web容器相比较于之前的容器则多了如下几个操作:

  1. web环境变量的设置
  2. web容器下自定义刷新:网页主题的国际化
  3. 容器完成事件的监听:初始化文件请求、国际化、网页主题、请求、视图等相关解析器

spring-mvc-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);
	}

上述步骤分别为:

  1. 文件上传请求的解析支持
  2. 国际化解析支持
  3. 网页主题解析支持(使用参考:https://www.baeldung.com/spring-mvc-themes)
  4. 初始化类-请求映射关系
  5. 初始化请求-映射适配器
  6. 错误视图解析支持
  7. 初始化请求-视图翻译器
  8. 初始化视图解析器
  9. 重定向属性管理(使用参考: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的类图:

image-20230505132720734

可以看到映射关系的注册是在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主要做了这些事情:

  1. 扫描方法的@RequestMapping注解,并根据注解中的属性创建具体的请求信息
    1. 将类上的@RequestMapping信息拼接到方法的请求信息中
  2. 将所有带有@RequestMapping注解的Method和请求信息存入到集合中
  3. 遍历集合,注册所有请求-方法映射关系

如果我们debug对应的web应用,则可以在初始化后可以看到对应的mapping注册信息:

image-20230505141410488

请求调用处理

在我们实际访问URL,后台进行解析时,会调用DispatherServletdoService方法进行处理,其处理流程如下:

image-20230505152506157

在上述请求过程中,实际处理请求的是我们的请求映射适配器,我们请求注册时的映射为RequestMappingHandlerMapping,所以会查找到其对应的默认适配器RequestMappingHandlerMappingRequestMappingHandlerMapping在bean初始化时,主要做了如下操作:

  1. 控制器切面处理(可用作全局异常处理
  2. 设置参数解析器(获取注解中声明的参数值)
  3. 绑定参数解析器(获取参数的实际值并绑定注解值)
  4. 设置返回值处理器
	@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);
	}
2

评论区