1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Spring系列(九)- Spring Web MVC 框架

Spring系列(九)- Spring Web MVC 框架

时间:2020-05-02 03:38:40

相关推荐

Spring系列(九)- Spring Web MVC 框架

文章目录

MVC设计模式简介Spring MVC 工作流程Spring MVC接口需求的配置Spring MVC视图解析器Controller 注解类型Spring MVC的转发与重定向@Autowired和@Service依赖注入类型转换类型转换的意义Spring MVC Converter(类型转换器)详解内置的类型转换器自定义类型转换器 拦截器(Interceptor)的配置及使用拦截器的定义拦截器的配置

MVC设计模式简介

MVC 设计不仅限于 Java Web 应用,还包括许多应用,比如前端、PHP、.NET 等语言。之所以那么做的根本原因在于解耦各个模块。

MVC 是 Model、View 和 Controller 的缩写,分别代表 Web 应用程序中的 3 种职责。

模型:用于存储数据以及处理用户请求的业务逻辑。视图:向控制器提交数据,显示模型中的数据。控制器:根据视图提出的请求判断将请求和数据交给哪个模型处理,将处理后的有关结果交给哪个视图更新显示。

基于Servlet的 MVC 模式的具体实现如下。

模型:一个或多个 JavaBean 对象,用于存储数据(实体模型,由 JavaBean 类创建)和处理业务逻辑(业务模型,由一般的 Java 类创建)。视图:一个或多个 JSP 页面,向控制器提交数据和为模型提供数据显示,JSP 页面主要使用 HTML 标记和 JavaBean 标记来显示数据。控制器:一个或多个 Servlet 对象,根据视图提交的请求进行控制,即将请求转发给处理业务逻辑的 JavaBean,并将处理结果存放到实体模型 JavaBean 中,输出给视图显示。

基于 Servlet 的 MVC 模式的流程如图所示:JSP 中的 MVC 模式

Spring MVC 工作流程

Spring MVC 框架主要由DispatcherServlet处理器映射控制器视图解析器视图组成,其工作原理如图1所示。

从图1可总结出 Spring MVC 的工作流程如下:

客户端请求提交到 DispatcherServlet。由 DispatcherServlet 控制器寻找一个或多个 HandlerMapping,找到处理请求的 Controller。DispatcherServlet 将请求提交到 Controller。Controller 调用业务逻辑处理后返回 ModelAndView。DispatcherServlet 寻找一个或多个 ViewResolver 视图解析器,找到 ModelAndView 指定的视图。视图负责将结果显示到客户端。

Spring MVC接口

在图 1 中包含 4 个 Spring MVC 接口,即 DispatcherServlet、HandlerMapping、Controller 和 ViewResolver。

Spring MVC 所有的请求都经过 DispatcherServlet 来统一分发,在 DispatcherServlet 将请求分发给 Controller 之前需要借助 Spring MVC 提供的 HandlerMapping 定位到具体的 Controller。

HandlerMapping 接口负责完成客户请求到 Controller 映射。

Controller 接口将处理用户请求,这和 Java Servlet 扮演的角色是一致的。一旦 Controller 处理完用户请求,将返回 ModelAndView 对象给 DispatcherServlet 前端控制器,ModelAndView 中包含了模型(Model)和视图(View)。

从宏观角度考虑,DispatcherServlet 是整个 Web 应用的控制器;从微观考虑,Controller 是单个 Http 请求处理过程中的控制器,而 ModelAndView 是 Http 请求过程中返回的模型(Model)和视图(View)。

ViewResolver 接口(视图解析器)在 Web 应用中负责查找 View 对象,从而将相应结果渲染给客户。

需求的配置

在开发 Spring MVC 应用时需要在 web.xml 中部署 DispatcherServlet,代码如下:

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="/2001/XMLSchema-instance"xmlns="/xml/ns/javaee" xmlns:web="/xml/ns/javaee/web-app_2_5.xsd"xsi:schemaLocation="/xml/ns/javaee /xml/ns/javaee/web-app_3_0.xsd"version="3.0"><display-name>springMVC</display-name><!-- 部署 DispatcherServlet --><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 表示容器再启动时立即加载servlet --><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><!-- 处理所有URL --><url-pattern>/</url-pattern></servlet-mapping></web-app>

上述 DispatcherServlet 的 servlet 对象 springmvc 初始化时将在应用程序的 WEB-INF 目录下查找一个配置文件,该配置文件的命名规则是“servletName-servlet.xml”,例如 springmvc-servlet.xml。

另外,也可以将 Spring MVC 配置文件存放在应用程序目录中的任何地方,但需要使用 servlet 的 init-param 元素加载配置文件。示例代码如下:

我的项目结构:

<!-- 部署 DispatcherServlet --><servlet><servlet-name>DispatcherServlet</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><load-on-startup>1</load-on-startup><async-supported>true</async-supported></servlet><servlet-mapping><servlet-name>DispatcherServlet</servlet-name><url-pattern>/</url-pattern> </servlet-mapping>

Spring MVC视图解析器

