SSM Chapter 09 SpringMVC体系结构和处理请求控制器

SSM Chapter 09 SpringMVC体系结构和处理请求控制器 笔记

本章重点:

  • 理解MVC设计模式
  • 了解Spring MVC的架构以及请求流程
  • 掌握Spring MVC开发环境搭建
  • 掌握Controller和View之间的映射
  • 掌握参数传递(View-Controller)

本章简介 :

随着Web应用复杂度的不断提升,单纯使用JSP技术完成Web应用程序开发的弊端越来越明显,在应用程序中引入控制器(Servlet 或者 Filter),可以有效的避免在JSP页面编写大量的业务和页面跳转代码,而JSP则专门用于展示内容,这种程序设计模式就是MVC设计模式。

了解了MVC设计模式之后,我们就会开始学习Controller层的框架产品Spring MVC。本章将学习Spring MVC 环境的搭建,掌握Contorller 和 View 之间的映射和参数传递,并理解Spring MVC 的架构及其请求处理流程。

1 . MVC 设计模式

Web项目的架构模式基本都一致,都进行了分层设计:

  • 数据访问接口 : DAO 层
  • 处理业务逻辑 : Service 层
  • 数据实体 : POJO
  • 负责前端请求的接受并处理 : Servlet
  • 负责前端页面的展示 : JSP

这种架构模式就是MVC设计模式,它是软件工程中的一种软件架构模式. 它强制性的使软件系统的输入,处理 和 输出分开,把软件系统分为三个部分:模型(Model), 视图(View),控制器(Controller),如图所示:
SSM Chapter 09  SpringMVC体系结构和处理请求控制器
根据图1所示 综合 超市订单管理系统,分析出该系统的分层设计与MVC设计模式的对应关系:

  • (1) 视图 (View) : 负责格式化数据并把它们呈现给用户,包括数据展示,用户交互,数据验证,界面设计等功能.对应组件:JSP 或者 HTML 文件(如超市系统一一JSP页面);

  • (2) 控制器(Controller) : 负责接收并转发请求,对请求处理后指派视图并将响应结果发送给客户端.对应组件:Servlet(如超市系统一一Servlet);

  • (3) 模型(Model) : 模型对象拥有最多处理任务,是应用程序的主体部分,它负责数据逻辑(业务规则)的处理 和 实现数据操作(即在数据库中存取数据).
    对应组件:JavaBean(如超市系统中处理业务逻辑的Service层,与数据库操作相关的DAO层,贯穿与各层之间的数据模型,即数据实体POJO);

通过以上分析,我们发现超市订单管理系统所采用的设计模式 ------- JSP+Servlet+JavaBean,其实就是最经典的MVC.

下面详细介绍MVC的两种模式:

1.1 JSP Model 1

当业务流程较为简单的时候,可以把控制器的功能交给视图来实现,这种模式称为 JSP Model1,故Model1 模式只有视图和模型,没有控制器(即 JSP + JavaBean),如图:
SSM Chapter 09  SpringMVC体系结构和处理请求控制器

通过上图,我们可以发现Model1 的基础是JSP,它由JSP和JavaBean组成,JSP从HTTP Request 中获得所需的数据,并进行业务逻辑的处理,然后将结果通过HTTP Response 返回给前端浏览器.

从中可见, Model1在一定程度上实现了MVC,即JSP将控制层和视图层合二为一,JavaBean为模型层。其中JSP身兼多职,既要负责视图层的数据展示,又要负责业务流程的控制,架构较为混乱,并且也不是我们所希望的松耦合架构模式,所以当业务流程复杂的时候并不推荐使用。

1.2 JSP Model2

相比于 JSP Model1 , 当业务流程复杂的时候,就需要把业务流程控制交给控制器来实现,JSP专注于视图的展现即可.这种模式就是 JSP Model2(即 JSP + Servlet + JavaBean).如图:
SSM Chapter 09  SpringMVC体系结构和处理请求控制器
从上图可以看出, 相比 Model1,Model2 是将控制层(Servlet)单独划分出来负责业务流程的控制,接收请求,创建所需的JavaBean实例,并将处理后的数据在返回给视图层(JSP)进行界面数据展示.这样的结构清晰,效果明显优化很多,并且也是一个松耦合的架构模式,所以除非项目非常简单,一般情况下建议使用 JSP Model2.

1.3 小结

1. MVC处理过程:

MVC整体结构图:
SSM Chapter 09  SpringMVC体系结构和处理请求控制器

从图中,我们可以分析出MVC整体的处理过程:

  • (1) 首先视图提供系统与用户交互的界面,并发送用户输入给控制器

  • (2) 控制器接收用户请求,并决定应该调用哪个模型来进行处理

  • (3) 模型根据用户请求进行相应的业务逻辑处理,并返回处理结果(数据)

  • (4) 控制器根据返回的处理结果,调用相应的视图格式化模型返回的数据,并通过视图呈现给用户结果

2. MVC优缺点:

