整合营销服务商

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

免费咨询热线:

Spring MVC的工作原理,我们来看看其源码实现

源:https://www.cnblogs.com/youzhibing/p/10695012.html

作者:youzhibing2904

遗留问题

在关于利用maven搭建ssm的博客,我们一起来探讨下问的最多的问题中,我遗留了一个问题:Spring mvc是何时、何地、如何将Model中的属性绑定到哪个作用域,这里的作用域指的是Servlet的四大作用域;不了解问题背景的可以回过头去看看我的上篇博文。

明确的解答我会放到最后,在解答问题之前,我先和大家一起来捋一捋Spring mvc的工作原理。废话不多说,开始我们神秘的探险之旅!

应用示例

在讲工作原理之前,我们先看一个简单的spring mvc(ssm)示例,以及实现的效果

工程代码地址:ssm-web 

工程结构与效果如上所示,我们不做过多的探究,我们打起精神往下看本篇的重点

工作原理

准备 - 资源的加载与初始化

1、DispatcherServlet 静态初始化

DispatcherServlet中有如下静态块

static {
 // Load default strategy implementations from properties file.
 // This is currently strictly internal and not meant to be customized
 // by application developers.
 try {
 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
 }
 catch (IOException ex) {
 throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
 }
 }

这里会将DispatcherServlet.properties中的内容读取到DispatcherServlet的属性:private static final Properties defaultStrategies中,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.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
 org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
 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

指定了DispatcherServlet策略接口的默认实现,后续DispatcherServlet初始化策略的时候会用到

2、interceptor定义的加载

spring启动过程中会调用InterceptorsBeanDefinitionParser的parse方法来解析出我们自定义的interceptor定义,封装成MappedInterceptor类型的bean定义,并放到spring容器中;我们可以简单的认为spring容器中已经存在了我们自定义的interceptor的bean定义

3、DispatcherServlet初始化策略:initStrategies

DispatcherServlet的继承图如下


DispatcherServlet是一个Servlet,tomcat启动过程中会调用其init方法,一串的调用后,会调用DispatcherServlet的initStrategies方法

protected void initStrategies(ApplicationContext context) {

initMultipartResolver(context);

initLocaleResolver(context);

initThemeResolver(context);

initHandlerMappings(context);

initHandlerAdapters(context);

initHandlerExceptionResolvers(context);

initRequestToViewNameTranslator(context);

initViewResolvers(context);

initFlashMapManager(context);

}

实例化步骤1中的默认实现,并填充到DispatcherServlet各个属性值中

4、DefaultAnnotationHandlerMapping的拦截器初始化

DispatcherServlet.properties种指定了两个默认的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,这两者的类继承图如下(我们暂时只关注DefaultAnnotationHandlerMapping)


DefaultAnnotationHandlerMapping间接实现了ApplicationContextAware,那么在DefaultAnnotationHandlerMapping实例初始化过程中,会调用setApplicationContext(ApplicationContext applicationContext)方法,一串调用后,会来到AbstractUrlHandlerMapping的initApplicationContext()

@Override
protected void initApplicationContext() throws BeansException {
 extendInterceptors(this.interceptors);
 detectMappedInterceptors(this.mappedInterceptors);
 initInterceptors();
}

初始化了DefaultAnnotationHandlerMapping的拦截器:interceptor

我们来看下具体的初始化过程,看看上面的顺序是否只是我个人的臆想?

可以看到,初始化顺序就是我们上面说的,不是我个人的意淫;此时的DefaultAnnotationHandlerMapping中有我们自定义的MyInterceptor。初始化过程我们需要关注的就是上述这些,下面我们一起看看具体请求的过程

请求的处理

请求从servlet的service开始,一路到DispatcherServlet的doDispatch,如下图