Spring 视图解析器是 Spring MVC 中的重要组成部分,用户可以在配置文件spring-mvc.xml中定义 Spring MVC 的一个视图解析器(ViewResolver),示例代码如下:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" ><!--前缀--><property name="prefix" value="/WEB-INF/jsp/"/><!--后缀--><property name="suffix" value=".jsp"/></bean>

上述视图解析器配置了前缀和后缀两个属性,控制器类的视图路径仅需提供类似下图中login,视图解析器将会自动添加前缀和后缀。

在WEB-INF中创建如下JSP

hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>Hello World</title></head><body><h2>${message}</h2></body></html>

从上,比如在 InternalResourceViewResolver 中定义了 prefix=/WEB-INF/jsp,suffix=.jsp,然后请求的 Controller 处理器方法返回的视图名称为 hello,那么这个时候 InternalResourceViewResolver 就会把 hello解析为一个 InternalResourceView 对象,先把返回的模型属性都存放到对应的 HttpServletRequest 属性中,然后利用 RequestDispatcher 在服务器端把请求 forword 到 /WEB-INF/jsp/hello.jsp。结果如图

注意:配置了<mvc:annotation-driven />后视图解析器失效,具体原因不知,如下图

此时spring-mvc配置

<!-- 扫描controller --><context:component-scan base-package="com.demo.controller" /><!-- 当配置了mvc:annotation-driven/后,Spring就知道了我们启用注解驱动。然后Spring通过context:component-scan/标签的配置,会自动为我们将扫描到的@Component,@Controller,@Service,@Repository等注解标记的组件注册到工厂中,来处理我们的请求 --><mvc:annotation-driven /><!--通过location,可以重新定义资源文件的位置--><mvc:resources mapping="/resources/**" location="/resources/"/><!--静态页面,如html,css,js,images可以访问--><mvc:default-servlet-handler /><!-- 视图定位到/WEB/INF/jsp 这个目录下 --><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/jsp/" /><property name="suffix" value=".jsp" /></bean>

InternalResourceViewResolver 一个非常重要的特性:

我们都知道存放在 /WEB-INF/ 下面的内容是不能直接通过 request 请求的方式请求到的,为了安全性考虑,我们通常会把 jsp 文件放在 WEB-INF 目录下,而 InternalResourceView 在服务器端跳转的方式可以很好的解决这个问题。

Controller 注解类型

在 Spring MVC 中使用 org.springframework.stereotype.Controller 注解类型声明某类的实例是一个控制器。

package controller;import org.springframework.stereotype.Controller;/*** “@Controller”表示 IndexController 的实例是一个控制器** @Controller相当于@Controller(@Controller) 或@Controller(value="@Controller")*/@Controllerpublic class IndexController {// 处理请求的方法}

在 Spring MVC 中使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用<context:component-scan/>元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。

<!-- 使用扫描机制扫描控制器类,控制器类都在controller包及其子包下 --><context:component-scan base-package="controller" />

Spring MVC的转发与重定向

重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域。

转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。

转发是服务器行为,重定向是客户端行为。

@Autowired和@Service依赖注入

Spring MVC 框架本身就是一个非常优秀的 MVC 框架,它具有依赖注入的优点,可以通过 org.springframework.beans.factory. annotation.Autowired 注解类型将依赖注入到一个属性(成员变量)或方法,例如:

@Autowiredpublic UserService userService;

在 Spring MVC 中,为了能被作为依赖注入,类必须使用 org.springframework.stereotype.Service 注解类型注明为 @Service(一个服务)。另外,还需要在配置文件中使用 <context:component-scan base-package=“基本包”/> 元素来扫描依赖基本包。

例:登录逻辑

UserService接口

package service;import pojo.UserForm;public interface UserService {boolean login(UserForm user);}

UserServiceImpl 实现类

@Servicepublic class UserServiceImpl implements UserService {public boolean login(UserForm user) {if ("zhangsan".equals(user.getUname())&& "123456".equals(user.getUpass())) {return true;}return false;}}

然后在配置文件中添加一个 <context:component-scan base-package=“基本包”/>元素,具体代码如下:

<context:component-scan base-package="service" />

最后控制器类 UserController

@Controller@RequestMapping("/user")public class UserController {// 得到一个用来记录日志的对象,这样在打印信息的时候能够标记打印的是哪个类的信息private static final Log logger = LogFactory.getLog(UserController.class);// 将服务依赖注入到属性userService@Autowiredpublic UserService userService;/*** 处理登录*/@RequestMapping("/login")public String login(UserForm user, HttpSession session, Model model) {if (userService.login(user)) {session.setAttribute("u", user);logger.info("成功");return "main"; // 登录成功,跳转到 main.jsp} else {logger.info("失败");model.addAttribute("messageError", "用户名或密码错误");return "login";}}}

类型转换

类型转换的意义

以一个简单应用(JSP+Servlet)为示例来介绍类型转换的意义。如图 1 所示的添加商品页面用于收集用户输入的商品信息,商品信息包括商品名称(字符串类型 String)、商品价格(双精度浮点类型 double)、商品数量(整数类型 int)。

希望页面收集到的数据提交到 addGoods 的 Servlet(AddGoodsServlet 类),该 Servlet 将这些请求信息封装成一个 Goods 类的值对象。

Goods 类的代码:

public class Goods {private String goodsname;private double goodsprice;private int goodsnumber;// 无参数的构造方法public Goods() {}// 有参数的构造方法public Goods(String goodsname, double goodsprice, int goodsnumber) {super();this.goodsname = goodsname;this.goodsprice = goodsprice;this.goodsnumber = goodsnumber;}// 此处省略了setter和getter方法}

AddGoodsServlet 类的代码:

public class AddGoodsServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doPost(request, response);}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");// 设置编码,防止乱码request.setCharacterEncoding("utf-8");// 获取参数值String goodsname = request.getParameter("goodsname");String goodsprice = request.getParameter("goodsprice");String goodsnumber = request.getParameter("goodsnumber");// 下面进行类型转换double newgoodsprice = Double.parseDouble(goodsprice);int newgoodsnumber = Integer.parseInt(goodsnumber);// 将转换后的数据封装成goods值对象Goods goods = new Goods(goodsname, newgoodsprice, newgoodsnumber);// 将goods值对象传递给数据访问层,进行添加操作,代码省略...}}

对于上面这个应用而言,开发者需要自己在 Servlet 中进行类型转换,并将其封装成值对象。这些类型转换操作全部手工完成,异常烦琐。

对于 Spring MVC 框架而言,它必须将请求参数转换成值对象类中各属性对应的数据类型——这就是类型转换的意义。

Spring MVC Converter(类型转换器)详解

Spring MVC 框架的 Converter<S,T> 是一个可以将一种数据类型转换成另一种数据类型的接口,这里 S 表示源类型,T 表示目标类型。开发者在实际应用中使用框架内置的类型转换器基本上就够了,但有时需要编写具有特定功能的类型转换器。

内置的类型转换器

在 Spring MVC 框架中,对于常用的数据类型,开发者无须创建自己的类型转换器,因为 Spring MVC 框架有许多内置的类型转换器用于完成常用的类型转换。Spring MVC 框架提供的内置类型转换包括以下几种类型

1)标量转换器

2)集合、数组相关转换器