SSM Chapter 09  SpringMVC体系结构和处理请求控制器

2 . Spring MVC 介绍及其环境搭建

2.1 Spring MVC 框架介绍

有了以上对MVC设计模式的认识,将有助于我们理解Spring MVC的理念与原理。

Spring MVC是Spring家族中应用于Web应用的一个模块,是Spring提供的一个基于MVC设计模式的Web开发框架,可以将它理解为Servlet。

在MVC模式中,Spring MVC作为控制器(Controller)来建立模型与视图的数据交互,是结构最清晰的JSP Model2实现,可以说是一个典型的MVC框架。如图所示
SSM Chapter 09  SpringMVC体系结构和处理请求控制器
除此之外,Spring MVC框架采用松耦合、可插拔的组件结构,具有高度可配置性,比起其他的MVC框架更具有扩展性和灵活性。此外,SpringMVC 的注解驱动和对REST风格的支持,也是它最具有特色的功能. 并且它本身就是Spring家族的一部分,与Spring框架整合更是天衣无缝。

在Spring MVC框架中,Controller替换Servlet来担负控制器的职责。Controller接收请求,调用相应的Model进行处理,Model处理完之后将结果返回给Controller,Conreoller再指派相应的View对处理结果进行渲染,最终响应给客户端进行展示。

由于Spring MVC结构较复杂,以上只是简单的介绍,接下来我们以一个例子,来搭建Spring MVC环境,从而更深入的了解它的架构模型及请求处理流程。万变不离其宗,依然以编程界的圣经——“Hello World”为例。

2.2 Spring MVC 环境搭建

IDEA中创建maven web项目,使用 Spring MVC 框架步骤如下:

  • (1) 引入jar文件(添加maven依赖)

  • (2) Spring MVC 配置

    • 在 web.xml 中 配置 Servlet,定义DispatcherServlet
    • 创建 Spring MVC 的配置文件
  • (3) 创建 Controller (处理请求的控制器)

  • (4) 创建 View(本书中我们使用的是JSP作为视图)

  • (5) 部署运行

1. 下载需要的jar文件

在之前Spring项目jar文件的基础上,加入Spring MVC 的两个 jar 文件

  • Spring-web.jar:在Web应用开发时使用Spring框架的核心类
  • Spring-webmvc.jar:Spring MVC 框架相关的所有类,包含框架的Servlets,Web MVC框架,以及对控制器和视图的支持.

IDEA中只需要加入如下一个依赖,就可以将Spring以及Spring MVC的jar包都导入

<!-- Spring 以及 Spring mvc的jar -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<!-- servlet jar -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<!-- jsp jar -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.1</version>
    <scope>provided</scope>
</dependency>
<!-- log4j2-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.11.1</version>
</dependency>

编写log4j2.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout
                    pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="org.springframework.web" level="debug" />
        <Logger name="org.springframework.context" level="debug" />
        <Logger name="cn.smbms.controller" level="debug" />
        <Root level="error">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

具体配置可参考官网http://logging.apache.org/log4j/2.x/manual/usage.html

或者使用slf4j记录日志:

在maven项目的pom.xml文件加入slf4j的坐标 , 内容如下:

<!-- slf4j-simple -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.26</version>
</dependency>

在resources目录下创建log4j.properties文件 , 内容如下:

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=warn, stdout
log4j.org.springframework=debug

2. 在 web.xml 中配置 Servlet

Spring MVC 是基于Servlet的,DispatcherServlet 是整个 Spring MVC 框架的核心,它负责截获请求并分派给相应的处理器.

跟所有的Servlet一样,DispatcherServlet也是在web.xml中配置:

参考官网:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
SSM Chapter 09  SpringMVC体系结构和处理请求控制器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">
    <!--配置SpringMVC的核心控制器-->
    <servlet>
    	<!--配置名为 springmvc 的Servlet,该Servlet是 DispatcherServlet类型,
    		它是Spring MVC 的入口-->
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
        	<!--该contextConfigLocation 用来指定SpringMVC配置文件所在的位置-->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--该配置标记容器在启动的时候 首先加载此 Servlet,即自动启动-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <!--该配置标记 DispatcherServlet需要截获并处理该项目的所有URL请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3. 创建Spring MVC 的配置文件(springmvc-servlet.xml)

在项目的resources目录下创建springmvc-servlet.xml 配置文件,配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">
    <!--指定url请求:index.html,处理该URL请求的控制器是IndexController -->
    <bean name="/index.html" class="cn.smbms.controller.IndexController"/>
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
     <!--通过配置prefix和suffix,将视图逻辑解析为:/WEB-INF/jsp/<viewName>.jsp -->
	<!--<constructor-arg index="0" value="/WEB-INF/jsp/"/>
        <constructor-arg index="1" value=".jsp"/>-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
	<!--配置视图解析器第二种方式-->
    <!-- <mvc:view-resolvers>
        &lt;!&ndash;默认情况下,“/WEB-INF/”被注册为一个视图名称的前缀。
        “jsp”作为后缀&ndash;&gt;
        <mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp" />
    </mvc:view-resolvers>-->
