整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

SpringMVC流程及源码分析

SpringMVC流程及源码分析

学了一遍SpringMVC以后,想着做一个总结,复习一下。复习写下面的总结的时候才发现,其实自己学得并不彻底、牢固、也没有学全,视频跟书本是要结合起来一起,每一位老师的视频可能提到的东西都不一致,也导致也不是很全面,书本上会讲的笔记系统、全面。同时我自己也是一个初学者,下面总结的可能并不完善、正确,希望看到的大神给我指出,在此非常感谢。


目录

  • SpringMVC流程及源码分析
  • 一 、Spring核心模块
    • 1、核心模块
    • 2、Spring版本命名规则(补充)
  • 二、SpringMVC流程及原理
    • 1、执行流程
      • 1.1、执行流程
      • 1.2、执行流程说明:
        • 1.2.1、第02、03说明
        • 1.2.2、第04说明
        • 1.2.2、SpringMVC组件说明
        • 1.2.3、SpringMVC详细流程图
  • 二、源码分析
    • 1、初始化
      • 1.1、ApplicationContext
    • 2、前端控制器(中央处理器)DistepcherServlet
      • 2.1、查找处理器映射器HandlerMapping
      • 2.2、根据处理器映射器HandlerMapping返回结果调用处理器适配器HandlerAdapter
      • 2.3、检查拦截器Interceptor
      • 2.3、处理器适配器HandlerAdapter执行Handler(Controller)返回ModelAndView
      • 2.4、视图解析器ViewResolver
      • 2.5、视图View
        • 2.5.1、视图对象的作用
        • 2.5.2、View接口图
        • 2.5.3、View的实现类图
        • 2.5.4、View的UML图
        • 2.5.5、常用的View视图类
      • 2.6、其他重要的点
        • 2.6.1、DispatcherServlet.properties
  • 三、引用参考资料
    • 1、引用资料
    • 2、参考资料

一 、Spring核心模块

1、核心模块

Spring Web MVC (下文简称为 SpringMVC )是 Spring 提供 Web 应用的框架设计,属于表现层的框架。SpringMVC是Spring框架的一部分。
Spring框架包括大致六大模块,核心容器(Core Container)、AOP和设备支持、数据访问及集成、Web、报文发送、Test

图片来源于Spring官网5.0.0.M5:

? https://docs.spring.io/spring-framework/docs/5.0.0.M5/spring-framework-reference/html/overview.html#overview-modules

对于Spring5模块图,有2点疑问:
1、不清楚为什么在Spring官网上5.0版本以后,Release版(稳定版)的都未找到模块图,但是在M(里程碑版)版找到 了,如果有人在5.0以后的Release版(稳定版)找到,麻烦给我留个言,谢谢。
2、在其他博文中看到Spring5模块结构图是这样的:

挺奇怪这个图是哪里来的?(路过的大神请指点)

对于问题2,我在Spring5.2.13.RELEASE GA中,找到了如下所示信息:

拷贝以上信息:

Spring Framework Documentation

Version 5.2.13.RELEASE

What’s New, Upgrade Notes, Supported Versions, and other topics, independent of release cadence, are maintained externally on the project’s Github Wiki.

Overview

history, design philosophy, feedback, getting started.

Core

IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.

Testing

Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient.

Data Access

Transactions, DAO Support, JDBC, O/R Mapping, XML Marshalling.

Web Servlet

Spring MVC, WebSocket, SockJS, STOMP Messaging.

Web Reactive

Spring WebFlux, WebClient, WebSocket.

Integration

Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching.

Languages

Kotlin, Groovy, Dynamic Languages.

按照以上信息的Web Servlet、Web Reactive已经是分属于不同的模块了。

  • Web Servlet:Spring MVC, WebSocket, SockJS, STOMP Messaging.
  • Web Reactive:Spring WebFlux, WebClient, WebSocket.

Spring官方文档:https://spring.io/projects/spring-framework#learn/

2、Spring版本命名规则(补充)

上面提到了Spring有不同的版本,在此记录一下各个版本的意义。

描述方式

说明

含义

Snapshot

快照版

尚不稳定,仍处于开发中的版本

Release

稳定版

功能相对稳定,可以对外发行,但有时间限制

GA

正式版

代表广泛可用的稳定版(General Availability)

M

里程碑版

(M是Milestone的意思)具有一些全新的功能或是有意义的版本

RC

终测版

Release Candidate(最终测试),即将作为正式版发布

二、SpringMVC流程及原理

1、执行流程

SpringMVC执行流程图

图片来源:三、引用参考资料