/**
 * Process the actual dispatching to the handler. 将请求分发到具体的handler,也就是我们的controller
 * <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;
 // Determine handler for the current request. 决定哪个handler来处理当前的请求
 // mappedHandler是由handler和interceptor集合组成的一个执行链,有点类似FilterChain
 mappedHandler = getHandler(processedRequest);
 if (mappedHandler == null || mappedHandler.getHandler() == null) {
 noHandlerFound(processedRequest, response);
 return;
 }
 // Determine handler adapter for the current request. 决定哪个adapter来处理当前的请求
 // handlerMapping是找出适配的handler,而真正回调handler的是adapter
 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 (logger.isDebugEnabled()) {
 String requestUri = urlPathHelper.getRequestUri(request);
 logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
 }
 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
 return;
 }
 }
 // handler的前置处理,也就是调用适配当前url的interceptor的preHandler方法
 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
 return;
 }
 try {
 // Actually invoke the handler. 真正调用handler
 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 }
 finally {
 if (asyncManager.isConcurrentHandlingStarted()) {
 return;
 }
 }
 applyDefaultViewName(request, mv);
 // handler的后置处理,也就是调用适配当前url的interceptor的postHandler方法
 mappedHandler.applyPostHandle(processedRequest, response, mv);
 }
 catch (Exception ex) {
 dispatchException = ex;
 }
 // 处理handler返回的结果,会调用适配当前url的interceptor的afterCompletion方法
 // 这里会将响应结果返回给请求者
 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
 }
 catch (Exception ex) {
 triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
 }
 catch (Error err) {
 triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
 }
 finally {
 if (asyncManager.isConcurrentHandlingStarted()) {
 // Instead of postHandle and afterCompletion
 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
 return;
 }
 // Clean up any resources used by a multipart request.
 if (multipartRequestParsed) {
 cleanupMultipart(processedRequest);
 }
 }
}

handlerMapping具体如何找到匹配当前url的handler(一般而言就是我们的controller)、handlerAdapter具体如何回调真正的handler,有兴趣的可以自行去跟下,我就不跟了。我们具体看下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 这个与我们最初的疑问有关

processDispatchResult

可以看到model中的persons会被设置到request的attributes中,然后转发请求到show_person.jsp,转发过程中request作用域的变量仍然有效,所以show_person.jsp中的jstl标签和el表达式能够取到persons变量,最后将show_person.jsp中的内容填充好之后的静态内容返回给请求者;至此就完成了一次请求的响应

问题解答

回到我们开篇的疑问:Spring mvc是何时、何地、如何将Model中的属性绑定到哪个作用域?想必大家已经知道答案了

Controller中的model、ModelMap的注入由spring mvc完成,这个不是请求传入的参数,用于绑定变量到Servlet作用域;默认情况下,在DispatcherServlet调用了真正的handler之后,将结果返回给请求者的过程中,将model、modelMap中的变量设置到了request的attributes中,转发的过程中,request中的变量仍然有效,所以show_person.jsp中能取到persons这个变量,自此疑问得到解答

总结

1、Spring MVC工作原理图

图是用的别人的,具体是谁的我也不记得了(捂脸)

2、DefaultAnnotationHandlerMapping在spring3.2中被废弃,替换成了RequestMappingHandlerMapping


对于 Web 应用程序而言,我们从浏览器发起一个请求,请求经过一系列的分发和处理,最终会进入到我们指定的方法之中,这一系列的的具体流程到底是怎么样的呢?

Spring MVC 请求流程

记得在初入职场的时候,面试前经常会背一背 Spring MVC 流程,印象最深的就是一个请求最先会经过 DispatcherServlet 进行分发处理,DispatcherServlet 就是我们 Spring MVC 的入口类,下面就是一个请求的大致流转流程(图片参考自 Spring In Action):

  1. 一个请求过来之后会到达 DispatcherServlet,但是 DispatcherServlet 也并不知道这个请求要去哪里。
  2. DispatcherServlet 收到请求之后会去查询处理器映射(HandlerMapping),从而根据浏览器发送过来的 URL 解析出请求最终应该调用哪个控制器。
  3. 到达对应控制器(Controller)之后,会完成一些逻辑处理,而且在处理完成之后会生成一些返回信息,也就是 Model,然后还需要选择对应的视图名。
  4. 将模型(Model)和视图(View)传递给对应的视图解析器(View Resolver),视图解析器会将模型和视图进行结合。
  5. 模型和视图结合之后就会得到一个完整的视图,最终将视图返回前端。

上面就是一个传统的完整的 Spring MVC 流程,为什么要说这是传统的流程呢?因为这个流程是用于前后端没有分离的时候,后台直接返回页面给浏览器进行渲染,而现在大部分应用都是前后端分离,后台直接生成一个 Json 字符串就直接返回前端,不需要经过视图解析器进行处理,也就是说前后端分离之后,流程就简化成了 1-2-3-4-7(其中第四步返回的一般是 Json 格式数据)。

Spring MVC 两大阶段

Spring MVC主要可以分为两大过程,一是初始化,二就是处理请求。初始化的过程主要就是将我们定义好的 RequestMapping 映射路径和 Controller 中的方法进行一一映射存储,这样当收到请求之后就可以处理请求调用对应的方法,从而响应请求。

初始化

初始化过程的入口方法是 DispatchServlet 的 init() 方法,而实际上 DispatchServlet 中并没有这个方法,所以我们就继续寻找父类,会发现 init 方法在其父类(FrameworkServlet)的父类 HttpServletBean 中。

HttpServletBean#init()

在这个方法中,首先会去家在一些 Servlet 相关配置(web.xml),然后会调用 initServletBean() 方法,这个方法是一个空的模板方法,业务逻辑由子类 FrameworkServlet 来实现。

FrameworkServlet#initServletBean

这个方法本身没有什么业务逻辑,主要是初始化 WebApplicationContext 对象,WebApplicationContext 继承自 ApplicationContext,主要是用来处理 web 应用的上下文。

FrameworkServlet#initWebApplicationContext

initWebApplicationContext() 方法主要就是为了找到一个上下文,找不到就会创建一个上下文,创建之后,最终会调用方法 configureAndRefreshWebApplicationContext(cwac) 方法,而这个方法最终在设置一些基本容器标识信息之后会去调用 refresh()方法,也就是初始化 ioc 容器。

当调用 refresh() 方法初始化 ioc 容器之后,最终会调用方法 onRefresh(),这个方法也是一个模板钩子方法,由子类实现,也就是回到了我们 Spring MVC 的入口类 DispatcherServlet。

DispatchServlet#onRefresh

onRefresh() 方法就是 Spring MVC 初始化的最后一个步骤,在这个步骤当中会初始化 Spring MVC 流程中可能需要使用到的九大组件。

Spring MVC 九大组件

MultipartResolver

这个组件比较熟悉,主要就是用来处理文件上传请求,通过将普通的 Request 对象包装成 MultipartHttpServletRequest 对象来进行处理。

LocaleResolver

LocaleResolver 用于初始化本地语言环境,其从 Request 对象中解析出当前所处的语言环境,如中国大陆则会解析出 zh-CN 等等,模板解析以及国际化的时候都会用到本地语言环境。

ThemeResolver

这个主要是用户主题解析,在 Spring MVC 中,一套主题对应一个 .properties 文件,可以存放和当前主题相关的所有资源,如图片,css样式等。

HandlerMapping

用于查找处理器(Handler),比如我们 Controller 中的方法,这个其实最主要就是用来存储 url 和 调用方法的映射关系,存储好映射关系之后,后续有请求进来,就可以知道调用哪个 Controller 中的哪个方法,以及方法的参数是哪些。

HandlerAdapter

这是一个适配器,因为 Spring MVC 中支持很多种 Handler,但是最终将请求交给 Servlet 时,只能是 doService(req,resp) 形式,所以 HandlerAdapter 就是用来适配转换格式的。

HandlerExceptionResolver

这个组件主要是用来处理异常,不过看名字也很明显,这个只会对处理 Handler 时产生的异常进行处理,然后会根据异常设置对应的 ModelAndView,然后交给 Render 渲染成页面。

RequestToViewNameTranslator

这个主键主要是从 Request 中获取到视图名称。

ViewResolver

这个组件会依赖于 RequestToViewNameTranslator 组件获取到的视图名称,因为视图名称是字符串格式,所以这里会将字符串格式的视图名称转换成为 View 类型视图,最终经过一系列解析和变量替换等操作返回一个页面到前端。

FlashMapManager

这个主键主要是用来管理 FlashMap,那么 FlashMap 又有什么用呢?要明白这个那就不得不提到重定向了,有时候我们提交一个请求的时候会需要重定向,那么假如参数过多或者说我们不想把参数拼接到 url 上(比如敏感数据之类的),这时候怎么办呢?因为参数不拼接在 url 上重定向是无法携带参数的。

FlashMap 就是为了解决这个问题,我们可以在请求发生重定向之前,将参数写入 request 的属性 OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的 handler 中,Spring 会自动将其设置到 Model 中,这样就可以从 Model 中取到我们传递的参数了。

处理请求

在九大组件初始化完成之后,Spring MVC 的初始化就完成了,接下来就是接收并处理请求了,那么处理请求的入口在哪里呢?处理请求的入口方法就是 DispatcherServlet 中的 doService 方法,而 doService 方法又会调用 doDispatch 方法。

DispatcherServlet#doDispatch

这个方法最关键的就是调用了 getHandler 方法,这个方法就是会获取到前面九大组件中的 HandlerMapping,然后进行反射调用对应的方法完成请求,完成请求之后后续还会经过视图转换之类的一些操作,最终返回 ModelAndView,不过现在都是前后端分离,基本也不需要用到视图模型,在这里我们就不分析后续过程,主要就是分析 HandlerMapping 的初始化和查询过程。

DispatcherServlet#getHandler

这个方法里面会遍历 handllerMappings,这个 handllerMappings 是一个 List 集合,因为 HandlerMapping 有多重实现,也就是 HandlerMapping 不止一个实现,其最常用的两个实现为 RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping。

AbstractHandlerMapping#getHandler

AbstractHandlerMapping 是一个抽象类,其 getHandlerInternal 这个方法也是一个模板方法:

getHandlerInternal 方法最终其会调用子类实现,而这里的子类实现会有多个,其中最主要的就是 AbstractHandlerMethodMapping 和 AbstractUrlHandlerMapping 两个抽象类,那么最终到底会调用哪个实现类呢?

这时候如果拿捏不准我们就可以看一下类图,上面我们提到,HandlerMapper 有两个非常主要的实现类:RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping。那么我们就分别来看一下这两个类的类图关系:

可以看到,这两个实现类的抽象父类正好对应了 AbstractHandlerMapping 的两个子类,所以这时候具体看哪个方法,那就看我们想看哪种类型了。

  • RequestMappingHandlerMapping:主要用来存储 RequestMapping 注解相关的控制器和 url 的映射关系。
  • BeanNameUrlHandlerMapping:主要用来处理 Bean name 直接以 / 开头的控制器和 url 的映射关系。

其实除了这两种 HandlerMapping 之外,Spring 中还有其他一些 HandllerMapping,如 SimpleUrlHandlerMapping 等。

提到的这几种 HandlerMapping,对我们来说最常用,最熟悉的那肯定就是 RequestMappingHandlerMapping ,在这里我们就以这个为例来进行分析,所以我们应该

AbstractHandlerMethodMapping#getHandlerInternal

这个方法本身也没有什么逻辑,其主要的核心查找 Handler 逻辑在 lookupHandlerMethod 方法中,这个方法主要是为了获取一个 HandlerMethod 对象,前面的方法都是 Object,而到这里变成了 HandlerMethod 类型,这是因为 Handler 有各种类型,目前我们已经基本跟到了具体类型之下,所以类型就变成了具体类型,而如果我们看的的另一条分支线,那么返回的就会是其他对象,正是因为支持多种不同类型的 HandlerMapping 对象,所以最终为了统一执行,才会需要在获得 Hanlder 之后,DispatcherServlet 中会再次通过调用 getHandlerAdapter 方法来进一步封装成 HandlerAdapter 对象,才能进行方法的调用

AbstractHandlerMethodMapping#lookupHandlerMethod

这个方法主要会从 mappingRegistry 中获取命中的方法,获取之后还会经过一系列的判断比较判断比较,因为有些 url 会对应多个方法,而方法的请求类型不同,比如一个 GET 方法,一个 POST 方法,或者其他一些属性不相同等等,都会导致最终命中到不同的方法,这些逻辑主要都是在 addMatchingMappings 方法去进一步实现,并最终将命中的结果加入到 matches 集合内。

在这个方法中,有一个对象非常关键,那就是 mappingRegistry,因为最终我们根据 url 到这里获取到对应的 HandlerMtthod,所以这个对象很关键:

看这个对象其实很明显可以看出来,这个对象其实只是维护了一些 Map 对象,所以我们可以很容易猜测到,一定在某一个地方,将 url 和 HandlerMapping 或者 HandlerMethod 的映射关系存进来了,这时候其实我们可以根据 getMappingsByUrl 方法来进行反推,看看 urlLookup 这个 Map 是什么时候被存入的,结合上面的类图关系,一路反推,很容易就可以找到这个 Map 中的映射关系是 AbstractHandlerMethodMapping 对象的 afterPropertiesSet 方法实现的(AbstractHandlerMethodMapping 实现了 InitializingBean 接口),也就是当这个对象初始化完成之后,我们的 url 和 Handler 映射关系已经存入了 MappingRegistry 对象中的集合 Map 中。

AbstractHandlerMethodMapping 的初始化

afterPropertiesSet 方法中并没有任何逻辑,而是直接调用了 initHandlerMethods。

AbstractHandlerMethodMapping#initHandlerMethods

initHandlerMethods 方法中,首先还是会从 Spring 的上下文中获取所有的 Bean,然后会进一步从带有 RequestMapping 注解和 Controller 注解中的 Bean 去解析并获得 HandlerMethod。

AbstractHandlerMethodMapping#detectHandlerMethods

这个方法中,其实就是通过反射获取到 Controller 中的所有方法,然后调用 registerHandlerMethod 方法将相关信息注册到 MappingRegistry 对象中的各种 Map 集合之内:

AbstractHandlerMethodMapping#register

registerHandlerMethod 方法中会直接调用 AbstractHandlerMethodMapping 对象持有的 mappingRegistry 对象中的 regidter方法,这里会对 Controller 中方法上的一些元信息进行各种解析,比如参数,路径,请求方式等等,然后会将各种信息注册到对应的 Map 集合中,最终完成了整个初始化。

总结

本文重点以 RequestMappingHandlerMapping 为例子分析了在 Spring 当中如何初始化 HandlerMethod,并最终在调用的时候又是如何根据 url 获取到对应的方法并进行执行最终完成整个流程。

、SpringMVC介绍

1.MVC介绍

模型-视图-控制器(MVC 是一个众所周知的以设计界面应用程序为基础的设计模式。它主要通过分离模型、视图及控制器在应用程序中的角色将业务逻辑从界面中解耦。通常,模型负责封装应用程序数据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请求,并调用后台服务(manager或者dao)来处理业务逻辑。处理后,后台业务层可能会返回了一些数据在视图层展示。控制器收集这些数据及准备模型在视图层展示。MVC模式的核心思想是将业务逻辑从界面中分离出来,允许它们单独改变而不会相互影响。

springmvc介绍

概念

1.Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架
2.使用了MVC架构模式的思想,将web层进行职责解耦
3.基于请求驱动指的就是使用请求-响应模型
4.框架的目的就是帮助我们简化开发,
Spring Web MVC也是要简化我们日常Web开发的。

优点

1.性能比struts2好
2.简单、便捷,易学
3.和spring无缝衔接【IOC,AOP】
4.使用约定优于配置
5.支持Restful
6.异常处理,国际化,数据验证,类型转换等
7.使用的人多,使用的公司多

二、第一个案例HelloWorld

1.创建web项目

普通web项目

2.导入相关jar包

3.创建配置文件

在src目录下创建一个 spring-mvc.xml文件,名称可以自定义。内容就是spring的schema内容

<?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>
1234567

4.设置处理器和映射器

在spring-mvc.xml中添加

<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">
	
	<!-- 处理器映射器 将bean的name作为url进行查找 ,
	              需要在配置Handler时指定beanname(就是url) 所有的映射器都实现 
		 HandlerMapping接口。
	 -->
	<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

	<!-- 配置 Controller适配器 -->
	<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
	
</beans>
123456789101112131415

5.配置前端控制器

<?xml version="1.0" encoding="UTF-8"?>
<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>test</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- 配置前端控制器 -->
  <!-- contextConfigLocation配置springmvc加载的配置文件(配置处理器映射器、适配器等等)
  	如果不配置contextConfigLocation,
  	默认加载的是/WEB-INF/servlet名称-serlvet.xml(springmvc-servlet.xml)
  	 -->
  <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:spring-mvc.xml</param-value>
  	</init-param>
  </servlet>
  
  <servlet-mapping>
  	<servlet-name>springmvc</servlet-name>
  	<url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
12345678910111213141516171819202122232425262728293031

6.创建自定义的Controller

/**
 * 自定义控制器
 * 必须实现Controller接口
 * @author dpb【波波烤鸭】
 *
 */