类型转换是在视图与控制器相互传递数据时发生的。Spring MVC 框架对于基本类型(例如 int、long、float、double、boolean 以及 char 等)已经做好了基本类型转换。例如,对于 商品 的提交请求,可以由以下处理方法来接收请求参数并处理:

@Controllerpublic class Goodsontroller {@RequestMapping("/addGoods")public String add(String goodsname, double goodsprice, int goodsnumber) {double total = goodsprice * goodsnumber;System.out.println(total);return "success";}}

注意:在使用内置类型转换器时,请求参数输入值与接收参数类型要兼容,否则会报 400 错误。请求参数类型与接收参数类型不兼容问题需要学习输入校验后才可解决。

自定义类型转换器

当 Spring MVC 框架内置的类型转换器不能满足需求时,开发者可以开发自己的类型转换器。不详讲了,另外自行了解

拦截器(Interceptor)的配置及使用

Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

拦截器的定义

在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,定义一个拦截器可以通过两种方式:一种是通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类来定义;另一种是通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义。

本节以实现 HandlerInterceptor 接口的定义方式为例讲解自定义拦截器的使用方法。示例代码如下:

package 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 {@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex)throws Exception {System.out.println("afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行");}@Overridepublic void postHandle(HttpServletRequest request,HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {System.out.println("postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");}@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");return false;}}

在上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法。有关这 3 个方法的描述如下。

preHandle方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。postHandle方法:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。afterCompletion方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

拦截器的配置

让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置,配置示例代码如下:

<!-- 配置拦截器 --><mvc:interceptors><!-- 配置一个全局拦截器,拦截所有请求 --><bean class="interceptor.TestInterceptor" /> <mvc:interceptor><!-- 配置拦截器作用的路径 --><mvc:mapping path="/**" /><!-- 配置不需要拦截作用的路径 --><mvc:exclude-mapping path="" /><!-- 定义<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 --><bean class="interceptor.Interceptor1" /></mvc:interceptor><mvc:interceptor><!-- 配置拦截器作用的路径 --><mvc:mapping path="/gotoTest" /><!-- 定义在<mvc: interceptor>元素中,表示匹配指定路径的请求才进行拦截 --><bean class="interceptor.Interceptor2" /></mvc:interceptor></mvc:interceptors>

在上述示例代码中,<mvc:interceptors> 元素用于配置一组拦截器,其子元素 定义的是全局拦截器,即拦截所有的请求。

<mvc:interceptor> 元素中定义的是指定路径的拦截器,其子元素 <mvc:mapping> 用于配置拦截器作用的路径,该路径在其属性 path 中定义。

如上述示例代码中,path 的属性值“/**”表示拦截所有路径,“/gotoTest”表示拦截所有以“/gotoTest”结尾的路径。如果在请求路径中包含不需要拦截的内容,可以通过 <mvc:exclude-mapping> 子元素进行配置。

需要注意的是,<mvc:interceptor> 元素的子元素必须按照 <mvc:mapping…/>、<mvc:exclude-mapping…/>、<bean…/> 的顺序配置。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。