</beans>
1)配置处理器映射

我们在web.xml中配置DispatcherServlet时,配置了所有请求都由此Servlet来处理,那么DispatcherServlet怎么判断它将哪个请求、分发给哪个Controller(控制器)去处理呢?

DispatcherServlet需要咨询一个Bean组件,这个Bean叫做HandlerMapping,它的作用就是将一个URL请求映射到相应的Controller,类似于servlet-mapping中的url-pattern。

Spring MVC提供了多种处理器映射(HandlerMapping)的支持,如:

  • org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
  • org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
  • org.springframework.web.servlet.mvc.annotation.DefaultAnnontationHandlerMapping;
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,等等

可以根据需求选择处理器映射,此处我们采用BeanNameUrlHandlerMapping(默认的),即在Spring容器中查找与请求URL相同名称的Bean。这个映射器不需要配置,根据请求的URL路径映射到控制器Bean的名称。如下代码:

<!--指定url请求:index.html,处理该URL请求的控制器是IndexController -->
<bean name="/index.html" class="cn.smbms.controller.IndexController"/>
2)配置视图解析器

处理请求的最后一件必要的事情就是渲染输出,返回给客户端显示,这个任务由视图(View)实现。那么就需要确定指定的请求需要哪个视图进行渲染?

DispatcherServlet会查找到一个视图解析器,将控制器返回的逻辑视图名称转换成实际视图。Spring也提供了多种视图解析器,如下:

  • org.springframework.web.servlet.view.InternalResourceViewResolver;
  • org.springframework.web.servlet.view.ContentNegotiatingViewResolve,等等
    SSM Chapter 09  SpringMVC体系结构和处理请求控制器

此处我们使用InternalResourceViewResolver定义该视图解析器,通过配置prefix(前缀)和suffix(后缀),最后将视图逻辑名解析为/WEB-INF/jsp/xxx.jsp,即定位到/WEB-INF/jsp文件夹下的.jsp文件。

4. 创建 Controller

到这一步,Spring MVC的环境已经搭建好了,我们只需要再填入一点缺失的东西,即Controller和View。我们先创建Controller。

在com.smbms.controller目录下新建IndexController.java文件,即我们上面配置的处理器映射。如何将一个JavaBean变成可以处理前端请求的控制器?

需要继承org.springframework.web.servlet.mvc.AbstractController,并实现其handleRequestInternal方法,代码如下:

/**
 * 控制器
 */
