参考:http://c.biancheng.net/spring_mvc/profile.html
MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Controller)指控制层。使用 MVC 的目的是将 M 和 V 的实现代码分离,使同一个程序可以有不同的表现形式。其中,View 的定义比较清晰,就是用户界面。
在 Web 项目的开发中,能够及时、正确地响应用户的请求是非常重要的。用户在网页上单击一个 URL 路径,这对 Web 服务器来说,相当于用户发送了一个请求。而获取请求后如何解析用户的输入,并执行相关处理逻辑,最终跳转至正确的页面显示反馈结果,这些工作往往是控制层(Controller)来完成的。
在请求的过程中,用户的信息被封装在 User 实体类中,该实体类在 Web 项目中属于数据模型层(Model)。
在请求显示阶段,跳转的结果网页就属于视图层(View)。
像这样,控制层负责前台与后台的交互,数据模型层封装用户的输入/输出数据,视图层选择恰当的视图来显示最终的执行结果,这样的层次分明的软件开发和处理流程被称为 MVC 模式。
在学习 Servlet 及 JSP 开发时,JavaBean 相当于 Model,Servlet 相当于 Controller,JSP 相当于 View。
总结如下:
SUN 公司推出 JSP 技术的同时,也推出了两种 Web 应用程序的开发模式。即 JSP+JavaBean 和 Servlet+JSP+JavaBean。
JSP+JavaBean 中 JSP 用于处理用户请求,JavaBean 用于封装和处理数据。该模式只有视图和模型,一般把控制器的功能交给视图来实现,适合业务流程比较简单的 Web 程序。
通过上图可以发现 JSP 从 HTTP Request(请求)中获得所需的数据,并进行业务逻辑的处理,然后将结果通过 HTTP Response(响应)返回给浏览器。从中可见,JSP+JavaBean 模式在一定程度上实现了 MVC,即 JSP 将控制层和视图合二为一,JavaBean 为模型层。
JSP+JavaBean 模式中 JSP 身兼数职,既要负责视图层的数据显示,又要负责业务流程的控制,结构较为混乱,并且也不是我们所希望的松耦合架构模式,所以当业务流程复杂的时候并不推荐使用。
Servlet+JSP+JavaBean 中 Servlet 用于处理用户请求,JSP 用于数据显示,JavaBean 用于数据封装,适合复杂的 Web 程序。
相比 JSP+JavaBean 模式来说,Servlet+JSP+JavaBean 模式将控制层单独划分出来负责业务流程的控制,接收请求,创建所需的 JavaBean 实例,并将处理后的数据返回视图层(JSP)进行界面数据展示。
Servlet+JSP+JavaBean 模式的结构清晰,是一个松耦合架构模式,一般情况下,建议使用该模式。
任何一件事都有利有弊,下面来了解一下 MVC 的优缺点。
总之,我们通过 MVC 设计模式最终可以打造出一个松耦合+高可重用性+高可适用性的完美架构。
MVC 并不适合小型甚至中型规模的项目,花费大量时间将 MVC 应用到规模并不是很大的应用程序,通常得不偿失,所以对于 MVC 设计模式的使用要根据具体的应用场景来决定。
Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。
Spring MVC 是结构最清晰的 Servlet+JSP+JavaBean 的实现,是一个典型的教科书式的 MVC 构架,不像 Struts 等其它框架都是变种或者不是完全基于 MVC 系统的框架。
Spring MVC 角色划分清晰,分工明细,并且和 Spring 框架无缝结合。Spring MVC 是当今业界最主流的 Web 开发框架,以及最热门的开发技能。
在 Spring MVC 框架中,Controller 替换 Servlet 来担负控制器的职责,用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。Controller 调用相应的 View 并对处理结果进行视图渲染,最终客户端得到响应信息。
Spring MVC 框架采用松耦合可插拔的组件结构,具有高度可配置性,比起其它 MVC 框架更具有扩展性和灵活性。
此外,Spring MVC 的注解驱动和对 REST 风格的支持,也是它最具特色的功能。无论是在框架设计,还是扩展性、灵活性等方面都全面超越了 Struts2 等 MVC 框架。并且由于 Spring MVC 本身就是 Spring 框架的一部分,所以可以说与 Spring 框架是无缝集成,性能方面具有先天的优越性,对于开发者来说,开发效率也高于其它的 Web 框架,在企业中的应用越来越广泛,成为主流的 MVC 框架。
一个好的框架要减轻开发者处理复杂问题的负担,内部有良好的扩展,并且有一个支持它的强大用户群体,恰恰 Spring MVC 都做到了。
由于 Spring MVC 的结构较为复杂,本节只对该框架进行了简单介绍。在《第一个Spring MVC程序》一节通过搭建 Spring MVC 环境来体验 Spring MVC 是如何使用的,从而更深入的了解它的架构模型以及请求处理流程。
本节通过一个简单的 Web 应用 springmvcDemo 来演示如何创建 Spring MVC 程序。
搭建步骤如下:
创建 Web 应用并引入 JAR 包,本教程 Spring 使用版本为 5.2.3
Spring MVC 配置:在 web.xml 中配置 Servlet,创建 Spring MVC 的配置文件
创建 Controller(处理请求的控制器)
创建 View(本教程使用 JSP 作为视图)
部署运行
1. 创建Web应用并引入JAR包
创建 Web 应用 springmvcDemo,在 springmvcDemo 的 lib 目录中添加 Spring MVC 所依赖的 JAR 包。
Spring MVC 依赖 JAR 文件包括 Spring 的核心 JAR 包和 commons-logging 的 JAR 包。
Maven 项目在 pom.xml 文件中添加以下内容:
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.2version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.35version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.3.RELEASEversion>
dependency>
<dependency>
<groupId>com.github.stefanbirknergroupId>
<artifactId>system-rulesartifactId>
<version>1.16.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.9version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.4version>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
dependencies>
2. Spring MVC配置
Spring MVC 是基于 Servlet 的,DispatcherServlet 是整个 Spring MVC 框架的核心,主要负责截获请求并将其分派给相应的处理器处理。所以配置 Spring MVC,首先要定义 DispatcherServlet。跟所有 Servlet 一样,用户必须在 web.xml 中进行配置。
1)定义DispatcherServlet(Spring的核心:请求分发器,前端控制器)
在开发 Spring MVC 应用时需要在 web.xml 中部署 DispatcherServlet,
Spring MVC 初始化时将在应用程序的 WEB-INF 目录下查找配置文件,该配置文件的命名规则是“servletName-servlet.xml”,例如 springmvc-servlet.xml。
也可以将 Spring MVC 的配置文件存放在应用程序目录中的任何地方,但需要使用 servlet 的 init-param 元素加载配置文件,通过 contextConfigLocation 参数来指定 Spring MVC 配置文件的位置,示例代码如下。
<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>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet servlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
此处使用 Spring 资源路径的方式进行指定,即 classpath:springmvc-servlet.xml
(在resource目录下的springmvc-servlet.xml)
上述代码配置了一个名为“springmvc”的 Servlet。该 Servlet 是 DispatcherServlet 类型,它就是 Spring MVC 的入口,并通过
配置标记容器在启动时就加载此 DispatcherServlet,即自动启动。然后通过 servlet-mapping 映射到“/”,即 DispatcherServlet 需要截获并处理该项目的所有 URL 请求。
2)创建Spring MVC配置文件
在 WEB-INF 目录下创建 springmvc-servlet.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 class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
bean>
<bean id="/hello" class="com.stu.controller.HelloController"/>
beans>
3. 创建Controller
在 src 目录下创建 com.stu.controller 包,
package com.stu.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//ModelAndView 模型和视图
ModelAndView mv = new ModelAndView();
//封装对象,放在ModelAndView中
mv.addObject("msg","Hello SpringMVC!");
//封装要跳转的视图,放在ModelAndView中
mv.setViewName("hello"); // 由于前后缀 WEB-INF/jsp/hello.jsp ---> hello
return mv;
}
}
4. 创建View
在 WEB-INF 下创建 jsp 文件夹,将 hello.jsp 放到 jsp 文件夹下。hello.jsp 代码如下。
<%--
Created by IntelliJ IDEA.
User: wp990
Date: 2021/10/15
Time: 21:20
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
${msg}
5. 部署运行
将 springmvcDemo 项目部署到 Tomcat 服务器,并访问 http://localhost:8081/hello
6.可能出现404问题
该项目没有lib目录,需要手动添加lib目录并导入相应的jar包
Spring MVC 和 Struts2 类似,是一款基于传统 MVC 设计模式的 Java EE 框架。它的核心是一个弹性的控制层,能够很好地发挥 MVC 模式的“分离显示逻辑和业务逻辑”的能力。
而近年来越来越多的开发者选择使用 Spring MVC 技术来代替 Struts2 技术,那么相比于 Struts2 框架,Spring MVC 的优点在哪里呢?
下面来分析一下两者的区别。
1. Spring MVC 基于方法开发,Struts2 基于类开发。
在使用 Spring MVC 框架进行开发时,会将 URL 请求路径与 Controller 类的某个方法进行绑定,请求参数作为该方法的形参。当用户请求该 URL 路径时, Spring MVC 会将 URL 信息与 Controller 类的某个方法进行映射,生成 Handler 对象,该对象中只包含了一个 method 方法。方法执行结束之后,形参数据也会被销毁。
而在使用 Struts2 框架进行开发时,Action 类中所有方法使用的请求参数都是 Action 类中的成员变量,随着方法变得越来越多,就很难分清楚 Action 中那么多的成员变量到底是给哪一个方法使用的,整个 Action 类会变得十分混乱。
相比较而言,Spring MVC 优点是其所有请求参数都会被定义为相应方法的形参,用户在网页上的请求路径会被映射到 Controller 类对应的方法上,此时请求参数会注入到对应方法的形参上。Spring MVC 的这种开发方式类似于 Service 开发。
2. Spring MVC 可以进行单例开发,Struts2 无法使用单例
Spring MVC 支持单例开发模式,而 Struts2 由于只能通过类的成员变量接受参数,所以无法使用单例模式,只能使用多例。
3. 经过专业人员的大量测试,Struts2 的处理速度要比 SpringMVC 慢,原因是 Struts2 使用了 Struts 标签,Struts 标签由于设计原因,会出现加载数据慢的情况
这里仅仅比较了 Spring MVC 在某些方面相比 Struts2 的优势,但这并不能说明 Spring MVC 比 Struts2 优秀,仅仅因为早期 Struts2 使用广泛,所以出现的漏洞也比较多,但是在新版本的 Struts2 中也修复了许多漏洞。Spring MVC 自诞生以来,几乎没有什么致命的漏洞 。且 Spring MVC 基于方法开发,这一点较接近 Service 开发,这也是 Spring MVC 近年来备受关注的原因之一。
没有使用过 Struts2 的开发者,可以尝试操作 Struts2 的样例,比较其与 Spring MVC 开发模式的不同。
视图解析器(ViewResolver)是 Spring MVC 的重要组成部分,负责将逻辑视图名解析为具体的视图对象。
Spring MVC 提供了很多视图解析类,其中每一项都对应 Java Web 应用中特定的某些视图技术。下面介绍一些常用的视图解析类。
UrlBasedViewResolver 是对 ViewResolver 的一种简单实现,主要提供了一种拼接 URL 的方式来解析视图。
UrlBasedViewResolver 通过 prefix 属性指定前缀,suffix 属性指定后缀。当 ModelAndView 对象返回具体的 View 名称时,它会将前缀 prefix 和后缀 suffix 与具体的视图名称拼接,得到一个视图资源文件的具体加载路径,从而加载真正的视图文件并反馈给用户。
使用 UrlBasedViewResolver 除了要配置前缀和后缀属性之外,还需要配置“viewClass”,表示解析成哪种视图。示例代码如下。
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
bean>
上述视图解析器配置了前缀和后缀两个属性,这样缩短了 view 路径。因此Controller 控制器类的视图路径仅需提供 hello,视图解析器将会自动添加前缀和后缀,此处解析为 /WEB-INF/jsp/register.jsp 和 /WEB-INF/jsp/hello.jsp。
上述 viewClass 值为 InternalResourceViewResolver,它用来展示 JSP 页面。如果需要使用 jstl 标签展示数据,将 viewClass 属性值指定为 JstlView 即可。
注意:
在web项目中,为了安全,可能需要把jsp文件放在WEB-INF目录下,这样如果我们的页面中出现超链接a标签或者js的location.href去直接转向到WEB-INF下的某一个jsp页面,那么就会引用不到,因为这样的请求方式是客户端的请求,而WEB-INF页面只对服务端开放,对客户端是不可见的。这时候我们可以使用action,来进行转向,我们先去请求一个action,然后由这个action分发到这个WEB-INF下的页面就可以了。我们可以自己定义一个类似struts1的DispatcherAction的一个action来分发页面。
由于WEB-INF下对客户端是不可见的,所以相关的资源文件,如css,javascript和图片等资源文件不能放在WEB-INF下
InternalResourceViewResolver 为“内部资源视图解析器”,是日常开发中最常用的视图解析器类型。它是 URLBasedViewResolver 的子类,拥有 URLBasedViewResolver 的一切特性。
InternalResourceViewResolver 能自动将返回的视图名称解析为 InternalResourceView 类型的对象。InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。也就是说,使用 InternalResourceViewResolver 视图解析时,无需再单独指定 viewClass 属性。示例代码如下。
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
bean>
FreeMarkerViewResolver 是 UrlBasedViewResolver 的子类,可以通过 prefix 属性指定前缀,通过 suffix 属性指定后缀。
FreeMarkerViewResolver 最终会解析逻辑视图配置,返回 freemarker 模板。不需要指定 viewClass 属性。
FreeMarkerViewResolver 配置如下。
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="prefix" value="fm_"/>
<property name="suffix" value=".ftl"/>
bean>
下面指定 FreeMarkerView 类型最终生成的实体视图(模板文件)的路径以及其他配置。需要给 FreeMarkerViewResolver 设置一个 FreeMarkerConfig 的 bean 对象来定义 FreeMarker 的配置信息,代码如下。
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/ftl" />
bean>
定义了 templateLoaderPath 属性后,Spring 可以通过该属性找到 FreeMarker 模板文件的具体位置。当有模板位于不同的路径时,可以配置 templateLoaderPath 属性,来指定多个资源路径。
然后定义一个 Controller,让其返回 ModelAndView,同时定义一些返回参数和视图信息。
@Controller
@RequestMapping("viewtest")
public class ViewController {
@RequestMapping("freemarker")
public ModelAndView freemarker() {
ModelAndView mv = new ModelAndView();
mv.addObject("username", "BianChengBang");
mv.setViewName("freemarker");
return mv;
}
}
当 FreeMarkerViewResolver 解析逻辑视图信息时,会生成一个 URL 为“前缀+视图名+后缀”(这里即“fm_freemarker.ftl”)的 FreeMarkerView 对象,然后通过 FreeMarkerConfigurer 的配置找到 templateLoaderPath 对应文本文件的路径,在该路径下找到该文本文件,从而 FreeMarkerView 就可以利用该模板文件进行视图的渲染,并将 model 数据封装到即将要显示的页面上,最终展示给用户。
在 /WEB-INF/ftl 文件夹下创建 fm_freemarker.ftl,代码如下。
<html>
<head>
<title>FreeMarkertitle>
head>
<body>
<b>Welcome!b>
<i>${username }i>
body>
html>
最终返回给用户的视图如下所示。
fm_freemarker.ftl页面
在学习框架之前,首先来了解一下 Spring MVC 框架的整体请求流程和使用到的 API 类。
Spring MVC 框架是高度可配置的,包含多种视图技术,例如 JSP、FreeMarker、Tiles、iText 和 POI。Spring MVC 框架并不关心使用的视图技术,也不会强迫开发者只使用 JSP。
Spring MVC 涉及到的组件有 DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。下面对各个组件的功能说明如下。
1)DispatcherServlet
DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。
2)HandlerMapping
HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。
3)HandlerAdapter
HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。
4)Handler
Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。
5)View Resolver
View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。
6)View
View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。
以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。
注意:由于 Spring MVC 结构比较复杂,所以学习的时候也要掌握学习方法。首先要明确 Spring MVC 是一个工具,既然是工具,那么我们就需要先掌握工具的使用方法,不要陷入细节中,深入浅出,慢慢通过实际运用来加深对其的理解。
Spring MVC 执行流程如图 1 所示。
SpringMVC 的执行流程如下。
Handle是什么?
Handler是一个Controller的对象和请求方式的组合的一个Object对象
HandleExcutionChains是HandleMapping返回的一个处理执行链,它是对Handle的二次封装,将拦截器关联到一起。然后,在DispatcherServlert中完成了拦截器链对handler的过滤。
DispatcherServlet要将一个请求交给哪个特定的Controller,它需要咨询一个Bean——这个Bean的名字为“HandlerMapping”。HandlerMapping是把一个URL指定到一个Controller上,(就像应用系统的web.xml文件使用将URL映射到servlet)。
SpringMVC的执行流程再次理解:
以下组件通常使用框架提供实现:
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
组件:
1.前端控制器DispatcherServlet(不需要工程师开发),由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
2.处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3.处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4.处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5.视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
6.视图View(需要工程师开发jsp…)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)
核心架构的具体流程步骤如下:
1.首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2.DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3.DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4.HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
5.ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6.View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
下边两个组件通常情况下需要开发:
Handler:处理器,即后端控制器用controller表示。
View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。
总结:
看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件:
前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。
处理器映射器(HandlerMapping):根据URL去查找处理器
处理器(Handler):(需要程序员去写代码处理逻辑的)
处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)
视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面
@Controller和@RequestMapping
Spring 2.5 版本新增了 Spring MVC 注解功能,用于替换传统的基于 XML 的 Spring MVC 配置。
在《第一个Spring MVC应用》一节中创建了两个传统风格的控制器,它们是实现 Controller 接口的类。传统风格的控制器不仅需要在配置文件中部署映射,而且只能编写一个处理方法,不够灵活。
使用基于注解的控制器具有以下 2 个优点:
下面介绍在 Spring MVC 中最重要的两个注解类型:@Controller 和 @RequestMapping。
本节示例基于《第一个Spring MVC应用》一节中的代码实现。
@Controller 注解用于声明某类的实例是一个控制器。例如,在 net.biancheng.controller 包中创建控制器类 IndexController,示例代码如下。
package net.biancheng.controller;
import org.springframework.stereotype.Controller;
@Controller public class IndexController {
// 处理请求的方法
}
Spring MVC 使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用
元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。
例如,在 springmvcDemo 应用的配置文件 springmvc-servlet.xml 中添加以下代码:
<context:component-scan base-package="net.biancheng.controller" />
@Controller 一旦出现,就代表这个类别spring接管,被这个注解的类中的所有方法的返回值如果是String,并且存在具体的页面可以跳转,那么就会被视图解析器解析
一个控制器内有多个处理请求的方法,如 UserController 里通常有增加用户、修改用户信息、删除指定用户、根据条件获取用户列表等。每个方法负责不同的请求操作,而 @RequestMapping 就负责将请求映射到对应的控制器方法上。
在基于注解的控制器类中可以为每个请求编写对应的处理方法。使用 @RequestMapping 注解将请求与处理方法一 一对应即可。
@RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。
@RequestMapping 注解常用属性如下。
value 属性是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称。如下。
@RequestMapping(value="toUser")
@RequestMapping("toUser")
value 属性支持通配符匹配,如 @RequestMapping(value=“toUser/*”) 表示 http://localhost:8080/toUser/1 或 http://localhost:8080/toUser/hahaha 都能够正常访问。
path 属性和 value 属性都用来作为映射使用。即 @RequestMapping(value=“toUser”) 和 @RequestMapping(path=“toUser”) 都能访问 toUser() 方法。
path 属性支持通配符匹配,如 @RequestMapping(path=“toUser/*”) 表示 http://localhost:8080/toUser/1 或 http://localhost:8080/toUser/hahaha 都能够正常访问。
name属性相当于方法的注释,使方法更易理解。如 @RequestMapping(value = “toUser”,name = “获取用户信息”)。
method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。
@RequestMapping(value = “toUser”,method = RequestMethod.GET) 表示该方法只支持 GET 请求。也可指定多个 HTTP 请求,如 @RequestMapping(value = “toUser”,method = {RequestMethod.GET,RequestMethod.POST}),说明该方法同时支持 GET 和 POST 请求。
params 属性用于指定请求中规定的参数,代码如下。
@RequestMapping(value = "toUser",params = "type")
public String toUser() {
return "showUser";
}
以上代码表示请求中必须包含 type 参数时才能执行该请求。即 http://localhost:8080/toUser?type=xxx 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser 则不能正常访问 toUser() 方法。
@RequestMapping(value = "toUser",params = "type=1")
public String toUser() {
return "showUser";
}
以上代码表示请求中必须包含 type 参数,且 type 参数为 1 时才能够执行该请求。即 http://localhost:8080/toUser?type=1 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser?type=2 则不能正常访问 toUser() 方法。
header 属性表示请求中必须包含某些指定的 header 值。
@RequestMapping(value = “toUser”,headers = “Referer=http://www.xxx.com”) 表示请求的 header 中必须包含了指定的“Referer”请求头,以及值为“http://www.xxx.com”时,才能执行该请求。
consumers 属性用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。如
@RequestMapping(value = “toUser”,consumes = “application/json”)。
produces 属性用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。如 @RequestMapping(value = “toUser”,produces = “application/json”)。
除此之外,produces 属性还可以指定返回值的编码。如 @RequestMapping(value = “toUser”,produces = “application/json,charset=utf-8”),表示返回 utf-8 编码。
使用 @RequestMapping 来完成映射,具体包括 4 个方面的信息项:请求 URL、请求参数、请求方法和请求头。
方法级别注解的示例代码如下。
package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controllerpublic
class IndexController {
@RequestMapping(value = "/index/login")
public String login() {
return "login";
}
@RequestMapping(value = "/index/register")
public String register() {
return "register";
}
}
上述示例中有两个 RequestMapping 注解语句,它们都作用在处理方法上。在整个 Web 项目中,@RequestMapping 映射的请求信息必须保证全局唯一。
用户可以使用如下 URL 访问 login 方法(请求处理方法),在访问 login 方法之前需要事先在 /WEB-INF/jsp/ 目录下创建 login.jsp。
http://localhost:8080/springmvcDemo/index/login
类级别注解的示例代码如下:
package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller@RequestMapping("/index")
public class IndexController {
@RequestMapping("/login")
public String login() {
return "login";
}
@RequestMapping("/register")
public String register() {
return "register";
}
}
在类级别注解的情况下,控制器类中的所有方法都将映射为类级别的请求。用户可以使用如下 URL 访问 login 方法。
http://localhost:8080/springmvcDemo/index/login
为了方便维护程序,建议开发者采用类级别注解,将相关处理放在同一个控制器类中。例如,对用户的增、删、改、查等处理方法都可以放在 UserController 控制类中。
@RequestMapping 除了可以使用请求 URL 映射请求之外,还可以使用请求参数、请求方法来映射请求,通过多个条件可以让请求映射更加精确。
package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controllerpublic class IndexController {
@RequestMapping(value = "/index/success" method=RequestMethod.GET, Params="username")
public String success(@RequestParam String username) {
return "index";
}
}
上述代码中,@RequestMapping 的 value 表示请求的 URL;method 表示请求方法,此处设置为 GET 请求,若是 POST 请求,则无法进入 success 这个处理方法中。params 表示请求参数,此处参数名为 username。
在控制类中每个请求处理方法可以有多个不同类型的参数,以及一个多种类型的返回结果。
如果需要在请求处理方法中使用 Servlet API 类型,那么可以将这些类型作为请求处理方法的参数类型。Servlet API 参数类型的示例代码如下:
package net.biancheng.controller;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/index")
public class IndexController {
@RequestMapping("/login")
public String login(HttpSession session,HttpServletRequest request) {
session.setAttribute("skey", "session范围的值");
session.setAttribute("rkey", "request范围的值");
return "login";
}
}
除了 Servlet API 参数类型以外,还有输入输出流、表单实体类、注解类型、与 Spring 框架相关的类型等,这些类型在后续章节中使用时再详细介绍。
其中特别重要的类型是 org.springframework.ui.Model 类型,该类型是一个包含 Map 的 Spring MVC类型。在每次调用请求处理方法时 Spring MVC 都将创建 org.springframework.ui.Model 对象。Model 参数类型的示例代码如下:
package net.biancheng.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/index")
public class IndexController {
@RequestMapping("/register")
public String register(Model model) {
/*在视图中可以使用EL表达式${success}取出model中的值*/
model.addAttribute("success", "注册成功");
return "register";
}
}
请求处理方法可以返回如下类型的对象:
最常见的返回类型就是代表逻辑视图名称的 String 类型,例如前面几节中的请求处理方法。
web.xml 代码如下。
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>springMVCdisplay-name>
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/springmvc-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>web-app>
springmvc-servlet.xml 配置文件代码如下。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
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.xsd http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="net.biancheng.controller" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
bean>
beans>
创建 User 实体类,代码如下。如前面所说,使用 Controller 注解的一个优点在于一个控制类可以包含多个请求处理方法。创建 UserController,代码如下。
package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import net.biancheng.po.User;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String getLogin(Model model) {
User us = new User();
us.setName("编程帮");
model.addAttribute("user", us);
return "login";
}
@RequestMapping("/register")
public String getRegister() {
return "register";
}
}
index.jsp 文件页面代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
Insert title here
未注册的用户,请
注册!
已注册的用户,去
登录!
login.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
Insert title here
登录页面! 欢迎 ${user.name} 登录
register.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
Insert title here
注册页面!
在index.jsp页面中,当用户单击“注册”超链接时,控制器会将该请求转发给 UserController 的 getLogin 方法处理,处理后跳转到 /WEB-INF/jsp 下的 register.jsp 视图。同理,当单击“登录”超链接时,控制器处理后转到 /WEB-INF/jsp下的 login.jsp 视图。
Spring MVC Controller 接收请求参数的方式有很多种,有的适合 get 请求方式,有的适合 post 请求方式,有的两者都适合。主要有以下几种方式:
下面分别介绍这些方式,读者可以根据实际情况选择合适的接收方式。
实体 Bean 接收请求参数适用于 get 和 post 提交请求方式。需要注意,Bean 的属性名称必须与请求参数名称相同。示例代码如下。
@RequestMapping("/login")
public String login(User user, Model model) {
if ("bianchengbang".equals(user.getName())
&& "123456".equals(user.getPwd())) {
model.addAttribute("message", "登录成功");
return "main";
// 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
通过处理方法的形参接收请求参数就是直接把表单参数写在控制器类相应方法的形参中,即形参名称与请求参数名称完全相同。该接收参数方式适用于 get 和 post 提交请求方式。示例代码如下:
@RequestMapping("/login")
public String login(String name, String pwd, Model model) {
if ("bianchengbang".equals(user.getName())
&& "123456".equals(user.getPwd())) {
model.addAttribute("message", "登录成功");
return "main";
// 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
通过 HttpServletRequest 接收请求参数适用于 get 和 post 提交请求方式,示例代码如下:
@RequestMapping("/login")
public String login(HttpServletRequest request, Model model) {
String name = request.getParameter("name");
String pwd = request.getParameter("pwd");
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main";
// 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
通过 @PathVariable 获取 URL 中的参数,示例代码如下。
@RequestMapping("/login/{name}/{pwd}")
public String login(@PathVariable String name, @PathVariable String pwd, Model model) {
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main";
// 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
在访问“http://localhost:8080/springMVCDemo02/user/register/bianchengbang/123456”路径时,上述代码会自动将 URL 中的模板变量 {name} 和 {pwd} 绑定到通过 @PathVariable 注解的同名参数上,即 name=bianchengbang、pwd=123456。
在方法入参处使用 @RequestParam 注解指定其对应的请求参数。@RequestParam 有以下三个参数:
通过 @RequestParam 接收请求参数适用于 get 和 post 提交请求方式,示例代码如下。
@RequestMapping("/login")
public String login(@RequestParam String name, @RequestParam String pwd, Model model) {
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main";
// 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
该方式与“通过处理方法的形参接收请求参数”部分的区别如下:当请求参数与接收参数名不一致时,“通过处理方法的形参接收请求参数”不会报 404 错误,而“通过 @RequestParam 接收请求参数”会报 404 错误。
@ModelAttribute 注解用于将多个请求参数封装到一个实体对象中,从而简化数据绑定流程,而且自动暴露为模型数据,在视图页面展示时使用。
而“通过实体 Bean 接收请求参数”中只是将多个请求参数封装到一个实体对象,并不能暴露为模型数据(需要使用 model.addAttribute 语句才能暴露为模型数据,数据绑定与模型数据展示后面教程中会讲解)。
通过 @ModelAttribute 注解接收请求参数适用于 get 和 post 提交请求方式,示例代码如下。
@RequestMapping("/login")
public String login(@ModelAttribute("user") User user, Model model) {
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main";
// 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
Spring MVC 请求方式分为转发、重定向 2 种,分别使用 forward 和 redirect 关键字在 controller 层进行处理。
重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域;转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。
转发是服务器行为,重定向是客户端行为。
客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。
在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。
在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。
在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。示例代码如下:
@RequestMapping("/register")
public String register() {
return "register";
//转发到register.jsp
}
在 Spring MVC 框架中,重定向与转发的示例代码如下:
package net.biancheng.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller@RequestMapping("/index")
public class IndexController {
@RequestMapping("/login")
public String login() {
//转发到一个请求方法(同一个控制器类可以省略/index/)
return "forward:/index/isLogin";
}
@RequestMapping("/isLogin")
public String isLogin() {
//重定向到一个请求方法
return "redirect:/index/isRegister";
}
@RequestMapping("/isRegister")
public String isRegister() {
//转发到一个视图
return "register";
}
}
在 Spring MVC 框架中,不管是重定向或转发,都需要符合视图解析器的配置,如果直接转发到一个不需要 DispatcherServlet 的资源,例如:
return “forward:/html/my.html”;
则需要使用 mvc:resources 配置:
在表单提交的时候,get方法提交中文数据时不会乱码(tomcat已经帮我们解析好了),但是post提交时会产生中文乱码情况。
原理:在前端提交到后台时添加一层过滤网
类:
import javax.servlet.*;
import java.io.IOException;
public class EoncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//设置请求与发送的编码格式
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
//让servletRequest,servletResponse数据继续走
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
web.xml配置
<filter>
<filter-name>encodingfilter-name>
<filter-class>com.stu.filter.EoncodingFilterfilter-class>
filter>
<filter-mapping>
<filter-name>encodingfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
在web.xml中配置如下
filter-mapping>
<filter>
<filter-name>encodingfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>encodingfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
在开发中存在一个问题:后端的数据前端程序员不认识,前端的数据后端程序的也不一定认识。因此需要一种统一格式,让前后端的数据流动统一,json诞生
JSON(JavaScript Object Notation, JS 对象标记)是一种轻量级的数据交换格式。与 XML 一样,JSON 也是基于纯文本的数据格式。它有对象结构和数组结构两种数据结构。
对象结构以{
开始、以}
结束,中间部分由 0 个或多个以英文,
分隔的 key/value 对构成,key 和 value 之间以英文:
分隔。对象结构的语法结构如下:
{
key1:value1,
key2:value2,
…
}
其中,key 必须为 String 类型,value 可以是 String、Number、Object、Array 等数据类型。例如,一个 person 对象包含姓名、密码、年龄等信息,使用 JSON 的表示形式如下:
{
“pname”:“张三”,
“password”:“123456”,
“page”:40
}
数组结构以[
开始、以]
结束,中间部分由 0 个或多个以英文,
分隔的值的列表组成。数组结构的语法结构如下:
{
value1,
value2,
…
}
上述两种(对象、数组)数据结构也可以分别组合构成更加复杂的数据结构。例如,一个 student 对象包含 sno、sname、hobby 和 college 对象,其 JSON 的表示形式如下:
{
“sno”:“201802228888”,
“sname”:“张三”,
“hobby”:[“篮球”,“足球”],
“college”:{
“cname”:“清华大学”,
“city”:“北京”
}
}
为实现浏览器与控制器类之间的 JSON 数据交互,Spring MVC 提供了 MappingJackson2HttpMessageConverter 实现类默认处理 JSON 格式请求响应。该实现类利用 Jackson 开源包读写 JSON 数据,将 Java 对象转换为 JSON 对象和 XML 文档,同时也可以将 JSON 对象和 XML 文档转换为 Java 对象。
在使用注解开发时需要用到两个重要的 JSON 格式转换注解,分别是 @RequestBody 和 @ResponseBody。
需要注意的是,在该处理方法上,除了通过 @RequestMapping 指定请求的 URL,还有一个 @ResponseBody 注解。该注解的作用是将标注该注解的处理方法的返回结果直接写入 HTTP Response Body(Response 对象的 body 数据区)中。一般情况下,@ResponseBody 都会在异步获取数据时使用,被其标注的处理方法返回的数据都将输出到响应流中,客户端获取并显示数据。
早期 JSON 的组装和解析都是通过手动编写代码来实现的,这种方式效率不高,所以后来有许多的关于组装和解析 JSON 格式信息的工具类出现,如 json-lib、Jackson、Gson 和 FastJson 等,可以解决 JSON 交互的开发效率。
json-lib 最早也是应用广泛的 JSON 解析工具,缺点是依赖很多的第三方包,如 commons-beanutils.jar、commons-collections-3.2.jar、commons-lang-2.6.jar、commons-logging-1.1.1.jar、ezmorph-1.0.6.jar 等。
对于复杂类型的转换,json-lib 在将 JSON 转换成 Bean 时还有缺陷,比如一个类里包含另一个类的 List 或者 Map 集合,json-lib 从 JSON 到 Bean 的转换就会出现问题。
所以 json-lib 在功能和性能上面都不能满足现在互联网化的需求。
开源的 Jackson 是 Spring MVC 内置的 JSON 转换工具。相比 json-lib 框架,Jackson 所依赖 jar 文件较少,简单易用并且性能也要相对高些。并且 Jackson 社区相对比较活跃,更新速度也比较快。
但是 Jackson 对于复杂类型的 JSON 转换 Bean 会出现问题,一些集合 Map、List 的转换出现问题。而 Jackson 对于复杂类型的 Bean 转换 JSON,转换的 JSON 格式不是标准的 JSON 格式。
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8value>
list>
property>
bean>
mvc:message-converters>
mvc:annotation-driven>
Gson 是目前功能最全的 JSON 解析神器,Gson 当初是应 Google 公司内部需求由 Google 自行研发。自从在 2008 年 5 月公开发布第一版后,Gson 就已经被许多公司或用户应用。
Gson 主要提供了 toJson 与 fromJson 两个转换函数,不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。在使用这两个函数转换之前,需要先创建好对象的类型以及其成员才能成功的将 JSON 字符串转换为相对应的对象。
类里面只要有 get 和 set 方法,Gson 完全可以将复杂类型的 JSON 到 Bean 或 Bean 到 JSON 的转换,是 JSON 解析的神器。Gson 在功能上面无可挑剔,但性能比 FastJson 有所差距。
FastJson 是用 Java 语言编写的高性能 JSON 处理器,由阿里巴巴公司开发。
FastJson 不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。
FastJson 在复杂类型的 Bean 转换 JSON 上会出现一些问题,可能会出现引用的类型,导致 JSON 转换出错,需要制定引用。
FastJson 采用独创的算法,将 parse 的速度提升到极致,超过所有 JSON 库。
综上 4 种 JSON 技术的比较,在项目选型的时候可以使用 Google 的 Gson 和阿里巴巴的 FastJson 两种并行使用,如果只是功能要求,没有性能要求,可以使用Google 的 Gson。如果有性能上面的要求可以使用 Gson 将 Bean 转换 JSON 确保数据的正确,使用 FastJson 将 JSON 转换 Bean。
本节示例基于阿里巴巴提供的 FastJson。下面结合具体需求演示 Spring MVC 如何处理 JSON 格式数据。(本节代码基于《》一节的 springmvcDemo2 实现)
导入所需 jar 包 fastjson-1.2.62.jar,下载地址:https://github.com/alibaba/fastjson/releases。
Maven 项目在 pom.xml 文件中添加以下依赖。
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
在 springmvc-servlet.xml 中添加以下代码。
<mvc:annotation-driven>
<mvc:message-converters>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8" />
bean>
<bean
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4" />
mvc:message-converters>mvc:annotation-driven><mvc:default-servlet-handler />
<bean id="fastJsonpResponseBodyAdvice"
class="com.alibaba.fastjson.support.spring.FastJsonpResponseBodyAdvice">
<constructor-arg>
<list>
<value>callbackvalue>
<value>jsonpvalue>
list>
constructor-arg>
bean>
<mvc:resources location="/" mapping="/**" />
在 net.biancheng.pojo 包下创建 User 类,代码如下。
package net.biancheng.po;
public class User {
private String name;
private String password;
private Integer age;
/**省略setter和getter方法*/
}
创建 index.jsp 页面测试 JSON 数据交互,代码如下。
<%@ page language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试JSON交互title>
<script type="text/javaScript" src="${pageContext.request.contextPath }/js/jquery-3.2.1.min.js">script>head><body> <form action="">
用户名:<input type="text" name="name" id="name" /> <br>
密码:<input type="password" name="password" id="password" /> <br>
年龄:<input type="text" name="age" id="age"> <br>
<input type="button" value="测试" onclick="testJson()" />
form>
body>
<script type="text/javaScript">
function testJson() {
var name = $("#name").val();
var password = $("#password").val();
var age = $("#age").val();
$.ajax({
//请求路径
url : "${pageContext.request.contextPath}/testJson",
//请求类型
type : "post",
//data表示发送的数据
data : JSON.stringify({
name : name,
password : password,
age : age
}),
//定义发送请求的数据格式为JSON字符串
contentType : "application/json;charset=utf-8",
//定义回调响应的数据格式为JSON字符串,该属性可以省略
dataType : "json",
//成功响应的结果
success : function(data) {
if (data != null) {
alert("输入的用户名:" + data.name + ",密码:" + data.password
+ ", 年龄:" + data.age);
}
}
});
}
script>
body>
html>
由于在 index.jsp 中使用的是 JQuery 的 AJAX 进行的 JSON 的数据提交和响应,所以还需要引入 jquery.js 文件。这里我们引入的是 webapp 目录下的 js 文件夹中的 jquery-3.2.1.min.js。
在 UserController 中添加以下代码。
/*** 接收页面请求的JSON参数,并返回JSON格式的结果*/
@RequestMapping("/testJson")
@ResponseBody
public User testJson(@RequestBody User user) {
// 打印接收的 JSON数据
System.out.println("name=" + user.getName() + ",password=" + user.getPassword() + ",age=" + user.getAge());
// 返回JSON格式的响应
return user;
}
在上述控制器类中编写了接收和响应 JSON 格式数据的 testJson 方法,方法中的 @RequestBody 注解用于将前端请求体中的 JSON 格式数据绑定到形参 user 上,@ResponseBody 注解用于直接返回 Person 对象(当返回 POJO 对象时默认转换为 JSON 格式数据进行响应)。
在系统中,经常需要在处理用户请求之前和之后执行一些行为,例如检测用户的权限,或者将请求的信息记录到日志中,即平时所说的“权限检测”及“日志记录”。当然不仅仅这些,所以需要一种机制,拦截用户的请求,在请求的前后添加处理逻辑。
Spring MVC 提供了 Interceptor 拦截器机制,用于请求的预处理和后处理。
在开发一个网站时可能有这样的需求:某些页面只希望几个特定的用户浏览。对于这样的访问权限控制,应该如何实现呢?拦截器就可以实现上述需求。在 Struts2 框架中,拦截器是其重要的组成部分,Spring MVC 框架也提供了拦截器功能。
Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。
在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。
本节以实现 HandlerInterceptor 接口的定义方式为例讲解自定义拦截器的使用方法。示例代码如下。
package net.biancheng.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class TestInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行");
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法在控制器的处理请求方法调用之前执行");
return false;
}
}
上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法,说明如下。
让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置,配置示例代码如下:
<mvc:interceptors>
<bean class="net.biancheng.interceptor.TestInterceptor" />
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="" />
<bean class="net.biancheng.interceptor.Interceptor1" />
mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/gotoTest" />
<bean class="net.biancheng.interceptor.Interceptor2" />
mvc:interceptor>
mvc:interceptors>
在上述示例代码中,元素说明如下。
/**
时,表示拦截所有路径,值为/gotoTest
时,表示拦截所有以/gotoTest
结尾的路径。如果在请求路径中包含不需要拦截的内容,可以通过 mvc:exclude-mapping 子元素进行配置。需要注意的是,mvc:interceptor 元素的子元素必须按照 mvc:mapping.../、mvc:exclude-mapping.../、
下面通过拦截器来完成一个用户登录权限验证的 Web 应用,具体要求如下:只有成功登录的用户才能访问系统的主页面 main.jsp,如果没有成功登录而直接访问主页面,则拦截器将请求拦截,并转发到登录页面 login.jsp。当成功登录的用户在系统主页面中单击“退出”链接时回到登录页面。
本节示例基于《@Controller和@RequestMapping注解》一节的 springmvcDemo2 应用实现,具体实现步骤如下。
在 springmvcDemo2 的 net.biancheng.pojo 包中创建 User 类,代码如下。
package net.biancheng.po;public class User { private String name; private String pwd; /**省略setter和getter方法*/}
在 springmvcDemo2 的 net.biancheng.controller 包中创建控制器类 UserController,代码如下。
package net.biancheng.controller;import javax.servlet.http.HttpSession;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import net.biancheng.po.User;@Controllerpublic class UserController { /** * 登录页面初始化 */ @RequestMapping("/toLogin") public String initLogin() { return "login"; } /** * 处理登录功能 */ @RequestMapping("/login") public String login(User user, Model model, HttpSession session) { System.out.println(user.getName()); if ("bianchengbang".equals(user.getName()) && "123456".equals(user.getPwd())) { // 登录成功,将用户信息保存到session对象中 session.setAttribute("user", user); // 重定向到主页面的跳转方法 return "redirect:main"; } model.addAttribute("msg", "用户名或密码错误,请重新登录! "); return "login"; } /** * 跳转到主页面 */ @RequestMapping("/main") public String toMain() { return "main"; } /** * 退出登录 */ @RequestMapping("/logout") public String logout(HttpSession session) { // 清除 session session.invalidate(); return "login"; }}
在 springmvcDemo2 的 net.biancheng.interceptor 包中创建拦截器类 LoginInterceptor,代码如下。
package net.biancheng.interceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取请求的URL String url = request.getRequestURI(); // login.jsp或登录请求放行,不拦截 if (url.indexOf("/toLogin") >= 0 || url.indexOf("/login") >= 0) { return true; } // 获取 session HttpSession session = request.getSession(); Object obj = session.getAttribute("user"); if (obj != null) return true; // 没有登录且不是登录页面,转发到登录页面,并给出提示错误信息 request.setAttribute("msg", "还没登录,请先登录!"); request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); return false; } @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { // TODO Auto-generated method stub } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { // TODO Auto-generated method stub }}
在 WEB-INF 目录下创建配置文件 springmvc-servlet.xml 和 web.xml。web.xml 的代码和 springmvcDemo2 一样,这里不再赘述。在 springmvc-servlet.xml 文件中配置拦截器 LoginInterceptor,代码如下。
在 WEB-INF 目录下创建文件夹 jsp,并在该文件夹中创建 login.jsp 和 main.jsp。
login.jsp 的代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>用户登录 ${msg }
main.jsp 的代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>首页 欢迎 ${user.name },登录编程帮!
退出
首先将 springmvcDemo2 应用发布到 Tomcat 服务器并启动 Tomcat 服务器,然后通过地址“http://localhost:8080/springmvcDemo2/main”测试应用,运行效果如图 1 所示。
从图 1 可以看出,当用户没有登录而直接访问系统主页面时请求将被登录拦截器拦截,返回到登录页面,并提示信息。如果用户在用户名框中输入“bianchengbang”,在密码框中输入“123456”,单击“登录”按钮后浏览器的显示结果如图 2 所示。如果输入的用户名或密码错误,浏览器的显示结果如图 3 所示。
当单击图 2 中的“退出”链接后,系统将从主页面返回到登录页面。
一般情况下,用户的输入是随意的,为了保证数据的合法性,数据验证是所有 Web 应用必须处理的问题。
Spring MVC 有以下两种方法可以验证输入:
数据验证分为客户端验证和服务器端验证,客户端验证主要是过滤正常用户的误操作,通过 JavaScript 代码完成。服务器端验证是整个应用阻止非法数据的最后防线,通过在应用中编程实现。
本节使用 JSR 303 实现服务器端的数据验证。
JSR 303 是 Java 为 Bean 数据合法性校验所提供的标准框架。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。可以通过 https://jcp.org/en/jsr/detail?id=303 查看详细内容并下载 JSR 303 Bean Validation。
JSR 303 不需要编写验证器,它定义了一套可标注在成员变量、属性方法上的校验注解,如下表所示。
名称 | 说明 |
---|---|
@Null | 被标注的元素必须为 null |
@NotNull | 被标注的元素必须不为 null |
@AssertTrue | 被标注的元素必须为 true |
@AssertFalse | 被标注的元素必须为 false |
@Min(value) | 被标注的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被标注的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMax(value) | 被标注的元素必须是一个数字,其值必须大于等于指定的最大值 |
@DecimalMin(value) | 被标注的元素必须是一个数字,其值必须小于等于指定的最小值 |
@size | 被标注的元素的大小必须在指定的范围内 |
@Digits(integer,fraction) | 被标注的元素必须是一个数字,其值必须在可接受的范围内;integer 指定整数精度,fraction 指定小数精度 |
@Past | 被标注的元素必须是一个过去的日期 |
@Future | 被标注的元素必须是一个将来的日期 |
@Pattern(value) | 被标注的元素必须符合指定的正则表达式 |
Spring MVC 支持 JSR 303 标准的校验框架,Spring 的 DataBinder 在进行数据绑定时,可同时调用校验框架来完成数据校验工作,非常简单方便。在 Spring MVC 中,可以直接通过注解驱动的方式来进行数据校验。
Spring 本身没有提供 JSR 303 的实现,Hibernate Validator 实现了 JSR 303,所以必须在项目中加入来自 Hibernate Validator 库的 jar 文件,下载地址为 http://hibernate.org/validator/。本节使用版本为 hibernate-validator-5.1.0.Final-dist.zip,复制其中的 3 个 jar 文件即可,Spring 将会自动加载并装配。
本节示例基于《@Controller和@RequestMapping注解》一节中的 springmvcDemo2 实现。
pom.xml 文件中添加以下代码。
javax.validation validation-api 1.1.0.Final org.jboss.logging jboss-logging 3.1.0.CR2 org.hibernate hibernate-validator 5.1.0.Final
创建 User 实体类,代码如下。
package net.biancheng.po;import javax.validation.constraints.NotNull;import org.hibernate.validator.constraints.Email;import org.hibernate.validator.constraints.Length;public class User { @NotNull(message = "用户id不能为空") private Integer id; @NotNull @Length(min = 2, max = 8, message = "用户名不能少于2位大于8位") private String name; @Email(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]", message = "邮箱格式不正确") private String email; /** 省略setter和getter方法*/}
创建 addUser.jsp,代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>添加用户
创建 UserController 控制器类,代码如下。
package net.biancheng.controller;import java.util.List;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import javax.validation.Valid;import org.springframework.stereotype.Controller;import org.springframework.validation.BindingResult;import org.springframework.validation.ObjectError;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import net.biancheng.po.User;@Controllerpublic class UserController { @RequestMapping("/validate") public String validate(@Valid User user, BindingResult result) { // 如果有异常信息 if (result.hasErrors()) { // 获取异常信息对象 List errors = result.getAllErrors(); // 将异常信息输出 for (ObjectError error : errors) { System.out.println(error.getDefaultMessage()); } } return "index"; } @RequestMapping(value = "/addUser") public String add() { return "addUser"; }}
访问地址:http://localhost:8080/springmvcDemo2/addUser,运行结果如下。
Eclipse 控制台输出结果如下。
邮箱格式不正确
用户id不能为空
用户名不能少于2位大于8位
在 Spring MVC 应用的开发中,不管是操作底层数据库,还是业务层或控制层,都会不可避免地遇到各种可预知的、不可预知的异常。我们需要捕捉处理异常,才能保证程序不被终止。
Spring MVC 有以下 3 种处理异常的方式:
局部异常处理仅能处理指定 Controller 中的异常。
**示例 1:**下面使用 @ExceptionHandler 注解实现。定义一个处理过程中可能会存在异常情况的 testExceptionHandle 方法。
@RequestMapping("/testExceptionHandle")public String testExceptionHandle(@RequestParam("i") Integer i) { System.out.println(10 / i); return "success";}
显然,当 i=0 时会产生算术运算异常。
下面在同一个类中定义处理异常的方法。
@ExceptionHandler({ ArithmeticException.class })public String testArithmeticException(Exception e) { System.out.println("打印错误信息 ===> ArithmeticException:" + e); // 跳转到指定页面 return "error";}
注意:该注解不是加在产生异常的方法上,而是加在处理异常的方法上。
异常页面 error.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>错误页面 发生算术运算异常,请重新输出数据!
访问地址:http://localhost:8080/springmvcDemo2/testExceptionHandle?i=0,页面跳转到 error.jsp 页面,运行结果如图 1 所示。
控制器输出结果如下。
打印错误信息 ===> ArithmeticException:java.lang.ArithmeticException: / by zero
@ExceptionHandler 注解定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,这时候会根据异常的最近继承关系找到继承深度最浅的那个@ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。
被 @ExceptionHandler 标记为异常处理方法,不能在方法中设置别的形参。但是可以使用 ModelAndView 向前台传递数据。
使用局部异常处理,仅能处理某个 Controller 中的异常,若需要对所有异常进行统一处理,可使用以下两种方法。
Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。
public interface HandlerExceptionResolver { @Nullable ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);}
发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。
**示例 2:**在 net.biancheng.exception 包中创建一个 HandlerExceptionResolver 接口的实现类 MyExceptionHandler,代码如下。
package net.biancheng.exception;import java.util.HashMap;import java.util.Map;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.ModelAndView;public class MyExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) { Map model = new HashMap(); // 根据不同错误转向不同页面(统一处理),即异常与View的对应关系 if (arg3 instanceof ArithmeticException) { return new ModelAndView("error", model); } return new ModelAndView("error-2", model); }}
在 springmvc-servlet.xml 文件中添加以下代码。
再次访问 http://localhost:8080/springmvcDemo2/testExceptionHandle?i=0,页面跳转到 error.jsp 页面,运行结果如上图 1 所示。
全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
**示例 3:**在 springmvc-servlet.xml 中配置全局异常,代码如下。
error
再次访问 http://localhost:8080/springmvcDemo2/testExceptionHandle?i=0,页面跳转到 error.jsp 页面,运行结果如上图 1 所示。
Spring MVC 框架的文件上传基于 commons-fileupload 组件,并在该组件上做了进一步的封装,简化了文件上传的代码实现,取消了不同上传组件上的编程差异。
在 Spring MVC 中实现文件上传十分容易,它为文件上传提供了直接支持,即 MultpartiResolver 接口。MultipartResolver 用于处理上传请求,将上传请求包装成可以直接获取文件的数据,从而方便操作。
MultpartiResolver 接口有以下两个实现类:
MultpartiResolver 接口具有以下方法。
名称 | 作用 |
---|---|
byte[] getBytes() | 以字节数组的形式返回文件的内容 |
String getContentType() | 返回文件的内容类型 |
InputStream getInputStream() | 返回一个InputStream,从中读取文件的内容 |
String getName() | 返回请求参数的名称 |
String getOriginalFillename() | 返回客户端提交的原始文件名称 |
long getSize() | 返回文件的大小,单位为字节 |
boolean isEmpty() | 判断被上传文件是否为空 |
void transferTo(File destination) | 将上传文件保存到目标目录下 |
下面我们使用 CommonsMultipartResolver 来完成文件上传,分为单文件上传和多文件上传两部分介绍。
文件上传使用 Apache Commons FileUpload 组件,需要导入 commons-io-2.4.jar 和 commons-fileupload-1.2.2.jar 两个 jar 文件(可在 Apache 官网下载)。
Maven 项目在 pom.xml 文件中添加以下依赖。
commons-io commons-io 2.4 commons-fileupload commons-fileupload 1.2.2
使用 CommonsMultipartReslover 配置 MultipartResolver 解析器,在 springmvc-servlet.xml 中添加代码如下。
负责文件上传表单的编码类型必须是“multipart/form-data”类型。
fleUpload.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>文件上传
基于表单的文件上传需要使用 enctype 属性,并将它的值设置为 multipart/form-data,同时将表单的提交方式设置为 post。
表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值。
由上面 3 个属性的解释可知,在基于表单上传文件时 enctype 的属性值应为 multipart/form-data。
创建 net.biancheng.opjo 包,在该包下创建 FileDomain 类,在该 POJO 类中声明一个 MultipartFile 类型的属性封装被上传的文件信息,属性名与文件选择页面 filleUpload.jsp 中的 file 类型的表单参数名 myfile 相同,代码如下。
package net.biancheng.po;import org.springframework.web.multipart.MultipartFile;public class FileDomain { private String description; private MultipartFile myfile; /** 省略setter和getter参数*/}
创建 net.biancheng.controller 包,在该包下创建 FileUploadController 控制类,具体代码如下。
package net.biancheng.controller;import java.io.File;import javax.servlet.http.HttpServletRequest;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.ModelAttribute;import org.springframework.web.bind.annotation.RequestMapping;import net.biancheng.po.FileDomain;@Controllerpublic class FileUploadController { // 得到一个用来记录日志的对象,这样在打印信息时能够标记打印的是哪个类的信息 private static final Log logger = LogFactory.getLog(FileUploadController.class); @RequestMapping("getFileUpload") public String getFileUpload() { return "fileUpload"; } /** * 单文件上传 */ @RequestMapping("/fileupload") public String oneFileUpload(@ModelAttribute FileDomain fileDomain, HttpServletRequest request) { /* * 文件上传到服务器的位置“/uploadfiles”,该位置是指 workspace\.metadata\.plugins\org.eclipse * .wst.server.core\tmp0\wtpwebapps, 发布后使用 */ String realpath = request.getServletContext().getRealPath("uploadfiles"); String fileName = fileDomain.getMyfile().getOriginalFilename(); File targetFile = new File(realpath, fileName); if (!targetFile.exists()) { targetFile.mkdirs(); } // 上传 try { fileDomain.getMyfile().transferTo(targetFile); logger.info("成功"); } catch (Exception e) { e.printStackTrace(); } return "showFile"; }}
showFile.jsp 代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>文件上传 文件描述:${fileDomain.description }
文件名称:${fileDomain.myfile.originalFilename }
访问地址:http://localhost:8080/springmvcDemo2/getFileUpload,运行结果如下。
在以上代码的基础上,实现 Spring MVC 多文件上传。
创建 multiFiles.jsp 页面,在该页面中使用表单上传多个文件。代码如下。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib prefix="fm" uri="http://www.springframework.org/tags/form"%>多文件上传
创建 MultiFileDomain 类,上传多文件时用于封装文件信息,代码如下。
package net.biancheng.po;import java.util.List;import org.springframework.web.multipart.MultipartFile;public class MultiFileDomain { private List description; private List myfile; public List getDescription() { return description; } public void setDescription(List description) { this.description = description; } public List getMyfile() { return myfile; } public void setMyfile(List myfile) { this.myfile = myfile; }}
在 FileUploadController 控制器类中添加多文件上传处理方法 multifile,具体代码如下。
@RequestMapping("/getmultiFile")public String getmultiFile() { return "multiFiles";}/*** 多文件上传*/@RequestMapping("/multifile")public String multiFileUpload(@ModelAttribute MultiFileDomain multiFileDomain, HttpServletRequest request) { String realpath = request.getServletContext().getRealPath("uploadfiles"); File targetDir = new File(realpath); if (!targetDir.exists()) { targetDir.mkdirs(); } List files = multiFileDomain.getMyfile(); System.out.println("files"+files); for (int i = 0; i < files.size(); i++) { MultipartFile file = files.get(i); String fileName = file.getOriginalFilename(); File targetFile = new File(realpath, fileName); // 上传 try { file.transferTo(targetFile); } catch (Exception e) { e.printStackTrace(); } } return "showMulti";}
创建多文件上传成功显示页面 showMulti.jsp,具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>多文件上传显示 详情 文件名 ${description} ${multiFileDomain.myfile[loop.count-1].originalFilename}
访问地址:http://localhost:8080/springmvcDemo2/getmultiFile,运行结果如下。
顾名思义,文件下载就是将服务器中的文件下载到本地,下面主要介绍 Spring MVC 文件下载的实现方法和实现过程。
文件下载有以下两种实现方法:
利用程序编码实现下载需要设置以下两个报头:
该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。
设置报头的示例如下:
response.setHeader(“Content-Type”, “application/x-msdownload”);
response.setHeader(“Content-Disposition”, “attachment;filename=”+filename);
程序编码文件下载可分为两个步骤:
下面继续通过 springMVCDemo2 应用讲述利用程序实现下载的过程,要求从《Spring MVC文件上传》上传文件的目录(workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\springMVCDemo2\uploadfiles)中下载文件,具体开发步骤如下。
首先编写控制器类 FileDownController,在该类中有 3 个方法,即 show、down 和 toUTF8String。其中,show 方法获取被下载的文件名称;down 方法执行下载功能;toUTF8String 方法是下载保存时中文文件名的字符编码转换方法。
FileDownController 类的代码如下:
package controller;import java.io.File;import java.io.FileInputStream;import java.io.UnsupportedEncodingException;import java.util.ArrayList;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;@Controllerpublic class FileDownController { // 得到一个用来记录日志的对象,在打印时标记打印的是哪个类的信息 private static final Log logger = LogFactory .getLog(FileDownController.class); /** * 显示要下载的文件 */ @RequestMapping("showDownFiles") public String show(HttpServletRequest request, Model model) { // 从 workspace\.metadata\.plugins\org.eclipse.wst.server.core\ // tmp0\wtpwebapps\springMVCDemo11\下载 String realpath = request.getServletContext() .getRealPath("uploadfiles"); File dir = new File(realpath); File files[] = dir.listFiles(); // 获取该目录下的所有文件名 ArrayList fileName = new ArrayList(); for (int i = 0; i < files.length; i++) { fileName.add(files[i].getName()); } model.addAttribute("files", fileName); return "showDownFiles"; } /** * 执行下载 */ @RequestMapping("down") public String down(@RequestParam String filename, HttpServletRequest request, HttpServletResponse response) { String aFilePath = null; // 要下载的文件路径 FileInputStream in = null; // 输入流 ServletOutputStream out = null; // 输出流 try { // 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\ // tmp0\wtpwebapps下载 aFilePath = request.getServletContext().getRealPath("uploadfiles"); // 设置下载文件使用的报头 response.setHeader("Content-Type", "application/x-msdownload"); response.setHeader("Content-Disposition", "attachment; filename=" + toUTF8String(filename)); // 读入文件 in = new FileInputStream(aFilePath + "\\" + filename); // 得到响应对象的输出流,用于向客户端输出二进制数据 out = response.getOutputStream(); out.flush(); int aRead = 0; byte b[] = new byte[1024]; while ((aRead = in.read(b)) != -1 & in != null) { out.write(b, 0, aRead); } out.flush(); in.close(); out.close(); } catch (Throwable e) { e.printStackTrace(); } logger.info("下载成功"); return null; } /** * 下载保存时中文文件名的字符编码转换方法 */ public String toUTF8String(String str) { StringBuffer sb = new StringBuffer(); int len = str.length(); for (int i = 0; i < len; i++) { // 取出字符中的每个字符 char c = str.charAt(i); // Unicode码值为0~255时,不做处理 if (c >= 0 && c <= 255) { sb.append(c); } else { // 转换 UTF-8 编码 byte b[]; try { b = Character.toString(c).getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); b = null; } // 转换为%HH的字符串形式 for (int j = 0; j < b.length; j++) { int k = b[j]; if (k < 0) { k &= 255; } sb.append("%" + Integer.toHexString(k).toUpperCase()); } } } return sb.toString(); }}
下载文件示例需要一个显示被下载文件的 JSP 页面 showDownFiles.jsp,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>Insert title here 被下载的文件名 ${filename}