1.1、执行流程

  • 01、用户发送出请求到前端控制器(中央处理器)DispatcherServlet进行处理。
  • 02、前端控制器DispatcherServlet收到请求后,调用处理器映射器HandlerMapping。
  • 03、处理器映射器HandlerMapping(处理器映射器)根据request请求的URL等信息查找能够进行处理的Handler,以及相关拦截器interceptor,并构造HandlerExecutionChain执行链,然后将构造好的HandlerExecutionChain执行链对象返回给前端控制器DispatcherServlet。
  • 04、前端控制器DispatcherServlet根据处理器映射器HandlerMapping的
  • 05、处理器适配器HandlerAdapter经过适配调用具体的处理器(Handler/Controller),即业务中自己写的Controller。
  • 06、Controller处理完后返回ModelAndView(springmvc的封装对象,将model和view封装在一起)给处理器适配器HandlerAdapter;
  • 07、处理器适配器HandlerAdapter将Controller执行结果ModelAndView返回给前端控制器DispatcherServlet。
  • 08、前端控制器DispatcherServlet调用视图解析器ViewReslover处理ModelAndView。
  • 09、视图解析器ViewReslover解析后根据逻辑视图名解析成物理视图名即具体的页面地址,生成并返回具体对象View(springmvc封装对象,是一个接口)。
  • 10、前端控制器DispatcherServlet根据对象View进行视图渲染,填充Model。
  • 11、前端控制器DispatcherServlet向用户返回响应

1.2、执行流程说明:

1.2.1、第02、03说明

(1) 处理器映射器:springmvc框架中的一种对象,框架把实现了HandlerMapping接口的类都叫做映射器(多个);

(2) 处理器映射器作用:根据请求,从springmvc容器对象中获取处理器对象(MyController controller=ctx.getBean("some")

(3) 框架把找到的处理器对象放到一个叫做处理器执行链(HandlerExecutionChain)的类保存

(4) HandlerExecutionchain:类中保存着
?a:处理器对象(MyController);
?b:项目中的所有的拦截器List

(5) 方法调用:HandlerExecutionChain mappedHandler - getHandler (processedRequest);

1.2.2、第04说明

(1) HandlerExecutionChain执行链找到对应的处理器映射器HandlerAdapter。
(2) 处理器适配器:springmvc框架中的对象,需要实现HandlerAdapter接口,
(3) 处理器适配器作用:执行处理器方法(调用MyController.doSome()得到返回值ModelAndView )
(4) 前端控制器中调用适配器:HandlerAdapter ha=getHandlerAdapter (mappedHandler.getHandler());
(5) 执行处理器方法:mv=ha.handle (processedRequest, response, mappedHandler.getHandler());

第08说明:
(1) 视图解析器:springmvc中的对象,需要实现ViewResoler接口(可以有多个)
(2) 视图解析器作用:组成视图完整路径,使用前缀,后缀。并创建View对象。
(3) view是一个接口,表示视图的,在框架中jsp,htm1不是string表示,而是使用view和他的实现类表示视图。

InternalResourceview:视图类,表示jsp文件,视图解析器会创建InternalResourceView类对象。 这个对象的里面,有一个属性url-/WEB-INF/view/show.jsp

1.2.2、SpringMVC组件说明

  • (1). 前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。
  • (2). 处理器映射器(HandlerMapping):根据URL去查找处理器.
  • (3). 处理器(Handler):(需要程序员去写代码处理逻辑的).
  • (4). 处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用).
  • (5). 视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面.

1.2.3、SpringMVC详细流程图

综上所述,总结下SpringMVC的详细流程图:


图片来源:三、引用参考资料

二、源码分析

以下源码来源jar包:spring-webmvc-5.25.RELEASE.jar

1、初始化

1.1、ApplicationContext

? ApplicationContext初始化入口类:ApplicationObjectSupport的setApplicationContext方法,setApplicationContext方法中核心部分就是初始化容器initApplicationContext(context),子类AbstractDetectingUrlHandlerMapping实现了该方法。
类图:


UML图:


? RequestMappingHandlerMapping ,用于注解@Controller,@RequestMapping来定义controller.
初始化时,3个类的大致分工如下:

  • AbstractHandlerMethodMapping定义整个算法流程;
  • RequestMappingInfoHandlerMapping提供匹配条件RequestMappingInfo的解析处理;
  • RequestMappingHandlerMapping根据@RequestMapping注解生成 RequestMappingInfo,同时提供isHandler实现

2、前端控制器(中央处理器)DistepcherServlet

? 从上面的流程图可以看到前端控制器(中央处理器)DistepcherServlet是SpringMVC核心,查看DistepcherServlet类的继承情况。
UML图:
![2021022601-08-DispatcherServlet UML图](https://gitee.com/chuchq/blogs-gallery/raw/master/images / 2021/2021022601-08-DispatcherServlet UML图.png)
从继承关系看出:
? DistepcherServlet ---> FrameworkServlet ---> HttpServletBean---> HttpServlet
? 那就说明DistepcherServlet 类也是一个Servlet类,那最终核心的方法就是service()方法,即Servlet的核心方法。
? 那就找service()方法,在DistepcherServlet中没有servic()方法,在父类FrameworkServlet有service()方法,源码如下:
来源:

org.springframework.web.servlet.FrameworkServlet.service(HttpServletRequest request, HttpServletResponse response)

/**
	 * Override the parent class implementation in order to intercept PATCH requests.
	 */
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		HttpMethod httpMethod=HttpMethod.resolve(request.getMethod());
		if (httpMethod==HttpMethod.PATCH || httpMethod==null) {
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}

可以看到:
FrameworkServlet.service(HttpServletRequest request, HttpServletResponse response)拿到request请求,判断当前请求是否是PATCH请求,不是的就调用父类的servic()方法,调用父类中的service方法就是去调用该类中doPost(),doGet()方法,根据不同的请求方式然后走doPost()或者doGet(),调用中以doGet()为例,
FrameworkServlet类的doGet()源码:

/**
	 * Delegate GET requests to processRequest/doService.
	 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
	 * with a {@code NoBodyResponse} that just captures the content length.
	 * @see #doService
	 * @see #doHead
	 */
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}