public class IndexController extends AbstractController {
    private Logger log = LogManager.getLogger();
    @Override
    protected ModelAndView handleRequestInternal(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws Exception {
        log.info("Welcome to Spring MVC ^_^");
        System.out.println("Hello SpringMVC ~~~~");//在控制台输出信息
        return new ModelAndView("index");//返回逻辑视图名
    }

上述代码中,控制器处理方法的返回值为ModelAndView对象,该对象既包含视图信息,也包含模型数据信息,这样Spring MVC就可以使用视图对模型数据进行渲染。该示例中的 return new ModelAndView(“index”); 其中“index”就是视图的逻辑名,最后由视图解析器处理就会变成/WEB-INF/jsp/index.jsp。因为我们这个例子并不返回数据给页面,所以Model为空,不进行设置。

**注意 **

ModelAndView,正如其名所示,它代表Spring MVC中呈现视图界面时所使用的Model(模型数据)和View(逻辑视图名)。由于Java一次只能返回一个对象,所以ModelAndView的作用就是封装这两个对象,以方便一次返回我们所需的Model和View。

当然,返回的模型跟视图都是可选的,有时候模型中没有任何数据,那么只返回视图即可;或者只返回模型数据,让Spring MVC根据请求URL来决定视图。在以后会对ModelAndView对象进行更详细的讲解

5. 创建View

上面刚说过,我们的Controller返回的逻辑视图名被解析之后,会转换成/WEB-INF/jsp/index.jsp,所以我们需要在WEB-INF下创建jsp文件夹,jsp文件夹下创建真正的JSP视图-----index.jsp,并在视图上输出 “hello Spring MVC!”,代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>第一个Spring MVC 示例</title>
</head>
<body>
    <h1>Hello Spring MVC ^_^ !!!!</h1>
</body>
</html>

由于控制器IndexController返回的逻辑视图名为index,那么通过视图解析器,会将视图逻辑名解析为/WEB-INF/jsp/index.jsp,得到真正的视图名.

6. 部署运行

部署你的项目到Tomcat容器,启动项目,浏览器中输入请求路径:http://localhost:8080/ssm_ch09/index.html,浏览器正确返回相应页面.

通过上述示例,我们简单总结一下Spring MVC的处理流程:

当用户发起URL请求http://localhost:8080/ssm_ch09/index.html时,根据web.xml中对于DispatcherServlet的配置,该请求被DispatcherServlet所截获,并根据HandlerMapping找到处理此次请求的相应的Controller(WelcomeController),Controller处理完成之后返回ModelAndView对象,该对象告诉DispatcherServlet需要通过哪个视图来进行数据模型的渲染展示,然后DispatcherServlet拿着这个返回的视图名称去找视图解析器,转换成真正的View(即/WEB-INF/jsp/index.jsp),返回给浏览器客户端。

7. 更改HandlerMapping(处理器映射)

在以上的示例中,我们通过BeanNameUrlHandlerMapping的方式实现请求与Controller之间的映射,但是如果有多种请求,岂不是要在spring-mvc-servlet中配置多个映射关系?针对这种问题,Spring MVC提供了一键式配置方法:<mvc:annotation-driven />,通过注解的方式定义Controller,下面就对以上示例进行优化,结合实例来理解。

修改springmvc-servlet.xml配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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="cn.smbms.controller"/>
    <mvc:annotation-driven/>
    <!--配置视图解析器-->
     <mvc:view-resolvers>
        <!--默认情况下,“/WEB-INF/”被注册为一个视图名称的前缀。
        “jsp”作为后缀-->
         <!--通过配置prefix和suffix,将视图逻辑解析为:/WEB-INF/jsp/<viewName>.jsp-->
        <mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp" />
	</mvc:view-resolvers>

</beans>

我们更改了Spring MVC的处理器映射的配置为支持注解式处理器——配置<mvc:annotation-driven />标签。

它是Spring MVC提供的一种配置方法,配置此标签之后Spring MVC会帮我们做一些注册组件之类的事,它会根据注解定义的Controller中的处理方法所定义的URL请求路径,自动为我们做URL和Controller之间的映射。

具体的实现是,这个标签会自动注册DefaultAnnotationHandlerMapping(处理器映射)和AnnotationMethodHandlerAdapter(处理器适配器),从3.1.x版本开始对应实现类改为了RequestMappingHandlerMappingRequestMappingHandlerAdapter

Spring MVC会通过这两个实例完成对@Controller和@RequestMapping等注解的支持,从而找出URL与handler method的关系并予以关联。

然后我们添加了<context:component-scan>标签来扫描我们的controller包,找出使用注解定义的Bean组件,加载到Spring容器.

修改完了配置文件,再去我们的控制器做修改,将IndexController.java修改为以下内容:

/**
 * 控制器
 */
@Controller
public class IndexController {
    private Logger logger = Logger.getLogger(this.getClass());
    //@RequestMapping 表示 方法与 哪个 URL请求对应,此处对应的是"/index"
    @RequestMapping("/index")
    public String index(){
        logger.info("Hello,Spring mvc=====>");
        return "index";
    }

}

在之前讲解使用注解定义Bean组件的时候说过,@Controller用于定义控制层的组件。

这里我们使用@Controller注解将我们的WelcomeController类定义为一个控制器,使其成为一个可以处理HTTP请求的控制器,再使用@RequestMapping注解对它的方法进行标注,确定welcome()方法对应的请求URL,即说明URL为/welcome的请求都将由这个方法进行处理。

这样一来,如果还有其他的业务需求,若不是很复杂,那么就可以直接在该类下增加相应的方法即可,然后再为方法标注@RequestMapping。这样无须创建很多个JavaBean作为Controller,这也是我们经常用的一种方式,支持注解式的处理器。

重新运行项目,与之前的结果是一样的。

注:<mvc:annotation-driven />的原理实现在以后会详解,这里只需要会用就行。

解释<mvc:annotation-driven />如下:

  • <mvc:annotation-driven />配置开启的两个常用的组件,RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
  • RequestMappingHandlerMapping是HandlerMapping的实现类,它会在容器启动的时候,扫描容器内的bean,解析带有@RequestMapping 注解的方法,并将其解析为url和handlerMethod键值对方式注册到请求映射表中。
  • RequestMappingHandlerAdapter是HandlerAdapter的实现类,它是处理请求的适配器,说白了,就是确定调用哪个类的哪个方法,并且构造方法参数,返回值。

其实<context:component-scan/>标签是告诉Spring容器来扫描指定包下的类,并注册被@Component@Controller@Service@Repository等注解标记的组件。

<mvc:annotation-driven/>是告知Spring容器,我们启用注解驱动,支持@RequestMapping注解,这样我们就可以使用@RequestMapping来配置处理器。

> 扩展:使用编码的方式注册DispatcherServlet

在Servlet 3.0+环境中,您可以选择以编程方式配置Servlet容器作为备选方案,或者与web.xml文件结合使用。下面的例子注册了一个DispatcherServlet:

/**
 * 编程方式配置Servlet容器
 */
public class MyWebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("classpath:springmvc-servlet.xml");
        ServletRegistration.Dynamic registration = servletContext.addServlet(
                "dispatcher",new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

WebApplicationInitializer是Spring MVC提供的一个接口,它可以确保检测到您的实现并自动用于初始化Servlet 3容器。AbstractDispatcherServletInitializerWebApplicationInitializer的实现抽象基类 , 继承它可以使得注册DispatcherServlet更加容易,方法是覆盖指定servlet映射和DispatcherServlet配置位置的方法。

对于使用基于java的Spring配置的应用程序,建议这样做,如下面的示例所示:

/**
 * 注册DispatcherServlet
 */
public class MyWebAppInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * 指定spring配置类所在的位置
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    /**
     * 指定springmvc配置类所在的位置
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyWebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

MyWebConfig.java代码如下:

/**
 * 使用配置类的方式
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "cn.smbms.controller")
public class MyWebConfig {
    /*@Bean(name = "/index.html")
    public IndexController indexController(){
        return new IndexController();
    }*/
    @Bean
    public InternalResourceViewResolver internalResourceViewResolver(){
        InternalResourceViewResolver internalResourceViewResolver =
                new InternalResourceViewResolver();
        //        InternalResourceViewResolver internalResourceViewResolver =
//                new InternalResourceViewResolver("/WEB-INF/jsp/",".jsp");
        internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
    }
}

如果使用基于xml的Spring配置,应该直接从AbstractDispatcherServletInitializer扩展,如下面的示例所示:

/**
 * 注册DispatcherServlet
 */
public class MyWebAppInitializer
extends AbstractDispatcherServletInitializer{

    /**
     * 指定spring配置文件所在的位置
     * @return
     */
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    /**
     * 指定springmvc配置文件所在的位置
     * @return
     */
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("classpath:springmvc-servlet.xml");
        return appContext;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

注意:

更多注册DispatcherServle方法可参考官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-multipart**

2.3 Spring MVC 框架的请求处理流程以及体系结构

1. Spring MVC 框架的请求

通过上面的一个小示例,我们了解了Spring MVC环境的搭建,接下来再深入了解一下它的处理流程。
SSM Chapter 09  SpringMVC体系结构和处理请求控制器
分析这张图,可以发现Spring MVC也是一个基于请求驱动的Web框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(处理器)来进行处理。

下面具体分析一下它的处理步骤:

  • (1) 首先用户发送请求到前端控制器(DispatcherServlet),前端控制器根据请求信息(比如URL)决定将请求分发给哪个页面控制器(Controller)来处理。对应上图中的步骤1、2。

  • (2) 页面控制器接收到请求之后,进行业务处理,处理完毕之后返回一个ModelAndView。对应上图中的步骤3、4、5。

  • (3) 前端控制器将控制权收回,然后根据返回的逻辑视图名,通过视图解析器选择真正的视图,将模型数据传入供其渲染展示。对应上图中的步骤6、7。

  • (4) 前端控制器再次收回控制权,将响应结果返回给浏览器客户端,至此整个流程结束。对应上图中的步骤8

2. Spring MVC框架的体系结构

根据上述的请求处理流程,我们继续了解下Spring MVC的体系结构。
SSM Chapter 09  SpringMVC体系结构和处理请求控制器

通过上图我们发现,从接收请求到返回响应,Spring MVC框架的众多组件通力配合、各司其职地完成整个流程工作。

在整个框架中,Spring MVC通过一个前端控制器接收所有的请求,并将具体工作委托给其他组件进行处理,所以说DispatcherServlet处于核心地位,它负责协调组织不同组件完成请求处理并返回响应结果。

根据Spring MVC的请求处理过程,我们具体分析一下每个组件所负责的工作内容:

  • (1) 客户端发出HTTP请求,Web应用服务器接收此请求。如匹配DispatcherServlet的请求映射路径,则Web容器将该请求转交给DispatcherServlet处理;

  • (2) DispatcherServlet拿到请求之后,根据请求的信息(URL、请求参数、HTTP方法等)及HandlerMapping的配置找到处理请求的处理器(Handler);

  • (3) 当DispatcherServlet找到相应的Handler之后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。HandlerAdapter可以理解为真正使用Handler来干活的人。

  • (4) 在请求信息真正到达调用Handler的处理方法之前的这段时间,Spring MVC还完成了很多工作,它会将请求信息以一定的方式转换并绑定到请求方法的入参,对于入参的对象会进行数据转换、数据格式化以及数据校验等。这些都做完以后,最后才真正调用Handler的处理方法进行相应的业务逻辑处理。

  • (5) 处理器完成业务处理之后,将一个ModelAndView对象返回给DispatcherServlet,其中包含了逻辑视图名和模型数据信息。

  • (6) DispatcherServlet通过ViewResolver将逻辑视图名解析为真正的视图对象View,可以是JSP、HTML、XML、PDF、JSON等等,Spring MVC均可灵活配置。

  • (7) 得到真正的视图对象之后,DispatcherServlet会根据ModelAndView对象中的模型数据对View进行视图渲染。

  • (8) 最终客户端获得响应消息。

以上是关于Spring MVC的体系结构的分析,我们可以体会到其设计的精妙之处。

3. Spring MVC 框架的特点

通过上文的演示示例以及对Spring MVC的处理流程、体系结构的介绍,我们可以总结一下它的特点:

  • (1) 角色划分清晰。Model、View、Controller各司其职,耦合度较低。
  • (2) 灵活的配置功能。Spring的核心是IoC,统一可以实现在MVC上,把各种类当作Bean组件配置在Spring容器中。
  • (3) 提供了大量的接口和实现类,方便各种场景的开发。
  • (4) 真正做到与View层的实现无关。
  • (5) 结合Spring的依赖注入,更好地应用了面向接口编程的思想。
  • (6) 可以与Spring天衣无缝的整合
  • … 等等

总之,关于Spring家族的框架,对开发者来说都是一种不错的选择。

总结

Spring MVC结构比较复杂,学习的时候也要掌握方法。首先要明确Spring MVC是一个工具,既然是工具,那么就先学会它的使用方法,不要深陷其细节中,不建议刚开始学习就研究源码,深入浅出,慢慢地在使用过程中加深理解,最后看透源码自然会水到渠成。

3 . 参数传递

我们学习了Spring MVC的原理、处理流程及其体系结构,完成了请求与处理器之间的映射。接下来继续深入学习Spring MVC的一些知识,主要是参数传递(View到Controller、Controller到View)、视图解析器。

3.1 参数传递(View to Controller)

参数传递在项目中是必不可少的,只要是动态页面,肯定会涉及数据传递,想都不用想。接下来我们就学习一下View与Controller之间的参数传递。

页面往控制器传参,最粗暴的方式是直接入参,改造我们之前的IndexController.java如下:

/**
 * 控制器
 */
@Controller
public class IndexController {
    private Logger logger = Logger.getLogger(this.getClass());
    /**
     *  参数传递 View to Controller
     */
    @RequestMapping("/welcome")
    public String welcome(@RequestParam String username){
    	//如果方法的入参名跟传入的参数是一致的,可省略@RequestParam注解
        logger.info("welcome,"+username);
        return "index";
    }

}

运行项目,浏览器输入url:http://localhost:8080/ssm_ch09/welcome?username=admin,控制台正确输出日志信息.

以上示例中使用了@RequestParam在处理方法的的入参进行了标注,说明这是一个从View传来的参数(这里只简单的使用此注解,不进行任何设置,下文会讲述)。

这种情况下我们必须保证方法的入参名与View传来的参数名一致,可以发现,如果我们传来的参数名不是name而是userName,那么会报错400,错误信息是“请求中参数name不存在”。

但是在实际开发中,由于业务需求,可能有些参数并不是必需的,那么我们该如何解决这个问题呢?

这就需要详细了解@RequestMapping@RequestParam,两者结合使用,实现灵活的参数传递。

1. @RequestMapping

通过之前的学习,我们知道@RequestMapping的主要职责就是将不同的请求映射到对应的控制器处理方法。HTTP请求信息除了最基本的URL地址之外,还包括请求方法、HTTP协议及版本、报文头、报文体等,所以我们除了使用URL进行映射,还可以应用其他的HTTP信息来完成更精确的映射。

使用@RequestMapping完成映射,具体包含4个方面的信息:请求URL、请求参数、请求方法、请求头。

1)通过请求URL进行映射

我们之前的写法就是通过请求URL映射,即@RequestMapping("/welcome"),等同于@RequestMapping(value="/welcome")

此外,@RequestMapping可以在控制器的类定义处指定URL,即是这种的:

@Controller
@RequestMapping("/user")
public class IndexController{
    @RequestMapping(value = "/welcome")
    public String welcome(@RequestParam String name){
        //如果方法的入参名与传来的参数名一致,可省略@RequestParam注解
        System.out.println(name);
        return "welcome";
    }
}

在以上代码中,我们使用@RequestMapping注解在WelcomeController的类定义处进行了标注,定义URL为“/user”,此时这个URL相对于Web应用的部署路径,而welcome()方法处指定的URL则相对于类定义处的URL而言的,什么意思呢?

就是说,最后我们发过来的请求得是 http://localhost:8088/工程名/user/welcome?username=admin。需要注意的是在整个Web项目中,@RequestMapping映射的请求信息必须保证全局唯一.

这种是很常用的方式,在不同的Controller的类定义处指定相应的@RequestMapping,以便区分不同的请求,不易出错,也更规范。比如/user/add,/user/update,/user/delete,等等。

此外,分析源码可以看出来,@RequestMapping的返回值是一个String类型的数组,也就是说,我们还可以这样写:@RequestMapping({"/index","/welcome"})

这句话说明URL请求为/index的、/welcome的都可以进入该处理方

2 ) 通过请求参数、请求方法、请求URL进行映射

@RequestMapping除了可以使用请求URL进行映射请求之外,还可以使用请求参数、请求方法加以限制,通过多条件可以让请求映射更加准确。改造IndexController.java,如下:

@RequestMapping(value = "/welcome",method = RequestMethod.GET,params = "username")
public String welcome(String username){
    //如果方法的入参名与传来的参数名一致,可省略@RequestParam注解
    logger.info("welcome,"+username);
    return "welcome";
}

在上述代码中,@RequestMapping的value表示请求的URL,method表示请求方法,此次设置了GET,所以POST请求是进不来的,param表示请求参数,此处我们的参数名为name。运行我们的项目,同样可以访问此方法,下面分析一下:

首先,value(请求路径)匹配,method(超链接是GET请求)匹配,请求参数(name)也匹配,所以进入了处理方法。

下面分析几种错误情况。

