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),如图所示:
根据图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),如图:
通过上图,我们可以发现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).如图:
从上图可以看出, 相比 Model1,Model2 是将控制层(Servlet)单独划分出来负责业务流程的控制,接收请求,创建所需的JavaBean实例,并将处理后的数据在返回给视图层(JSP)进行界面数据展示.这样的结构清晰,效果明显优化很多,并且也是一个松耦合的架构模式,所以除非项目非常简单,一般情况下建议使用 JSP Model2.
1.3 小结
1. MVC处理过程:
MVC整体结构图:
从图中,我们可以分析出MVC整体的处理过程:
-
(1) 首先视图提供系统与用户交互的界面,并发送用户输入给控制器
-
(2) 控制器接收用户请求,并决定应该调用哪个模型来进行处理
-
(3) 模型根据用户请求进行相应的业务逻辑处理,并返回处理结果(数据)
-
(4) 控制器根据返回的处理结果,调用相应的视图格式化模型返回的数据,并通过视图呈现给用户结果
2. MVC优缺点:
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框架。如图所示
除此之外,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
<?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>
<!–默认情况下,“/WEB-INF/”被注册为一个视图名称的前缀。
“jsp”作为后缀–>
<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,等等
此处我们使用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版本开始对应实现类改为了RequestMappingHandlerMapping
和RequestMappingHandlerAdapter
。
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容器。AbstractDispatcherServletInitializer
是WebApplicationInitializer
的实现抽象基类 , 继承它可以使得注册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环境的搭建,接下来再深入了解一下它的处理流程。
分析这张图,可以发现Spring MVC也是一个基于请求驱动的Web框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(处理器)来进行处理。
下面具体分析一下它的处理步骤:
-
(1) 首先用户发送请求到前端控制器(DispatcherServlet),前端控制器根据请求信息(比如URL)决定将请求分发给哪个页面控制器(Controller)来处理。对应上图中的步骤1、2。
-
(2) 页面控制器接收到请求之后,进行业务处理,处理完毕之后返回一个ModelAndView。对应上图中的步骤3、4、5。
-
(3) 前端控制器将控制权收回,然后根据返回的逻辑视图名,通过视图解析器选择真正的视图,将模型数据传入供其渲染展示。对应上图中的步骤6、7。
-
(4) 前端控制器再次收回控制权,将响应结果返回给浏览器客户端,至此整个流程结束。对应上图中的步骤8
2. Spring MVC框架的体系结构
根据上述的请求处理流程,我们继续了解下Spring MVC的体系结构。
通过上图我们发现,从接收请求到返回响应,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接口,如图:
对于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字符串等,会在后续的章节中讲到