? doGet()又调用FrameworkServlet类中的processRequest(request, response);

/**
	 * Process this request, publishing an event regardless of the outcome.
	 * <p>The actual event handling is performed by the abstract
	 * {@link #doService} template method.
	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime=System.currentTimeMillis();
		Throwable failureCause=null;

		LocaleContext previousLocaleContext=LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext=buildLocaleContext(request);

		RequestAttributes previousAttributes=RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes=buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager=WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		initContextHolders(request, localeContext, requestAttributes);

		try {
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause=ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause=ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes !=null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

? processRequest(request, response)方法中最关键的又调用了doService(request, response);查看FrameworkServlet类中的doService(request, response),或者是调试跟踪可知,doService(request, response)由子类DispatcherServlet实现。

源码来源:

org.springframework.web.servlet.FrameworkServlet.doService(HttpServletRequest request, HttpServletResponse response)

/**
	 * Subclasses must implement this method to do the work of request handling,
	 * receiving a centralized callback for GET, POST, PUT and DELETE.
	 * <p>The contract is essentially the same as that for the commonly overridden
	 * {@code doGet} or {@code doPost} methods of HttpServlet.
	 * <p>This class intercepts calls to ensure that exception handling and
	 * event publication takes place.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @throws Exception in case of any kind of processing failure
	 * @see javax.servlet.http.HttpServlet#doGet
	 * @see javax.servlet.http.HttpServlet#doPost
	 */
	protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
			throws Exception;

? 查看DispatcherServlet中的doService(HttpServletRequest request, HttpServletResponse response)方法

/**
	 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
	 * for the actual dispatching.
	 */
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot=null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot=new HashMap<>();
			Enumeration<?> attrNames=request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName=(String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		if (this.flashMapManager !=null) {
			FlashMap inputFlashMap=this.flashMapManager.retrieveAndUpdate(request, response);
			if (inputFlashMap !=null) {
				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
			}
			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
		}

		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot !=null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

? DispatcherServlet的doService()方法中最终调用doDispatch(request, response),查看源码如下:
org.springframework.web.servlet.DispatcherServlet.doDispatch()

/**
	 * Process the actual dispatching to the handler.
	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
	 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
	 * to find the first that supports the handler class.
	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
	 * themselves to decide which methods are acceptable.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @throws Exception in case of any kind of processing failure
	 */
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest=request;
		HandlerExecutionChain mappedHandler=null;
		boolean multipartRequestParsed=false;

		WebAsyncManager asyncManager=WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv=null;
			Exception dispatchException=null;

			try {
				// 文件上传相关,判断是不是二进制请求
				processedRequest=checkMultipart(request);
				multipartRequestParsed=(processedRequest !=request);
				// 取得处理当前请求的controller,这里也称为hanlder处理器,第一个步骤的意义就在这里体现了.这里并不是直接返回controller,而是返回的HandlerExecutionChain请求处理器链对象,该对象封装了handler和拦截器interceptors.
				// Determine handler for the current request.
				mappedHandler=getHandler(processedRequest);
				// 如果handler为空,则返回404
				if (mappedHandler==null) {
					noHandlerFound(processedRequest, response);
					return;
				}
				//3. 获取处理request的处理器适配器HandlerAdapter
				// Determine handler adapter for the current request.
				HandlerAdapter ha=getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method=request.getMethod();
				boolean isGet="GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified=ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				//处理器适配器执行之前,检查拦截器的方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				//处理器适配器根据找到,执行handler,返回ModelAndView
				// Actually invoke the handler.
				mv=ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException=ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException=new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler !=null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

? 可以看出doDispatch()就是SpringMVC的核心代码了,分析doDispatch():

2.1、查找处理器映射器HandlerMapping

? 首先看下处理器映射器HandlerMapping类图:

doDispatch()关键代码:

HandlerExecutionChain mappedHandler=null;

mappedHandler=getHandler(processedRequest);

? mappedHandler是一个执行链HandlerExecutionChain 对象,这里封装了handler和拦截器interceptors,getHandler(processedRequest)方法就是从处理器映射器HandlerMapping中找到url和controller的对应关系,并返回给前端控制器DispatchServlet。
查看getHandler(processedRequest);源码:

/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
	 */
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings !=null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler=mapping.getHandler(request);
				if (handler !=null) {
					return handler;
				}
			}
		}
		return null;
	}

调试代码如下:


从代码调试中可以看到handlerMapping中有三个对象:

this.handlerMappings={ArrayList@4662}  size=3
 0={BeanNameUrlHandlerMapping@4791} 
 1={RequestMappingHandlerMapping@4792} 
 2={RouterFunctionMapping@4793} 
  • BeanNameUrlHandlerMapping:初始化时会将urlpath做映射存储(xml);
  • RequestMappingHandlerMapping:初始化时会将Controller中配置@RequestMapping注解的方法做映射存储(注解);
  • RouterFunctionMapping:
    (这个对象不是太理解)
    这也就是为什么要去HandlerMapping找一个Handler了,因为处理器映射器HandlerMapping有不同的实现:
  • 1、xml方式
  • 2、注解方式

接着看getHandler(HttpServletRequest request)方法,先遍历HandlerMappers,查找控制器找到之后就返回执行链HandlerExecutionChain类型的Handler。

可以看到返回的Handler中,拿到的就是我们自己编码的Controller类,以及拦截器(演示项目中未编写,所以调试汇总返回的Handler最后是0 interceptors)
HandlerExecutionChain with [com.bjpowernode.controller.MyController#doSome()] and 0 interceptors


将正在调试的idea打开自己编写的Controller来对照,发现一致:

2.2、根据处理器映射器HandlerMapping返回结果调用处理器适配器HandlerAdapter

doDispatch()里面的关键代码:

HandlerAdapter ha=getHandlerAdapter(mappedHandler.getHandler());

源码如下:

/**
	 * Return the HandlerAdapter for this handler object.
	 * @param handler the handler object to find an adapter for
	 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
	 */
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters !=null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

为什么还要获取处理器适配器HandlerAdapter:与获取处理器映射器HandlerMapping一样,Spring提供了不通的处理器适配器。
调试如下:


查看DEBUG调试模式中getHandlerAdapter()方法在中的:
handler、adapter、this.handlerAdapters


以下是拷贝的结果:
handler

handler={HandlerMethod@4792} "com.bjpowernode.controller.MyController#doSome()"
 logger={LogAdapter$JavaUtilLog@4858} 
 bean={MyController@4859} 
 beanFactory={DefaultListableBeanFactory@4847} "org.springframework.beans.factory.support.DefaultListableBeanFactory@56b5a4c3: defining beans [myController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy"
 beanType={Class@3782} "class com.bjpowernode.controller.MyController"
 method={Method@4860} "public org.springframework.web.servlet.ModelAndView com.bjpowernode.controller.MyController.doSome()"
 bridgedMethod={Method@4860} "public org.springframework.web.servlet.ModelAndView com.bjpowernode.controller.MyController.doSome()"
 parameters={MethodParameter[0]@4861} 
 responseStatus=null
 responseStatusReason=null
 resolvedFromHandlerMethod={HandlerMethod@4863} "com.bjpowernode.controller.MyController#doSome()"
 interfaceParameterAnnotations=null
 description="com.bjpowernode.controller.MyController#doSome()"

adapter

adapter={RequestMappingHandlerAdapter@4827} 
 customArgumentResolvers=null
 argumentResolvers={HandlerMethodArgumentResolverComposite@4833} 
 initBinderArgumentResolvers={HandlerMethodArgumentResolverComposite@4834} 
 customReturnValueHandlers=null
 returnValueHandlers={HandlerMethodReturnValueHandlerComposite@4835} 
 modelAndViewResolvers=null
 contentNegotiationManager={ContentNegotiationManager@4836} 
 messageConverters={ArrayList@4837}  size=4
 requestResponseBodyAdvice={ArrayList@4838}  size=0
 webBindingInitializer=null
 taskExecutor={SimpleAsyncTaskExecutor@4839} 
 asyncRequestTimeout=null
 callableInterceptors={CallableProcessingInterceptor[0]@4840} 
 deferredResultInterceptors={DeferredResultProcessingInterceptor[0]@4842} 
 reactiveAdapterRegistry={ReactiveAdapterRegistry@4844} 
 ignoreDefaultModelOnRedirect=false
 cacheSecondsForSessionAttributeHandlers=0
 synchronizeOnSession=false
 sessionAttributeStore={DefaultSessionAttributeStore@4845} 
 parameterNameDiscoverer={DefaultParameterNameDiscoverer@4846} 
 beanFactory={DefaultListableBeanFactory@4847} "org.springframework.beans.factory.support.DefaultListableBeanFactory@56b5a4c3: defining beans [myController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy"
 sessionAttributesHandlerCache={ConcurrentHashMap@4848}  size=0
 initBinderCache={ConcurrentHashMap@4849}  size=0
 initBinderAdviceCache={LinkedHashMap@4850}  size=0
 modelAttributeCache={ConcurrentHashMap@4851}  size=0
 modelAttributeAdviceCache={LinkedHashMap@4852}  size=0
 order=2147483647
 supportedMethods=null
 allowHeader="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"
 requireSession=false
 cacheControl=null
 cacheSeconds=-1
 varyByRequestHeaders=null
 useExpiresHeader=false
 useCacheControlHeader=true
 useCacheControlNoStore=true
 alwaysMustRevalidate=false
 servletContext={ApplicationContextFacade@4754} 
 logger={LogAdapter$JavaUtilLog@4854} 
 applicationContext={XmlWebApplicationContext@4665} "WebApplicationContext for namespace 'myweb-servlet', started on Tue Mar 02 23:25:35 CST 2021"
 messageSourceAccessor={MessageSourceAccessor@4855} 

this.handlerAdapters

this.handlerAdapters={ArrayList@4658}  size=4
 0={HttpRequestHandlerAdapter@4810} 
 1={SimpleControllerHandlerAdapter@4820} //XML方式
 2={RequestMappingHandlerAdapter@4827} //注解方式
 3={HandlerFunctionAdapter@4832} 

可以看到找到4个处理器适配器。通过DEBUG模式可以看到,此次取到的处理器适配器HandlerAdapter是:RequestMappingHandlerAdapter

ha={RequestMappingHandlerAdapter@4827} 

2.3、检查拦截器Interceptor

doDispatch()中的关键代码:

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle

applyPreHandle(processedRequest, response)源码:

/**
	 * Apply preHandle methods of registered interceptors.
	 * @return {@code true} if the execution chain should proceed with the
	 * next interceptor or the handler itself. Else, DispatcherServlet assumes
	 * that this interceptor has already dealt with the response itself.
	 */
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors=getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i=0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor=interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex=i;
			}
		}
		return true;
	}