  • 如果对地址:http://localhost:8080/ssm_demo_war/welcome?username=admin,则访问会出现400,控制台也会报出异常;

  • 如果将处理方法的入参改为String userCode,即这种的,与传参名不匹配:此时不会报错,只是后台会获取不到参数值,输出null。所以我们若选择方法参数直接入参,方法入参名必须与请求中的参数名一致。

简单的学习@RequestMapping之后,一开始的问题依然没有解决,如果没有传递参数依然报错。接下来学习@RequestParam,两者结合完成灵活的请求传参。

2. @RequestParam

在方法入参处使用@RequestParam注解指定其对应的请求参数。@RequestParam有以下三个参数:

  • value:参数名
  • required:是否是必需的,默认为true,表示请求中必须包含对应的参数名,否则抛出异常
  • defaultValue:默认参数名,不推荐使用,比较混乱

接下来利用@RequestParam注解中的第二个参数required 来解决上个示例中存在的参数非必需的问题,修改代码如下:

@RequestMapping(value = "/welcome")
public String welcome(@RequestParam(value="username",required = false) String username){
    logger.info("welcome,"+username);
    return "index";
}

测试运行,没问题。

这种情况下不要求方法的入参名与请求中的参数名一致,保证@RequestParam注解能够将二者关联上即可。但是为了规范起见,建议一致。

3.2 参数传递(Controller to View)

了解完从View到Controller的传参,接下来学习Controller到View的传参。

这就需要模型数据的处理了,对于MVC框架来说,模型数据是最重要的一部分,因为控制层是为了产生模型数据(Model),而视图(View)最终也是为了渲染模型数据并进行输出。

那么如何将模型数据传递给视图?Spring MVC提供了多种方式,介绍一下比较常用的:

1. ModelAndView

顾名思义,控制器的处理方法的返回值若是ModelAndView,即既包含Model,也包含View。那么拿到该对象之后,Spring MVC就可以使用视图对模型数据进行渲染。改造以上示例,拿到参数之后,再返回给页面:

@RequestMapping("/index1")
public ModelAndView index(String username){
    logger.info("welcome,"+username);
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("username",username);
    modelAndView.setViewName("index");
    return modelAndView;
}

通过以上代码可以看出在welcome处理方法中,返回了ModelAndView对象,并通过addObject()方法添加模型数据,通过setViewName()方法设置逻辑视图名。

ModelAndView对象的常用方法如下:

