前言
终于来到了Web项目了。今天来介绍一下Spring MVC的工作原理和常见用法。
学习技术最好的方式当然还是上手练。通过最简单的可运行代码一步步添加复杂功能。但是直接学习别人的代码还是有问题——你无法知道项目是怎么从零到现在的。因此,兜了一圈,我发现还是只能通过写博客来帮助自己学习。
Spring MVC工作原理
Spring MVC是一个Web框架,基于Model-View-Controller这个出名的Web模式实现。而作为Java Web应用,它仍然离不开Servlet容器。
下图来自嘟嘟的博客
框架解释
DispatcherServlet接口:
Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。
HandlerMapping接口:
能够完成客户请求到Controller映射。也就是所谓的“Controller路由”功能。
Controller接口:
需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。
Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。
从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。
ViewResolver接口:
Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。
运行原理
- 客户端请求被发送到DispatcherServlet
- 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller
- DispatcherServlet将请求提交到Controller
- Controller调用业务逻辑处理后,返回ModelAndView
- DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
- 视图负责将结果显示到客户端
DispatcherServlet是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工作有以下三项:
- 截获符合特定格式的URL请求。
- 初始化DispatcherServlet上下文对应的WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。
- 初始化Spring MVC的各个组成组件,并装配到DispatcherServlet中。
启用Spring MVC
XML方式
我们先来看看最熟悉的XML方式。
在传统的Java Web应用中都会有个重要的web/WEB-INF/web.xml
文件,其中定义Servlet与URL的映射关系。而要如下是一个常见的开启了Spring MVC配置的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">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--加载springmvc配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--配置文件的地址
如果不配置contextConfigLocation,默认查找的配置文件名称是classpath下的:servlet名称+"-servlet.xml"即springmvc-servlet.xml-->
<param-value>classpath:config/spring/springmvc.xml</param-value>
</init-param>
</servlet>
<!--配置spring容器监听器-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/config/spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
把DispatcherServlet
类映射到了根路径,便可以启用Spring MVC. DispatcherServlet
在加载时可以根据contextConfigLocation
中配置的springmvc.xml初始化Spring应用上下文。
两个Spring应用上下文
Spring Web也依赖于Spring容器,而Spring容器的入口是Spring应用上下文。在之前的文章中,我们介绍了使用Spring容器的方式就是显式地new一个Spring应用上下文。然而在Web应用中,我们并没有交代应该怎么做。
细心者会发现上面的配置还有个listener
的配置,它也会加载Spring的应用上下文。没错,listener是Web应用加载Spring容器的默认方式,受到Servlet容器支持。而有了Spring MVC后,这两种配置所加载的Spring上下文实际上可以共存。而它们的推荐使用方式是各有分工:
- Spring MVC的Spring应用上下文(即通过DispatcherServlet加载的上下文)负责加载包含Web组件的bean,如Controller, ViewResolver等。
- 默认的Spring应用上下文(即通过listener)则负责加载与Web层不太相关的bean,例如中间层和数据层组件。
当然,以上只是推荐,实际上可以抛弃掉listener,只用DispatcherServlet.
Java方式
其实,我们现在甚至不需要WebApp
这个文件夹,自然也不需要web.xml
配置文件了。只需要扩展AbstractAnnotationConfigDispatchServletInitializer
这个抽象类即可。这个类是Spring提供的,而Servlet 3.0之后的容器都能间接地找到这个类,并根据它的配置初始化Spring MVC。它的三个方法又恰好对应上面XML文件的配置:
-
getServletMappings()
:返回DispatcherServlet要映射到哪些路径上。 -
getServletConfigClasses()
:返回DispatcherServlet所要创建的Spring应用上下文的配置类。 -
getRootConfigClasses()
:返回通过ContextLoaderListener创建的Spring应用上下文的配置类。
Spring MVC配置
Spring MVC可以使用注解来配置各种Web组建,这也是它当初领先于Struts的一个地方。如今通过注解配置Web已经深入人心,XML就不再考虑。
要启用Spring MVC注解,只需要在Java Config类中加入@EnableWebMVC
注解即可。这里的config类就是前面指定的DispatcherServlet所要创建的Spring应用上下文的配置类。
此外,还可以在此文件中添加配置ViewResolver的类等等。而让这个类继承自WebMvcConfigurerAdapter
,则可以提供更多配置,如静态资源处理等等。
最后,别忘了使用@ComponentScan
启用组件扫描。
Controller注解
-
@Controller
:声明一个Controller,这是一个bean,并且Dispatcher会通过HandlerMapping查找它。 -
@RequestMapping
: 配置请求URL的映射。根据路径和请求动作定位方法。
返回视图名称:return 一个字符串。即controller方法的返回值是String
类型。
传递模型数据到视图中: 两种方法:
- Controller方法接受
Model
参数,然后在返回前调用model.addAttribute(key, value)
添加数据。 - 直接返回数据类型。数据将会被放到model中,数据的key将由数据类型推断出,而所对应的视图名将由请求路径推断出。
接受请求的输入:
- URI中的查询参数:在方法参数前加上
@RequestParam
注解。 -
URI中的路径参数:需要两处配置:
- 在方法参数前加上
@PathVariable
注解。 - 在
@RequestMapping
路径中添加占位符。如:@RequestMapping("/{userId}")
。
- 在方法参数前加上
- 请求体中的输入:添加一个POJO的model作为参数即可。
Controller方法支持的参数和返回类型
本段内容来自嘟嘟的博客。
支持的方法参数类型:
- HttpServlet 对象,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 对象。 这些参数Spring 在调用处理器方法的时候会自动给它们赋值,所以当在处理器方法中需要使用到这些对象的时候,可以直接在方法上给定一个方法参数的申明,然后在方法体里面直接用就可以了。但是有一点需要注意的是在使用HttpSession 对象的时候,如果此时HttpSession 对象还没有建立起来的话就会有问题。
- Spring 自己的WebRequest 对象。使用该对象可以访问到存放在HttpServletRequest 和HttpSession 中的属性值。
- InputStream 、OutputStream 、Reader 和Writer。 InputStream 和Reader 是针对HttpServletRequest 而言的,可以从里面取数据;OutputStream 和Writer 是针对HttpServletResponse 而言的,可以往里面写数据。
- 使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 标记的参数。
- 使用@ModelAttribute 标记的参数。
- java.util.Map 、Spring 封装的Model 和ModelMap。这些都可以用来封装模型数据,用来给视图做展示。
- 实体类。 可以用来接收上传的参数。
- Spring 封装的MultipartFile。用来接收上传文件。
- Spring 封装的Errors 和BindingResult 对象。这两个对象参数必须紧接在需要验证的实体对象参数之后,它里面包含了实体对象的验证结果。
支持的返回类型:
- 一个包含模型和视图的ModelAndView 对象。
- 一个模型对象,这主要包括Spring 封装好的Model 和ModelMap ,以及java.util.Map ,当没有视图返回的时候视图名称将由RequestToViewNameTranslator 来决定。
- 一个View 对象。这个时候如果在渲染视图的过程中模型的话就可以给处理器方法定义一个模型参数,然后在方法体里面往模型中添加值。
- 一个String 字符串。这往往代表的是一个视图名称。这个时候如果需要在渲染视图的过程中需要模型的话就可以给处理器方法一个模型参数,然后在方法体里面往模型中添加值就可以了。
- 返回值是void 。这种情况一般是我们直接把返回结果写到HttpServletResponse 中了,如果没有写的话,那么Spring 将会利用RequestToViewNameTranslator 来返回一个对应的视图名称。如果视图中需要模型的话,处理方法与返回字符串的情况相同。
- 如果处理器方法被注解@ResponseBody 标记的话,那么处理器方法的任何返回类型都会通过HttpMessageConverters 转换之后写到HttpServletResponse 中,而不会像上面的那些情况一样当做视图或者模型来处理。用于Restful Api.
- 除以上几种情况之外的其他任何返回类型都会被当做模型中的一个属性来处理,而返回的视图还是由RequestToViewNameTranslator 来决定,添加到模型中的属性名称可以在该方法上用@ModelAttribute(“attributeName”) 来定义,否则将使用返回类型的类名称的首字母小写形式来表示。使用@ModelAttribute 标记的方法会在@RequestMapping 标记的方法执行之前执行。
配置ViewResolver
Spring MVC自带很多种视图解析器。比如常见的:
视图解析器 | 描述 |
---|---|
InternalResourceViewResolver | 将试图解析为Web应用的内部资源(一般为JSP) |
FreeMarkerViewResolver | 将试图解析FreeMarker模板 |
ContentNegotiatingViewResolver | 根据客户请求的内容类型委托给对应的视图解析器 |
使用Java配置:声明Bean即可。例如:
@bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
使用XML配置:就是简单的创建bean。此处略过。
其它注解
-
@Service
: 声明一个service组件。它和@Controller
一样都是构造型注解,基于@Component
。 -
@Repository
: 声明一个数据层组件。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。