2.3、处理器适配器HandlerAdapter执行Handler(Controller)返回ModelAndView

doDispatch()中的关键代码:

mv=ha.handle(processedRequest, response, mappedHandler.getHandler());

DEBUG模式调试,是调到了:
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
源码如下:

/**
	 * This implementation expects the handler to be an {@link HandlerMethod}.
	 */
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}

再往下看handleInternal(request, response, (HandlerMethod) handler)方法,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session=request.getSession(false);
			if (session !=null) {
				Object mutex=WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav=invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav=invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			mav=invokeHandlerMethod(request, response, handlerMethod);
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}

注意,handleInternal(request, response, (HandlerMethod) handler)方法的返回值是ModelAndView ,这里就完成了处理器适配器HandlerAdapter执行Handler(Controller)并将结果ModelAndView返回给前端控制器DistepchServlet

2.4、视图解析器ViewResolver

??接上2.3:前端控制器DistepchServlet接收到处理器适配器HandlerAdapter返回的ModelAndView以后,这里分2种情况:

  • (1)、如果ModelAndView里面是逻辑视图
    前端控制器DistepchServlet调用视图解析器ViewResolver通过逻辑视图查找真正的视图对象View,并返回给前端控制器DistepchServlet。
  • (2)、如果ModelAndView里面是非逻辑视图:
    如:MappingJackson2JsonView(把当前数据转为为JSON数据,并不需要对视图逻辑名称进行转换)

总结一下:
视图解析器ViewResolver接口主要作用是解析前端控制器DispatcherServlet传递的逻辑视图名,并将解析结果的真正的视图对象View传回给前端控制器DispatcherServlet

ViewResolverd的实现类:


ViewResolver的UML:

2.5、视图View

2.5.1、视图对象的作用

  • (1)、将控制器返回的数据处理渲染,最终返回客户端展示给用户,主要就是完成转发或者是重定向的操作.。
  • (2)、为了实现视图模型和具体实现技术的解耦(指的是Spring在org.springframework.web.servlet包中定义的抽象View接口),详见2.5.2View接口图。
  • (3)、视图对象View由视图解析器负责实例化。由于视图是无状态(每一次请求都会创建一个新的view对象)的,所以不会有线程安全的问题.

2.5.2、View接口图

2.5.3、View的实现类图

2.5.4、View的UML图

![2021022601-20-01-View-uml(hierarchic group layout)](https://gitee.com/chuchq/blogs-gallery/raw/master/images / 2021/2021022601-20-01-View-uml(hierarchic group layout).png)

2.5.5、常用的View视图类

视图类型

简介

URL视图资源图

InternalResourceView

将JSP或其他资源封装成一个视图。被视图解析器InternalResourceViewResolver默认使用。

JstlView

InternalResourceView的子类。如果JSP中使用了JSTL的国际化标签,就需要使用该视图类。

文档视图

AbstractExcelView

Excel文档视图的抽象类。

AbstractPdfView

PDF文档视图的抽象类

报表视图

ConfigurableJasperReportsView

常用的JasperReports报表视图

JasperReportsHtmlView

JasperReportsPdfView

JasperReportsXlsView

JSON视图

MappingJackson2JsonView

将数据通过Jackson框架的ObjectMapper对象,以JSON方式输出

2.6、其他重要的点

2.6.1、DispatcherServlet.properties

DispatcherServlet.properties文件是在SpringMVC架包中:


DispatcherServlet.properties内容:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

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.function.support.RouterFunctionMapping

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.function.support.HandlerFunctionAdapter


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

SpringMVC为什么能加载不同处理器映射器HandlerMapping、处理器适配器handlerAdapter,就是因为框架配置了这个DispatcherServlet.properties文件。

转载于:https://www.cnblogs.com/chuchq/p/14489716.html

一篇[79、Spring MVC]

下一篇[80、使用 Spring 安全进行测试]

. 为啥要学 SpringMVC?

1.1 SpringMVC 简介

在学习 SpringMVC 之前我们先看看在使用 Servlet 的时候我们是如何处理用户请求的:

  1. 配置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_4_0.xsd"
         version="4.0">

    <!--将 Servlet接口实现类交给Tomcat管理-->
    <servlet>
        <servlet-name>userServlet</servlet-name> <!--Servlet接口实现类名称-->
        <servlet-class>com.xxl.controller.UserServlet</servlet-class><!--声明servlet接口实现类类路径-->
    </servlet>
    <servlet-mapping>
        <servlet-name>userServlet</servlet-name>
        <url-pattern>/user</url-pattern>  <!--设置Servlet类的请求路径,必须以 / 开头-->
    </servlet-mapping>
</web-app>
  1. 继承 HttpServlet,实现 doGet 和 doPost 方法
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       // 这里用来处理用户的 get 请求
       System.out.println("哈哈哈哈哈哈我头上有注解");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}
  1. 获取请求参数