  • addObject(String attributeName,Object attributeValue); 该方法的第一个参数为key值,第二个参数为key对应的value。其中key的值可以随便指定,只要保证在该Model内唯一即可。
  • addAllObjects(Map<String,?> modelMap); 从此方法可以看出,模型数据也是一个Map对象,我们可以添加Map到Model。
  • setView(View view); 指定一个具体的视图对象。
  • setViewName(String viewName); 指定一个逻辑视图名。

然后在index.jsp页面使用EL表达式展现从Controller返回的ModelAndView对象中的Model(至于返回其他格式的数据,在以后介绍)。

<h1>Hello Spring MVC !!!!</h1>
<h1>username(key:username)---->${username}</h1>

上述代码中,通过EL表达式从Controller返回的ModelAndView对象中接收参数username的值.部署并运行测试,浏览器输入地址:http://localhost:8080/ssm_ch09/index1?username=admin. 运行正确,页面正确显示了username的参数值,并且控制台日志输出正确.

2. Model

除了可以使用ModelAndView对象来返回模型数据外,我们还可以使用Spring MVC提供的Model对象来完成模型数据的传递。

其实,Spring MVC在调用方法前会创建一个隐含的模型对象,作为模型数据的存储容器,一般称为“隐含模型”。

若处理方法的入参为Model类型,Spring MVC会将这个隐含模型的引用传递给这些入参,这样开发者就可以通过一个Model类型的入参,访问到模型中的所有数据,当然也可以做修改、添加等。

继续修改上面的示例,通过传入Model参数的方式完成参数返回。

IndexController关键代码如下:

/**
 * 参数传递 controler to view -(Model)
 * @param username
 * @param model
 * @return
 */
@RequestMapping("/index2")
public String index(String username, Model model){
    logger.info("welcome,"+username);
    model.addAttribute("username",username);
    return "index";
}

3. Map

我们之前说过,Spring MVC的Model其实就是一个Map数据结构,故我们使用Map作为处理方法的入参也是可行的.

/**
 * 参数传递 controler to view -(map)
 * @param username
 * @param map
 * @return
 */
@RequestMapping("/index3")
public String index3(String username, Map<String,Object> map){
    logger.info("welcome,"+username);
    //map = new HashMap<>();
    map.put("username",username);
    return "index";
}

这种方式依然可以达到我们想要的结果。

Spring MVC控制器的处理方法若有Map或者Model类型的入参,就会将请求内的隐含模型对象传递给这些入参,因此可以在方法体内对模型数据进行读写。

推荐使用Model。

4. @ModelAttribute

若希望将入参的数据对象放入数据模型中,就需要在相应入参前使用此注解。在后续的章节在详细讲解,此处做了解即可

5. @SessionAttributes

此注解可以将模型中的属性存入HttpSession中,以便在一次会话*享该属性。以后会讲解。

4 . 视图解析器

请求处理方法执行完成之后,最终返回一个ModelAndView对象。对于那些返回String类型的处理方法,Spring MVC内部也会将它们装配成一个ModelAndView对象,它包含逻辑视图名和数据模型,那么接下来的工作就交给视图解析器(ViewResolver)了。

ViewResolver是Spring MVC处理视图的重要接口,通过它可以将控制器返回的逻辑视图名解析成一个真正的视图对象。

Spring MVC默认提供了多种视图解析器,所有的视图解析器都实现了ViewResolver接口,如图:
SSM Chapter 09  SpringMVC体系结构和处理请求控制器
对于JSP这种常见的视图技术,通常使用InternalResourceViewResolver作为视图解析器。那么它是如何工作的?

InternalResourceViewResolver是最常用的视图解析器,通常用于查找JSP和JSTL等视图。

通过源码就可以看出,它是URLBasedViewResolver的子类,会将返回的逻辑视图名都解析成InternalResourceViewResolver对象,该对象会把Controller的处理方法返回的模型属性都放在对应的请求作用域中,然后通过RequestDispatcher在服务器端把请求转发到目标URL。

备注: 快捷键 Alt+7就能显示当前类中的所有方法、全局常量,方法还包括形参和返回值

我们只需要在配置文件中进行如下配置:

<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--通过配置prefix和suffix,将视图逻辑门解析为:/WEB-INF/jsp/<viewName>.jsp -->
<!--<constructor-arg index="0" value="/WEB-INF/jsp/"/>
        <constructor-arg index="1" value=".jsp"/>-->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

如果控制器的处理方法返回字符串“index”,那么经过此视图解析器的解析,即为:/WEB-INF/jsp/index.jsp

视图解析器的第二种配置如下:

<!--配置视图解析器第二种方式-->
 <mvc:view-resolvers>
    <!--默认情况下,“/WEB-INF/”被注册为一个视图名称的前缀。
    “.jsp”作为后缀-->
     <!--通过配置prefix和suffix,将视图逻辑解析为:/WEB-INF/jsp/<viewName>.jsp-->
    <mvc:jsp prefix="/WEB-INF/jsp/" suffix=".jsp" />
</mvc:view-resolvers>

5 . 扩展Maven插件

maven安装tomcat插件官网:https://tomcat.apache.org/maven-plugin-2.2/

Web项目 pom.xml文件中加入以下内容:

<build>
    <plugins>
        <!--定义tomcat插件-->
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <!--端口号-->
                <port>8080</port>
                <!-- 路径 -->
                <path>/</path>
                 <!-- 解决get乱码 -->
                <uriEncoding>utf-8</uriEncoding>
            </configuration>
        </plugin>
        <!-- jrebel热部署插件 -->
        <plugin>
            <groupId>org.zeroturnaround</groupId>
            <artifactId>jrebel-maven-plugin</artifactId>
            <version>1.1.9</version>
            <executions>
                <execution>
                    <id>generate-rebel-xml</id>
                    <phase>process-resources</phase>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

本章总结如下 :

最后,我们对Spring MVC做一下简单的总结:

  • MVC设计模式在各种成熟框架中都得到了良好的运用,它将Model、View、Controller三层清晰地分开,搭建一个松耦合、高可用性、高可适用性的完美架构。
  • Spring MVC框架是典型的MVC框架,是一个结构清晰的JSP Model2实现。它基于Servlet,核心是DispatcherServlet。
  • Spring MVC的处理器映射(HandlerMapping)可配置为注解式的处理器,只需配置<mvc:annotation-driven />标签即可。
  • Spring MVC的控制器的处理方法返回的ModelAndView对象,包括模型数据和视图信息。
  • Spring MVC通过视图解析器来完成视图解析工作,把控制器的处理方法返回的逻辑视图名解析成一个真正的视图对象。

对于Spring MVC框架的更多内容,以及控制器返回JSON数据、HTML字符串等,会在后续的章节中讲到

上一篇:The Linux Programming Interface


下一篇:SSM Chapter 03 动态SQL