public class UserController implements Controller{

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("本方法被调用了...");
		ModelAndView view = new ModelAndView();
		view.setViewName("/index.jsp");
		return view;
	}
}
12345678910111213141516

7.测试效果

三、注解方式的使用

通过上一个普通实现的方式大家会发现其实现步骤比较繁琐,而且自定义controller也只有一个默认被调用的方法。不是很方便,这时我们可以使用SpringMVC基于注解的使用方式来实现,步骤如下:

1.修改配置文件开启注解方式

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	
	<!-- 开启注解 -->
	<mvc:annotation-driven></mvc:annotation-driven>
	<!-- 开启扫描 -->
	<context:component-scan base-package="com.dpb.controller"></context:component-scan>
</beans>
12345678910111213

2.controller中通过注解实现

/**
 * 自定义controller
 * @author dpb【波波烤鸭】
 *
 */
@Controller // 交给Spring容器管理
@RequestMapping("/user") // 设置请求的路径
public class UserController {
	
	/**
	 * 查询方法
	 * 请求地址是: 
	 * http://localhost:8080/SpringMVC-03-hellowordAnnation/user/query
	 * @return
	 */
	@RequestMapping("/query")
	public ModelAndView query(){
		System.out.println("波波烤鸭:query");
		ModelAndView mv = new ModelAndView();
		mv.setViewName("/index.jsp");
		return mv;
	}
	
	/**
	 * 添加方法
	 * 请求地址是: 
	 * http://localhost:8080/SpringMVC-03-hellowordAnnation/user/add
	 * @return
	 */
	@RequestMapping("/add")
	public ModelAndView add(){
		System.out.println("波波烤鸭:add");
		ModelAndView mv = new ModelAndView();
		mv.setViewName("/index.jsp");
		return mv;
	}
}
12345678910111213141516171819202122232425262728293031323334353637

3.测试

四、SpringMVC工作原理的介绍

1.原理图

2.流程文字说明

1.用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
2.DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
3.DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
4.提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
5.Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
6.根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
7.ViewResolver 结合Model和View,来渲染视图
8.将渲染结果返回给客户端。

3.组件说明