String name=request.getParameter("name");

String age=request.getParameter("age");
....

一顿操作下来,我们发现用 Servlet 处理用户的请求也太麻烦了吧。

每个 Servlet 都要继承 HttpServlet、重写两个方法,我们需要写一堆 getParameter() 方法来获取请求参数,而且还要做数据类型的转换。

那有没有一个别人封装好的工具或者是框架让我少写这些重复性的代码呢?

SpringMVC闪亮登场。

SpringMVC 是一种轻量级的、基于 MVC 的 Web 层应用框架,它属于 Spring 框架的一部分。SpringMVC 说白了就是对 Servlet 进行了封装,方便大家使用。

1.2 SpringMVC 优点

  • 天生与 Spring 集成
  • 支持 Restful 风格开发
  • 便于与其他视图技术集成,例如 theamleaf、freemarker等
  • 强大的异常处理
  • 对静态资源的支持

总之就是好用!


2. HelloWorld

这里我们先来开发一个基于 SpringMVC 的程序,感受一下 SpringMVC 的迷人特性。

  • 开发工具:IDEA
  • 构建工具:Maven

2.1 新建基于 Maven 的 web 项目


2.2 加入依赖

<!-- servlet -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
<!--springmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<!-- junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>

2.3 创建中央调度器

DispatcherServlet 是 SpringMVC 的中央调度器,它主要负责加载 SpringMVC 的配置。

从它的名字来看,他也属于一个 Servlet,遵守 Servlet 规范。所以我们需要在 web.xml 中创建 DispatcherServlet。

web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <!--servlet: DispatcherServlet-->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>


2.4 创建 SpringMVC 的配置文件

这里我们在 src/resources 资源目录下创建 SpringMVC的配置文件 springmvc.xml,该文件名字可以任意命名。

springmvc.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>


2.5 创建处理请求的处理器

TestController:

/**
* @Desc: 处理器
* @Author: 知否技术
* @date: 下午7:39 2022/4/29
*/
@Controller
public class TestController {
  @RequestMapping("/hello")
  public ModelAndView sayHello() {
      ModelAndView mv=new ModelAndView();
      mv.addObject("msg", "你好啊,李银河,我是王小波。");
      mv.setViewName("/hello.jsp");
      return mv;
  }
}

2.6 声明组件扫描器

我们在 springmvc.xml 中注册组件扫描器,


<!-- 扫描组件,将加上@Controller注解的类作为SpringMVC的控制层 -->
<context:component-scan base-package="com.zhifou"></context:component-scan>


2.7 创建 jsp 页面


2.8 配置视图解析器

我们需要在 springmvc.xml 中配置请求文件的路径和文件后缀。

<!-- 
    配置视图解析器
    作用:将prefix + 视图名称 + suffix 确定最终要跳转的页面
   -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/view/"></property>
  <property name="suffix" value=".jsp"></property>
</bean>


2.9 修改处理器请求文件路径

因为我们指定了请求文件的后缀是 .jsp,所以这里可以省略。


2.10 配置 tomcat,启动项目测试






3. 请求

3.1 @RequestMapping

@RequestMapping 注解用来指定处理哪些 URL 请求。

3.1.1 注解常用属性

  1. value

value 用来表示请求的 url,可以省略不写

@RequestMapping(value="/hello")

简写

@RequestMapping("/hello")
  1. method

method 用来表示请求方式,不写的话默认是 GET 请求。常用的请求方式:

POST、GET、PUT、DELETE

如果使用 method 属性,不能省略 value 属性。

@RequestMapping(value="/hello",method=RequestMethod.GET)
  public ModelAndView sayHello() {
      ModelAndView mv=new ModelAndView();
      mv.addObject("message", "你好啊,李银河,我是王小波。");
      mv.setViewName("/hello");
      return mv;
}

3.1.2 标记位置

  1. 标记在类上面

一个系统包含很多模块,例如用户、商品、订单等模块。

我们需要为不同的模块定义不同的类,然后在类上面添加 @RequestMapping 注解,表示这个模块下面统一的请求路径:例如:

// 用户操作控制器
@Controller
@RequestMapping("/user")
public class UserController {

}
// 订单操作控制器
@Controller
@RequestMapping("/order")
public class OrderController {

}
...
  1. 标记在方法上面

每个模块都有很多方法,例如增删改查等。所以我们一般会在相应的方法上面添加 @RequestMapping 注解,表示请求这个模块下的某个方法,例如:

@Controller
@RequestMapping("/user")
public class UserController {
 @RequestMapping("/list")
    public Object list() {
      return null;
    }
    @RequestMapping(value="/add",method=RequestMethod.POST)
    public Object add() {
        return null;
    }
    @RequestMapping(value="/update",method=RequestMethod.POST)
    public Object update() {
        return null;
    }
    @RequestMapping(value="/delete",method=RequestMethod.DELETE)
    public Object delete() {
        return null;
    }
}

所以当我们获取用户列表信息时,我们请求的后台接口的 url 就是:

ip地址:端口号/项目名/uset/list
//例如:
localhost:8080/ems/user/list

3.1.3 @RequestMapping 的缩写注解

方法上关于不同请求方式的注解都比较长,例如:

 @RequestMapping(value="/add",method=RequestMethod.POST)

SpringMVC 为我们提供了简化写法:

GET请求:

@GetMapping("/list")

POST 请求:

@PostMapping("/login")

DELETE 请求:

@DeleteMapping("/delete/{id}")

PUT 请求

@PutMapping("/update")

3.2 接收请求参数

3.2.1 接收多个参数

@PostMapping("/login") public Result<User> login(String username,String password) { User user=userService.login(username, password); return Result.success(user); }

3.2.2 实体类作为参数

Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。

@PostMapping("/login") public Result<User> login(User user){ User user=userService.login(user.getUsername(), user.getPassword); return Result.success(user); }

3.2.3 @RequestParam 注解

使用 @RequestParam 可以把请求参数传递给请求方法。

属性:

  1. value:参数名
  2. required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常
  3. defaultValue: 默认值,当没有传递参数时使用该值
@PostMapping("/login")
public Result<User> login(@RequestParam String username, @RequestParam String password) {
    User user=userService.login(username, password);
    return Result.success(user);
}

3.2.4 @PathVariable 注解

通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中。

URL 中的 {xxx} 占位符可以通过 @PathVariable("xxx")绑定到操作方法的入参中。

@DeleteMapping(value="/delete/{id}")
public Result<?> delete(@PathVariable("id") int id) {
    userService.remove(id);
    return Result.success();
}

3.3 解决中文乱码

请求参数如果含有中文,会出现中文乱码问题。我们可以通过在 web.xml 中配置字符过滤器来解决中文乱码问题。

<filter>
   <filter-name>encodingFilter</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
   <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
   </init-param>
   <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
   </init-param>
</filter>
<filter-mapping>
   <filter-name>encodingFilter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

注:filter 标签要在 servlet 标签之上

4. 响应

SpringMVC 提供了以下几种方式处理响应数据。

4.1 返回 String

  1. SpringMVC 的视图解析器将返回的字符串转换为视图(页面)。
@GetMapping("/hello")
public String toHello() {
    return "/hello";
}
  1. 重定向:

使用 redirect 关键字可以重定向到其他页面。

@GetMapping("/hello")
public String toHello() {
    return "redirect:/hello";
}

4.2 返回 ModelAndView

控制器在处理完用户的请求之后,如果既想跳转页面,又想传递数据,可以使用 ModelAndView 对象。

@RequestMapping(value="/hello",method=RequestMethod.GET)
  public ModelAndView sayHello() {
      ModelAndView mv=new ModelAndView();
      mv.addObject("message", "你好啊,李银河,我是王小波。");
      mv.setViewName("/hello");
      return mv;
}

4.3 返回 Model

Mode 对象也可以在跳转页面的时候传递数据。

@GetMapping("/print")
public String print(Model model) {
    model.addAttribute("msg", "祝大家五一快乐!");
    return "/hello";
}

4.4 返回 json

  1. 控制器的方法也可以返回 Object 对象,但返回的对象不是作为视图出现的,而是作为页面上显示的数据。

返回对象,需要使用 @ResponseBody 注解将对象转换为 json 格式的数据响应给浏览器。

@GetMapping(value="/print", produces="application/json;charset=utf8")
@ResponseBody
public Object print() {
    User user=new User(11111L, "张无忌", 12);
    String json=JSONUtil.toJsonStr(user);
    return json;
}



如果遇到中文乱码的问题,我们可以在 @GetMapping 注解里面设置 produces 属性。

  1. @RestController 注解

@RestController 注解是 @Controller 和 @ResponseBody 注解的组合注解,表示这个控制器中所有的方法全都返回 json 格式的数据。

4.5 返回 void

有时候我们不需要跳转页面,也不需要转发数据,我们只是希望浏览器能够渲染数据(例如后台向前台输出验证码)。这时候可以使用 HttpServletResponse 向浏览器输出数据。

@GetMapping("/print")
public void print(HttpServletResponse response) throws IOException {
    /*解决响应数据中文乱码问题*/
    response.setContentType("text/html;charset=utf8");
    PrintWriter writer=response.getWriter();
    writer.print("祝大家五一快乐");
    writer.flush();
    writer.close();
}

5. 访问静态

我们在 web.xml 中配置 url-pattern 为 /,当我们请求项目的静态资源的时候,SpringMVC 的中央调度器会先根据处理器映射器寻找相应的处理器,结果没找到,所以请求静态资源会报 404。

我们可以使用 mvc:resources 标签来解决无法访问静态资源的问题。

但是 DispatcherServlet 的映射路径为 /,而 Tomcat 默认的 Servlet 的映射路径也为/,所以 DispatcherServlet 会覆盖 Tomcat 中默认的 Servlet,去处理除 jsp 之外的所有资源,导致静态资源无法被访问。

所以这里还需要结合另外一个标签使用:

<!-- static/**表示该目录下所有文件-->
<mvc:resources mapping="/static/**" location="/static/"/>
<mvc:annotation-driven/>

这里我们测试访问项目中的照片:

@GetMapping("/hello")
public String toHello() {
    return "/hello";
}



6. 全局异常处理

在开发过程中我们会遇到各种各样的异常,我们会有两种处理方式:

  1. try-catch 捕获
  2. throws 抛出异常

但是用户不希望看到一堆异常信息:

即便是程序出错了,他们也希望看到一些看得懂的错误信息,例如:

{
  code: 500,
  message: '该商品信息不存在,请联系管理员'
}

所以我们需要搞一个全局异常处理器先捕获这些错误信息,然后返回给用户统一的结果信息。

  1. 创建全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
    //自定义异常处理器
    @ExceptionHandler(CustomException.class)
    public Result CustomExceptionHandler(Exception e) {
        
        return Result.fail(500,e.getMessage());
    }
}

@RestControllerAdvice:表示这个类是全局异常处理器,返回的格式是 json 格式。

@ExceptionHandler:捕获的异常类型

  1. 创建自定义异常
/**
 * @Desc: 自定义异常
 * @Author: 知否技术
 * @date: 下午8:05 2022/5/3
 */
public class CustomException  extends  RuntimeException{

    public CustomException(String message) {
        super(message);
    }
}

因为大部分异常是运行时异常,所以这里自定义的异常继承了运行时异常。

  1. 封装返回的统一结果类
/**
 * @Desc: 统一结果类
 * @Author: 知否技术
 * @date: 下午9:02 2022/5/3
 */
public class Result implements Serializable {

    private int code;

    private String message;

    private Object data;

    private Result(String message) {
        this.code=200;
        this.message=message;
    }

    private Result(String message, Object data) {
        this.code=200;
        this.message=message;
        this.data=data;
    }

    private Result(int code, String message) {
        this.code=code;
        this.message=message;
    }

    public static Result success(String message) {
        return new Result(message);
    }

    public static Result success(String message, Object data) {
        return new Result(message, data);
    }

    public static Result fail(int code, String message) {
        return new Result(code, message);
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code=code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message=message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data=data;
    }
}

4.测试

@GetMapping("/hello")
@ResponseBody
public Result toHello() {
    throw new CustomException("卧槽!!出错了");
}

SpringMVC 中的拦截器主要是用来拦截用户的请求,并进行相关的处理。

实现拦截器:

  1. 创建拦截器
/**
 * @Desc:
 * @Author: 知否技术
 * @date: 下午2:09 2022/5/3
 */
public class MyInterceptor implements HandlerInterceptor {

    // 在处理器中方法执行之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       
        return false;
    }
    // 在处理器中方法执行之后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
}
  1. 配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!--拦截所有请求-->
        <mvc:mapping path="/**"/>
        <!--排除请求-->
        <mvc:exclude-mapping path="/user/login"/>
        <bean class="com.zhifou.interceptor.MyInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

测试:


8. SpringMVC 执行流程


1.Tomcat 服务器启动的时候会立即创建 DispatcherServlet(中央调度器),同时会创建 SpringMVC 容器。

2.SpringMVC 容器初始化的时候会先根据配置文件中的组件扫描器先扫描一下哪些类上面有 @Controller 注解,并将这些类作为处理器类。

然后通过 @RequestMapping 注解生成对应的映射关系。这些对应关系由处理器映射器管理。

3.当收到用户的请求,中央调度器将请求转发给处理器映射器。

4.处理器映射器根据用户请求的 URL 从映射关系中找到处理该请求的处理器,然后封装成处理器执行链返回给中央处理器。

5.中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器,处理器适配器调用执行处理器。

6.处理器将处理结果及要跳转的视图封装到 ModelAndView 中,并将其返回给处理器适配器。

7.处理器适配器直接将结果返回给中央调度器,中央调度器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。

8.视图解析器将封装了的视图对象返回给中央调度器,中央调度器调用视图对象,填充数据,生成响应对象。

9.中央调度器将结果响应给浏览器。

原文链接:https://juejin.cn/post/7093670296888016933