【Spring MVC】Spring MVC框架的介绍及其使用方法-CSDN博客
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
1.前端控制器 DispatcherServlet不需要工程师开发由框架提供
2.处理器映射器 HandlerMapping不需要工程师开发由框架提供
3.处理器适配器 HandlerAdapter不需要工程师开发由框架提供
5.视图解析器 ViewResolver 不需要工程师开发由框架提供
4.3.2 Controller ----> Controller
4.5.1 Servlet api 中的 HttpServletRequest对象
2、使用@RequestParam注解的Map参数,object>
3、使用@RequestHeader注解的Map参数,object>
4、使用@PathVariable注解的Map参数,object>
一、MVC模式
之前详解了Spring体系结构的两大核心Spring IOC和Spring AOP今天主要谈Spring MVC。
Spring MVC 基于 MVC 模式因此理解 Spring MVC 需要先对 MVC 模式有所了解。
1.1 MVC模式的发展
1.1.1 Model1 模型
Model1 模型是很早以前项目开发的一种常见模型项目主要由 jsp 和 JavaBean 两部分组成。
它的优点是
结构简单。开发小型项目时效率高。
它的缺点也同样明显
- 第一JSP 的职责兼顾于展示数据和处理数据也就是干了控制器和视图的事
- 第二所有逻辑代码都是写在 JSP 中的导致代码重用性很低。
- 第三由于展示数据的代码和部分的业务代码交织在一起维护非常不便。 所以结论是此种设计模型已经被淘汰没人使用了。
在Model 1模式下整个Web应用几乎全部由JSP页面组成JSP页面接收处理客户端请求对请求处理后直接做出响应。用少量的JavaBean来处理数据库连接、数据库访问等操作。
1.1.2 Model2 模型
Model2 模型是在 Model1 的基础上进行改良它是 MVC 模型的一个经典应用。它把处理请求和展示数据进行分离让每个部分各司其职。 此时的 JSP 已经就是纯粹的展示数据了而处理请求的事情交由控制器来完成使每个组件充分独立提高了代码可重用性和易维护性。所以这个就是最终形态的MVC模型了。下图展示的就是 Model2 模型
Model 2是基于MVC架构的设计模式。 在Model 2架构中Servlet作为前端控制器负责接收客户端发送的请求在Servlet中只包含控制逻辑和简单的前端处理 后端JavaBean来完成实际的逻辑处理 最后转发到相应的JSP页面处理显示逻辑。 Model 2具有组件化的特点更适用于大规模应用的开发。
1.2 MVC模式简介
MVC模式是软件工程中常见的一种软件架构模式该模式把软件系统项目分为三个基本部分模型Model、视图View和控制器Controller。MVC就是 Model、View、和Controller的缩写。
MVC各部分根据职责进行分离使程序的结构更为直观增加了程序的可扩展性、可维护性、可复用性。
可以用如下的图形来表示MVC三者之间的关系
1.模型(Model)
模型封装了数据及对数据的操作可以直接对数据库进行访问不依赖视图和控制器也就是说模型并不关注数据如何展示只负责提供数据。GUI 程序模型中数据的变化一般会通过观察者模式通知视图而在 web 中则不会这样。
2.视图(View)
视图从模型中拉取数据只负责展示没有具体的程序逻辑。
3.控制器(Controller)
控制器用于控制程序的流程将模型中的数据展示到视图中。
二、Spring MVC模型简介
SpringMVC 全名叫 Spring Web MVC是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架。它以SpringIOC容器为基础并利用容器的特性来简化它的配置所以 SpringMVC 和 Spring 可直接整合使用是Spring框架的一个模块 。
Spring MVC属于SpringFrameWork的后续产品已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。SpringMVC是一种web层的mvc框架用于替代servlet处理响应请求获取表单参数表单验证等。所以Spring MVC是Spring体系结构的一部分如下图所示
Spring MVC其实就一种基于Servlet的MVC模型
- 模型一个或多个JavaBean对象用于存储数据和业务逻辑。
- 视图一个和多个JSP页面想控制器提交数据和为模型提供数据显示JSP页面主要使用HTML标记和JavaBean标记来显示数据。
- 控制器一个或多个Servlet对象根据视图提交的请求进行控制即将请求转发给业务逻辑的JavaBean并将处理记过存放到实体模型JavaBean中输出给视图显示。
Spring MVC 本质可以认为是对servlet的封装简化了我们serlvet的开发。
Spring容器和Spring MVC容器是父子容器的关系。Spring容器中可以装配Spring MVC容器中的Bean吗
同一个Bean是可以同时装配到父容器和子容器的也就是Spring容器和Spring MVC容器可以同时存在这个Bean
Spring容器不能使用Spring MVC中的Bean但是Spring MVC可以使用spring容器中的Bean。父容器不能用子容器中的Bean但是子容器可以用父容器中的Bean。
三、Spring MVC 六大核心组件
3.1 六大组件简介
1.前端控制器 DispatcherServlet不需要工程师开发由框架提供
DispatcherServlet本质上是一个Servlet相当于一个中转站所有的访问都会走到这个Servlet中再根据配置进行中转到相应的Handler中进行处理获取到数据和视图后在使用相应视图做出响应。
2.处理器映射器 HandlerMapping不需要工程师开发由框架提供
HandlerMapping本质上就是一段映射关系将访问路径和对应的Handler存储为映射关系在需要时供前端控制器查阅即根据请求的url查找Handler。
Spring MVC提供了不同的映射器实现不同的映射方式例如配置文件方式实现接口方式注解方式等。
3.处理器适配器 HandlerAdapter不需要工程师开发由框架提供
本质上是一个适配器可以根据要求HandlerAdapter要求的规则找到对应的Handler来运行。
由于 Handler 涉及到具体的用户业务请求所以一般情况需要工程师根据业务需求开发 Handler。
通过HandlerAdapter对处理器进行执行这是适配器模式的应用通过扩展适配器可以对更多类型的处理器进行执行。
4.处理器 Handler需要工程师开发
注意编写Handler时按照HandlerAdapter的要求去做这样适配器才可以去正确执行Handler。
Handler叫做处理器也叫控制器是继DispatcherServlet前端控制器的后端控制器在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求所以一般情况需要工程师根据业务需求开发Handler。
5.视图解析器 ViewResolver 不需要工程师开发由框架提供
本质上也是一种映射关系可以将视图名称映射到真正的视图地址。前端控制器调用处理器适配完成后得到model和view将view信息传给视图解析器进行视图解析来得到真正的view。
6.视图渲染 View (需要工程师开发jsp...)
View是一个接口实现类支持不同的View类型jsp、freemarker、pdf...。 View对象是通过视图解析器生成的它的作用就是将handler处理器中返回的model数据嵌入到视图解析器解析后得到的页面中向客户端做出响应。
3.2 Spring MVC中的handler究竟是什么
了解过Sping MVC流程的同学一定听说过handler百度翻译过来是处理者很多博客中称之为处理器。那就按照大部分人的说法称呼它为控制器说到控制器会不会联想到我们平常写业务代码中的各种controller也是控制器那他们两个是不是一种东西呢这里可以大胆猜测一下就是一种东西现在通过源码进行验证猜测
如果直接从源码中按照类文件类型直接搜索Handler是找不到的根据Spring MVC的工作流程开始捋(有很多帖子说过这里不在重述)最早出现handler是在这个地方:
AbstractHandlerMapping.java中getHandler()
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取handler,如果获取为空则使用默认handler,如果默认的也没有则返回null
Object handler = getHandlerInternal(request);
// 省略部分代码....
}
测试案例发送实际请求
@RequestMapping("/test")
@RestController
public class Test {
@GetMapping("/add")
public String add(String a,Personal personal,@RequestParam(name = "genderType") int gender) {
int i=0;
return a;
}
}
对应请求来看一下debug
Object handler = getHandlerInternal(request); 中handler具体是什么内容截图如下
从中可以看到handler实际上是一个HandlerMethod类型的对象里面的属性有请求所在的类信息、请求方法、请求参数等内容。所以从这里可以认为handler相当于是平常业务代码中每个请求对应的的Controller类以及方法信息。上面debug截图对应起来更容易理解
总结
Handler是什么
- Handler是一个Controller的对象和请求方式的组合的一个Object对象。其实可以粗略的认为Handler就是Controller对象也分情况有的类型的处理器就可以认为是Controller中的方法里面是程序员编写的处理请求的逻辑。
- HandleExcutionChains是HandleMapping返回的一个处理执行链它是对Handler的二次封装将拦截器关联到一起。然后在DispatcherServlert中完成了拦截器链对handler的过滤。
- DispatcherServlet要将一个请求交给哪个特定的Controller它需要咨询一个Bean——这个Bean的名字为“HandlerMapping”。HandlerMapping是把一个URL指定到一个Controller上就像应用系统的web.xml文件使用<servlet-mapping>将URL映射到servlet。
3.3 Spring MVC拦截器
Spring MVC提供了拦截器支持这也算是Spring MVC的一个组件是利用AOP实现的。
Servlet提供了过滤器Filter和监听器ListenerSpring MVC提供了拦截器Interceptor。
下面简单介绍一下它的使用方法我们以后会单独对拦截器进行详细讲解。
1、创建自定义的拦截器类实现HandlerInterceptor接口
true表示放行false表示拦截。
三种拦截器:
- preHandler: 请求通过DispatcherServlet后到达后端Handler之前进行拦截;
- postHandler: 后端Handler处理完后请求返回到DispatcherServlet之前进行拦截;
- afterHandler: 视图解析器View处理完后请求返回DispatcherServlet之前进行拦截;
2、注册拦截器
XML注册
在springmvc.xml中进行注册
<!--注册拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!-- 要拦截的路径 -->
<mvc:mapping path="/**"/>
<!-- 排除拦截的路径 -->
<mvc:exclude-mapping path="/login"/>
<!-- 拦截处理类拦截器类-->
<bean class="com.jd.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
- beanmvc:interceptors 标签下的拦截器 bean 将应用到所有的处理器。
- mvc: interceptor这个标签下的子标签可以指定拦截器应用到哪些请求路径。
- mvc: mapping指定处理的请求路径。
- mvc: exclude-mapping指定排除的请求路径。
- bean指定应用到给定路径的拦截器 bean。
注解注册
对于注解配置来说需要将 MappedInterceptor 配置为 Spring 的 bean。
@Configuration
public class MvcConfig {
@Bean
public MappedInterceptor logInterceptor() {
return new MappedInterceptor(null, new LoginInterceptor());
}
@Bean
public MappedInterceptor loginInterceptor() {
return new MappedInterceptor(new String[]{"/**"}, new String[]{"/login"}, new LoginInterceptor());
}
}
API 配置
拦截器与 Spring MVC 环境紧密结合并且是作用范围通常是全局性的因此大多数情况建议使用这种方式配置。
这里在配置类上添加了@EnableWebMvc注解开启了 Spring MVC 中的某些特性然后就可以实现 WebMvcConfigurer 接口中的 addInterceptors 方法向 Spring MVC 中添加拦截器。如果你使用了 spring-boot-starter-web不再需要手工添加 @EnableWebMvc 注解。
拦截器Interceptor和过滤器Filter的区别
- 过滤器是基于函数回调的而拦截器是基于Java反射的。
- 过滤器依赖于servlet容器是Servlet提供的组件而拦截器不依赖与Servlet容器拦截器依赖 Spring MVC是Spring MVC提供的组件。
- 过滤器几乎对所有的请求都可以起作用而拦截器只能对Spring MVC请求起作用。
- 拦截器可以访问处理方法的上下文而过滤器不可以。
两者的执行流程图
- 过滤器是在到达DispatcherServlet之前进行过滤所以过滤器几乎能对所有的请求起作用
- 拦截器是经过DispatcherServlet后进行拦截因为DispatcherServlet是Spring MVC提供的所以拦截器只能拦截对Spring MVC的请求。
层次关系图
四、Spring MVC的使用
4.1 环境搭建
这里我们以xml配置为例来讲解。其实也可以用注解来进行配置但是用xml配置更能帮助我们理解底层的原作原理。
4.1.1 引入依赖
<dependencies>
<!-- Spring MVC的核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- Spring MVC是基于servlet的所以也要引入servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<!--这里的scope是一定要加的 不然程序会出问题-->
<scope>provided</scope>
</dependency>
<!-- Spring MVC底层在帮我们进行JSON格式的解析和与Java属性之间的映射Spring MVC 使用jackson作为默认的json转换器所以需要我们引入jackson依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
4.1.2 配置web.xml
Spring MVC 已经提供了一个 DispatcherServlet 类作为前端控制器Java Web项目只要是使用servlet就需要配置web.xml所以要使用 Spring MVC 必须在web.xml 中配置前端控制器。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置springmvc的核心servlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<!-- 配置Spring MVC前端控制器 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定 Spring 容器启动加载的配置文件-->
<init-param>
<!--配置Spring MVC配置文件的位置-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Tomcat 启动初始化 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置前端控制器servlet映射路径 -->
<servlet-mapping>
<!--这里的url配置的是/说明要拦截所有请求来将请求转到DispatcherServlet去做处理-->
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 添加 register.jsp 为首页也就是URL访问项目名称时会默认跳转到这个页面 -->
<welcome-file-list>
<welcome-file>register.jsp</welcome-file>
</welcome-file-list>
</web-app>
其中<param-value>标签中的**.xml 这里可以使用多种写法
- 什么也不写,使用默认值:/WEB-INF/-servlet.xml
- /WEB-INF/classes/springMVC.xml
- classpath*:springmvc-config.xml
- 多个值用逗号分隔
注意
load-on-startup 元素是可选的若值为 0 或者大于 0 时表示容器在应用启动时就构建 Servlet 并调用其 init 方法做初始化操作非负数的值越小启动该 Servlet 的优先级越高若值为一个负数时或者没有指定时则在第一次请求该 Servlet 才加载。配置的话就可以让 Spring MVC 初始化的工作在容器启动的时候完成而不是丢给用户请求去完成提高用户访问的体验性。
4.1.2.1 配置映射路径的注意点
配置前端控制器的映射路径一般有以下的三种形式
- 配置如 .do、.htm 是最传统方式可以访问静态文件图片、 JS、 CSS 等但不支持 RESTful风格。
- 配置成 /可以支持流行的 RESTful 风格但会导致静态文件图片、 JS、 CSS 等被拦截后不能访问。
- 配置成 /*是错误的方式可以请求到 Controller 中但跳转到调转到 JSP 时被拦截不能渲染JSP 视图也会导致静资源访问不了。
4.1.2.1.1 访问静态资源和 JSP 被拦截的原因
Tomcat 容器处理静态资源是交由内置 DefaultServlet 来处理的拦截路径是 /处理 JSP 资源是交由内置的 JspServlet 处理的拦截路径是*.jsp | *.jspx。
启动项目时先加载容器的 web.xml而后加载项目中的 web.xml。当拦截路径在两者文件中配置的一样后面会覆盖掉前者。
所以前端控制器配置拦截路径是 / 的所有静态资源都会交由前端控制器处理而拦截路径配置 /*所有静态资源和 JSP 都会交由前端控制器处理。
4.1.2.1.2 如何解决
1、方式一
在 web.xml 中修改修改前端控制器的映射路径修改为*.do但注意访问控制器Controller里的处理方法时请求路径须携带 .do。
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
2、方式二
在 springmvc.xml中加入一段配置这个配置会在 Spring MVC 上下文中创建存入一个
DefaultServletHttpRequestHandler 的 bean它会对进入DispatcherServlet的请求进行筛查若不是映射的请求就将该请求交由容器默认的 Servlet处理。
<mvc:default-servlet-handler/>
4.1.3 编写Spring配置文件
在 web.xml 中配置了 DispatchcerServletDispatchcerServlet 加载时需要一个 Spring MVC 的配置文件默认会去 WEB-INF 下查找对应的 [servlet-name]-servlet.xml 文件所以说Spring的xml配置和Spring MVC的xml配置是两个独立的配置文件如本例中默认查找的是 springmvc-servlet.xml。
Spring MVC 的配置文件可以放在任何地方用 servlet 的子元素 init-param 标签描述即可。
<?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">
<!-- 1. 开启注解扫描-->
<context:component-scan base-package="com.lin.controller"/>
<!--开启Spring MVC其实就是将它的组件都作为Bean装配到Spring容器中-->>
<!-- 2. 配置处理器映射器-->
<!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />-->
<!-- 3. 开启处理器适配器-->
<!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />-->
<!-- 上面两段配置可以被下面的一句话所替代封装 这个注解里封装了所有种类的处理器映射器和适配器所以添加这个标签以后就可以不用单独配置处理器映射器和处理器适配器了所有的实现类都可以使用-->
<mvc:annotation-driven />
<!-- 4. 开启视图解析器-->
<!-- 定义了一个名为 “viewResolver” 的 InternalResourceViewResolver 管理器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--通过 <property> 标签设置视图的前缀和后缀 配置这个Spring MVC 找视图的路径就是前缀 + 逻辑视图名处理方法设置或返回视图名+ 后缀名-->
<!--prefix 属性指定了视图文件的路径前缀-->
<property name="prefix" value="/"/>
<!-- suffix 属性指定了视图文件的后缀名 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
注在 Spring4.0 之后如果不配置处理映射器、处理器适配器和视图解析器会使用默认的。
在 spring-mvc.xml 的配置文件里加上 <mvc:annotation-driven></mvc:annotation-driven> 相当于在 xml 中同时加入了如下配置当然如果不想要这么多也可以单独配置想要的 HandlerMapping 等 bean 信息。
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- HadnlerExceptionResolvers -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"></bean>
这里讲一下视图解析器的配置一般如果在Controller里面要返回视图视图页面的话会在Controller方法return一个字符串这个字符串就是那个视图文件的名字例如html文件名注意是不带文件类型后缀的在Spring MVC 程序运行时Thymeleaf 视图解析器会将视图的前缀和后缀与Controller返回的逻辑视图名拼接组成真正的 Thymeleaf 文件路径生成真正的View类型的对象然后再通过这个View接口的实现类把 Model 数据渲染到这个 Thymeleaf 中将model渲染到view的操作是View对象完成的以达到将视图展示给用户的目的。
也就是说配置中的前缀就是存放视图文件的路径后缀就是这个视图文件的类型后缀例如html、jsp等。
如果不配置这个前缀和后缀的话那么在Controller方法中返回视图名称字符串的时候就需要返回视图文件的全路径并且要带着视图文件格式后缀不能只返回视图文件的名称例如
@Controller
public class ResponseController {
@RequestMapping("/resp2")
public String resp2(Model model) {
// 往作用域或者模型中存入数据
model.addAttribute("msg", "方法返回类型是 String");
// 返回视图全路径并且要带着文件类型后缀
return "/WEB-INF/views/resp.jsp";
}
}
4.1.4 编写控制器Controller
@Controller
public class HellowController {
/*
@RequestMapping的修饰范围可以用在类上和方法上他的作用如下
1. 用在方法上可以给当前方法加入指定的请求路径
2. 用在类上可以给类中的所有方法都加入一个统一的请求路径在这个方法访问之前都必须加上
*/
@RequestMapping("/hello")
public String hello(String username,String password){
System.out.println("hello");
// 返回视图名称
return "index";
}
}
Spring MVC有三种不同的实现方式
- 通过实现Controller接口控制器有返回值。它的处理器适配器实现是一个叫SimpleControllerHandlerAdapter的类实现的。
- 通过Controller注解。基本流程和接口实现的完全一样只是有一些细节上的差别比如处理器适配器是RequestMappingHandlerAdapter实现的这一点和通过接口是实现是有区别的。
- 实现HttpRequestHandler接口这个没有返回值。这个方法用的不多。基本流程也是一样的只有一些不同比如这个的处理器适配器用的是HttpRequestHandlerAdapter所以handle方法的实现都不太一样。
上面三种方法的控制器应该都是一样的都是DispatcherServlet都调用了doService方法。
4.2 Spring MVC相关注解简介
4.2.1 @Controller
该注解作用于类上用来标识这是一个控制器组件类并创建这个Bean告诉spring我是一个控制器。
4.2.2 @RequestMapping
这个注解可以作用在方法上或者是类上用来指定请求路径。
4.3 Spring MVC的跳转方式
传统的Servlet开发跳转方式有两种
- forward请求转发forward跳转是在服务器内部跳转所以是同一次请求地址栏不变。跳转时可以携带数据进行传递使用request作用域进行传递。
- redirect重定向redirect跳转是客户端跳转所以是多次请求地址栏会改变跳转时不可以携带数据传递不共享之前请求的数据。
在请求转发和重定向的时候我们一般有两种方式来写请求路径
- 加/使用是绝对路径推荐使用从项目根路径查找。(/response/test6 ---> "redirect:/hello.html" ---> localhost:/hello.html)
- 不加/使用是相对路径相对于当前路径来查找。(/response/test6 ---> "redirect:hello.html" ---> localhost:/response/hello.html)
4.3.1 Controller ----> 前台页面
4.3.1.1 forward
通过测试我们可以发现Spring MVC默认的就是使用请求转发的方式来进行跳转到前台页面的;
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
// 默认使用请求转发跳转到前台页面 就相当于request.getRequestDispatcher().forward(request,response)
return"index";
}
}
也可以在return的时候加上请求转发关键字但是加了关键字后配置的视图解析器就不起作用了。返回视图必须写全路径。
return "forward:/WEB-INF/views/welcome.jsp";
4.3.1.2 redirect
如果我们想使用重定向的方式来进行跳转的话需要使用Spring MVC提供给我们的关键字——redirect:来完成。
语法
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
// 相当于 response.sendRedirect()
// 注意在redirect:后接页面的不是逻辑名而是全路径名。因为redirect跳转不会经过视图解析器。
return "redirect:/视图全路径名";
}
}
注意在redirect:后接页面的不是逻辑名而是全路径名。因为redirect跳转不会经过视图解析器。
4.3.2 Controller ----> Controller
4.3.2.1 forward
如果我们想使用请求转发的方式跳转到相同不同Controller的不同方法的时候我们也需要使用Spring MVC提供的关键字forward:。
语法
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
return:"forward: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;"
}
}
4.3.2.2 redirect
如果我们想使用重定向的方式跳转到相同不同Controller的不同方法的时候我们也需要使用Spring MVC提供的关键字redirect:。
语法
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
return:"redirect: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;"
}
}
4.4 Spring MVC的参数接收
前端向后台传递参数的方式
- 通过 Servlet api 中的 HttpServletRequest对象
- 基本数据类型 + String类型
- Array 数组
- Java Bean 对象
- List 集合
- Map 集合
- JSON 格式
4.4.1 Servlet接收参数的方式
这种方式想必是学习了Java Web之后最熟悉的一种获取前端页面传递参数的方式。直接在服务器端使用对HttpServletRequest对象进行操作即可。
在传统的Servlet开发我们一般都是用这种方式来进行接收请求参数的。
// 接收名字为name的参数
request.getParameter(name)
通过request对象的getParameter()方法直接获取指定的key属性名即可获取到对应的value属性值然后赋值给了name变量。
在SpringMVC的Controller中这种传统方式也是存在的。具体代码如下
import org.springframework.stereotype.Controller;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/param")
public class ParamController{
@RequestMapping("/servlet")
// 在Controller参数中写上HttpServletRequest就可以获取到request对象
public String servlet(HttpServletRequest request){
// 通过request获取参数
String name = request.getParameter("name");
System.out.println("name:" + name);
request.setAttribute("result", "hello " + name);
return "hello";
}
}
这种方式需要注意的点无非就是在Controller具体的请求处理方法中需要传入一个HttpServletRequest对象来作为当前方法的形参然后就可以在此方法中使用这个request对象了这个对象跟之前传统的方法中的request对象是一致的。通过它可以获取到很多的内容getSession(),getHeader(),getAttribute()等等。
在前端页面中访问的时候只需要指定url的路径然后利用key=value的形式访问即可。无需书写其他的代码例如
http://localhost:8080/param/servlet?name=golden3young
这样后端接收到name参数的之后使用System.out.println("name:" + name);即可将值打印到控制台上。
Servlet有几个需要注意的点
- 参数要求是表单域的name属性。
- getParameter方法用于获取单个值, 返回类型是String。
- getParameterValues方法用于获取一组数据, 返回结果是String[]。
- 冗余代码较多, 使用麻烦, 类型需要自己转换。
Spring MVC使用的是控制器中方法形参列表来接收客户端的请求参数它可以进行自动类型转换要求传递参数的key要与对应方法的形参变量名一致才可以完成自动赋值。它的优势很明显
- 简化参数接收形式不需要调用任何方法和@RequestParam注解, 需要什么参数, 就在控制器方法中提供什么参数。
- 参数类型不需要自己转换了。但是日期时间默认为yyyy/MM/dd得注意需要在处理方法的 Date 类型的形参上使用@DateTimeFormat注解声明日期转换时遵循的格式, 否则抛出400异常。
@Controller
public class RequestController {
@RequestMapping("/req5")
// 注意形参的类型为 java.util.Date
public ModelAndView resp5(@DateTimeFormat(pattern="yyyy-MM-dd")Date date) {
System.out.println(date.toLocaleString());
return null;
}
}
如果日期在封装对象的字段那么我们需要在字段的上贴@DateTimeFormat注解。
public class User {
private Long id;
private String Username;
private String password;
// 增加下面这个字段并贴注解
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date date;
// 省略 setter getter toString
}
@Controller
public class RequestController {
@RequestMapping("/req6")
public ModelAndView resp6(User user) {
System.out.println(user);
return null;
}
}
4.4.2 基本数据类型 + String类型
我们可以在Spring MVC的Controller中具体请求处理方法中直接使用基本数据类型或者String类型的参数进行对映只要前端页面和方法中指定的参数名称是完全一致的这里包括大小写那么前端这个参数名称所赋有的值就会传递到后台的处理请求的方法Controller中。其实这其中是Spring MVC底层的拦截器帮我们实现的它会将请求中传来的所有参数跟我们所定义的请求处理方法中的参数进行对比如果发现是一模一样的它就会从请求中把那个参数的值拿出来赋给我们处理方法中的那个变量于是我们就可以直接使用了。
总的要求就是传递参数的key要与对应方法的形参变量名一致才可以完成自动赋值。
SpringMVC的Controller中具体的请求处理方法
/**
* Spring MVC的自动匹配参数
*
* 形参paramName会自动匹配请求中key为paramName的参数值。
*
* 可以接收AJAX封装的请求参数
*
* @param paramName
*/
@RequestMapping("/simple")
// 参数中不需要加再加@RequestParam等注解只要是方法的参数名和请求参数的key一致就可以Spring MVC就可以自动匹配
public String simple(int id, String name, ModelMap modelMap){
System.out.println("id:" + id);
System.out.println("name:" + name);
modelMap.addAttribute("result", "Hello " + name + "," + id);
return "hello";
}
这里我们可以看出Controller中定义了请求路径为/simple的请求将直接执行simple()这个方法而这个方法我们给了3个形参id、name、modelMap这里我们只关注int id, String name 即可这两个参数的类型一个是int属于基本数据类型之一另一个是String字符串类型。那么这么写的意义是什么呢其实就是前端页面在传递值的时候如果有参数名为id或者name的属性则会直接将属性的值赋给这两个变量。
前端页面在访问时只需要给出指定的url和传递指定名称的参数即可例如
http://localhost:8080/param/simple?id=1&name=golden3young
通过上面的url可以看出请求直接奔向/param/simple而对应的方法恰好就是simple()方法同时前端请求通过key=value的格式传递了两个参数id 和 name 而我们的simple()方法中恰好就有同名的id 和 name那么我们simple()方法中的id和name将获得前端请求传来的值1 和 golden3young。同时它们的类型也会被转变成方法中定义的int和String类型。
有的同学可能发现了前端请求在传递过程中明明都是通过字符串格式传递的而为什么到了Controller的simple()方法中就变成了int和String类型是怎么转换的其实这就是Spring MVC的拦截器为我们做的事情。它不光接收映射同名的参数而且还会帮助我们将类型转换成功。但是类型的转换存在着问题比如:int类型是整数类型刚才传来的参数id就是int类型而值恰好是1则不会出现问题可是如果将值改成abc等非数字的内容呢那肯定是会报错的因为数字格式异常无法进行转换。同时还有传递的时候忘记传递id属性而只传了name属性或者id属性只给了key而没有value值这些情况都会导致错误的出现。
罗列一下几种错误
http://localhost:8080/param/simple?id=&name=golden3young
此时id为空字符串那么Spring MVC底层在为我们转换的时候是将空串转成int类型那肯定是会报错的。页面报错400.
http://localhost:8080/param/simple?name=golden3young
此时我们没有传递id这个属性那么Spring MVC底层在匹配的时候一旦没有找到前端传来的属性那么就会直接给Controller的方法中的参数赋值成null那么把null转成int类型同样也是会报错的。页面报错500.
那像以上几种情况都需要大家在前端参数传递的时候注意不要遗漏并且给出正确类型的数值。这里加一个小的拓展有些情况我们可能无法断定前端一定会传过来某一个属性有可能不传那这种情况我们可以进行规范。使用的注解是@RequestParam。
4.4.2.1 @RequestParam 注解
其实通过Spring MVC接收参数在Controller的方法参数上都可以使用@RequestParam注解。使用它和不适用它达到的效果是一样的都可以接收参数但是这个注解给我们提供了一些额外的拓展功能可以帮助我们完成一些额外的事情比如解决上面说的前端没有传递基本数据类型的参数导致报错的情况。
下面我们简单讲解一下这个注解。
1、作用
@RequestParam将请求参数绑定到你控制器的方法参数上是Spring MVC中接收普通参数的注解
2、语法
@RequestParam(value = "参数名", required = "true/false", defaultValue = "")
- value接收的参数名
- required是否包含该参数默认为true表示该请求路径中必须包含该参数如果不包含就报错。设置false的话请求路径中就不用必须包含该参数
- defaultValue默认参数值如果设置了该值required=true将失效自动为false如果没有传该参数就使用默认值
我们可以通过设置参数的defaultValue来避免前端没有传递基本数据类型的参数导致报错的问题了。
这个注解还可以解决一天个问题那就是请求参数名和控制器方法参数列表形参不同名的情况下如果不加注解Spring MVC是没办法将它们匹配上的。
如果前台传递过来的参数名和控制器方法中参数列表的形参参数名不相同的话我们需要使用一个注解@RequestParam("前台携带的参数名")来告诉Spring MVC我们任何对数据来进行赋值。
// 请求路径为/req1?username=zs&age=18
package cn.linstudy.web.controller;
@Controller
public class RequestController {
@RequestMapping("/req1")
public ModelAndView resp1(@RequestParam("username") String username1, @RequestParam("age") int age1) {
System.out.println(username);
System.out.println(age);
return null;
}
}
4.4.3 数组类型
这种方式简单来说就是在前端页面通过发送数组格式的数据后台Controller的处理方法中在接收参数的时候直接转换成数组格式。跟 基本数据类型和String类型的 思想是一样的但是转换的类型是不同的。
那这种方式我们在前端页面中经常用到的地方其实就是form表单中的多选框因为多选框的name都是一样的但是值有多个传递到后端页面后传统方式是进行截取而现在Spring MVC可以帮助我们完成底层的工作直接给我们一个array数组。我们需要接收数组类型的时候只需将要接收的数组类型直接声明为方法的形式参数数组名和要传入的数组名一致即可。
示例
前端页面代码
<form action="${pageContext.request.contextPath}/param/array" method="post">
爱好
<input type="checkbox" name="hobby" value="唱歌" />唱歌
<input type="checkbox" name="hobby" value="跳舞" />跳舞
<input type="checkbox" name="hobby" value="书法" />书法
<input type="checkbox" name="hobby" value="滑雪" />滑雪
<br>
<input type="submit" value="提交" />
</form>
Controller中的处理方法
@PostMapping("/array")
// 方法上的数组名和前端传递的数组名一致
public String array(String[] hobby){
for(String hobbyStr : hobby){
System.out.println(hobbyStr);
}
return "hello";
}
前端参数的name为hobbyvalue的值为多个不同的值而后端Controller中的方法中指定了同名hobby的一个String数组类型的参数于是乎Spring MVC底层就把从前端传递过来的多个hobby的值以数组的格式存放到了hobby数组中。我们使用了for循环进行了遍历打印。
4.4.4 对象类型Java Bean实体类
通过上面3种方式的介绍大家其实也能感觉出来SpringMVC大大简化了我们将请求中的参数进行转换的这样一个过程我们只需要吃现成的即可。那么同样有这样一个问题如果前端页面一次性要传递多个参数比如十个以上包括id,age,name,birthday,gender,school,city,province,area,salary,married等等参数如果按照上面学过的方法我们需要把这所有的参数都写在执行方法的形参位置处即可。如
@RequestMapping("/bean")
public String testBean(int id, int age, String name, String birthday, String school,
String city, String area, double salary, boolean married){
.....
}
这样显然写起来是非常麻烦如果执行方法不光这一个还有多个都需要接收这些参数那我们写起来就会浪费大量的无用功的时间。这时我们就用到面向对象的编程思想也就是创建一个对象让这些参数都成为这个对象的属性然后Spring MVC就会将前端传来的这些参数的名称与我们指定的对象Java Bean的属性进行名称的对比如果一致那么就进行赋值于是乎我们先创建一个对象用来存放所有的参数
@Data
public class User{
private int id;
private int age;
private String name;
private String birthday;
private String school;
private String city;
private String area;
private double salary
private boolean married;
}
如果我们需要接收对象类型的话直接将需要接收的对象作为控制器的方法参数声明即可。Spring MVC会自动封装对象若传递参数key与对象中属性名一致就会自动封装成对象。
那么这样Controller中处理方法的代码就简化成了这样
/**
* Spring MVC的自动装箱
*
* 如果我们需要接收对象类型的话直接将需要接收的对象作为控制器的方法参数声明即可。
* Spring MVC会自动封装对象若传递参数key与对象中属性名一致只要参数的属性名和传入的key一致即可完成封装就会自动封装成对象。
*
* @param paramsEntity
* @return
*/
@RequestMapping("/bean")
// 方法参数中直接写User即可Spring MVC会将传进来的参数和User对象中的属性名匹配名称相同的就会进行赋值最终就会将传入的参数封装成user对象
// 这里方法参数名称并不重要只要是对象的成员属性名称能和传入的参数名称匹配上就可以了
public String testBean(User user){
.....
}
然后我们只需要在方法中调用user.getter方法来获取所有的属性值或者说参数值。
当然这里有的同学可能会问那底层是怎么实现的呢SpringMVC底层其实还是将请求中的参数剥离出来然后调用我们指定的这个对象Java Bean的setter方法来为同名的属性进行赋值当我们用的时候直接使用getter方法来用。有同学会问那你刚才写的User类中没有写getter和setter啊其实这里我偷了个懒使用的是lombok插件它会自动为我们生成getter和setter有兴趣的同学可以自己学一下非常简单。
那前端页面在传参的时候还是保证访问指定的url然后传递同名的参数即可没有其他的变化例如
http://localhost:8080/param/bean?id=1&name=golden3young&age=18&married=false
根据我上面的url请求来看后端Spring MVC在接收值的时候只能映射到id,name,age,married这4个参数的值给User对象的同名属性而其他的属性由于我没有传值所以都会保持初始默认值。
4.4.5 集合类型
Spring MVC不能直接通过形式参数列表的方式接收集合类型的参数想要Spring MVC自动将接收的参数转换成集合类型有两种方法
- 将List集合定义在Java Bean中
- 接收JSON格式数组
4.4.5.1 List集合
1、将集合定义在Java Bean中
如果需要接收集合类型的参数可以将集合放入一个对象中并且提供get/set方法才可以。推荐放入VO对象中进行封装进而使用对象类型来进行接收。
这种方式比较简单直接需要先创建一个Bean对象然后将我们需要接收的参数定义成这个Bean对象的一个属性这里要求参数的name需要与属性名完全一致这样Spring MVC底层就可以自动的将名称相同的参数和bean中对象的属性进行映射赋值我们只需要使用即可。
这里我们看一下后端的代码实现
创建一个User对象Java Bean
public class User {
private List<Integer> idList;
// 必须有 getter 和 setter方法
public void setIdList(List<Integer> idList){
this.idList = idList;
}
public List<Integer> getIdList(){
return this.idList;
}
}
将想要接收的参数名称定义成这个类对象的一个属性属性的类型需要定义成List集合形式至于泛型也是可以自动进行转换的可以根据自己的需求进行调整不单单是String类型。例如这里我们让它自动接收前端传来名为idList的多个参数然后自动将其存储在List集合中并且所有的idList参数都由字符串自动转成Integer类型。而我们只需要在Controller中定义一下Bean对象参数即可代码如下
@RequestMapping("/testList")
// 参数的对象名不重要只要是这个对象的参数名称和传递进来的参数key一样即可
public String testList(User user){
List<Integer> idList = user.getIdList();
idList.forEach(id -> System.out.println(id));
return "hello";
}
在Controller的处理方法中我们只需要在方法参数列表中声明一个User user对象将我们刚才写有idList属性的类对象引入即可不需要直接引入idList属性而是引用包含它的Bean 对象这样我们就可以直接在方法中使用user这个对象了通过getIdList()属性拿到转换后的List类型的参数。
前端页面在传递List类型参数的时候可以直接用form表单传递代码
<h2>测试List传参</h2>
<form action="${pageContext.request.contextPath}/param/testList" method="post">
ID
<input type="checkbox" name="idList" value="1" />1
<input type="checkbox" name="idList" value="2" />2
<input type="checkbox" name="idList" value="3" />3
<input type="checkbox" name="idList" value="4" />4
<br>
<input type="submit" value="提交" />
</form>
这里的代码是典型的发送List集合类型参数的前端代码使用的是form表单中的checkbox多选框由于多选框的值有多个并且name属性相同那么后端在接收起来时就不再是一对一的关系而是一个属性字段有多个值那么正好可以使用List集合来存放。
如果前端页面不想使用form表单提交那么还有第二种方式可以通过js代码来发送JSON格式的数组信息来进行数据的提交更加的灵活。
2、接收JSON格式数组
后端接收前端多个参数时不仅是通过Bean对象的属性来进行转换存储还可以直接接收无需借助任何的变量类型。但是这里就对前端传递参数的格式有了要求也就是说如果想在后端Controller中接收到前端传递的多个参数并转换成List集合形式那么就需要前端按照规范来进行传递那到底是什么规范呢那就是JSON格式。
先来看一下后端对于请求的处理方法
@RequestMapping(value = "/jsonToList", method = RequestMethod.POST)
@ResponseBody // 此注解与本例无关这个注解只是为了返回json格式的字符串
public String jsonToList(@RequestBody List<String> hobby){
for(String hob: hobby){
System.out.println(hob);
}
// 以下代码与本例无关
// Spring MVC 可以将json格式的字符串转换成json
return "{\"code\":200,\"msg\":\"SUCCESS\"}"; //JSON格式的字符串
}
详细剖析一下这个方法首先@RequestMapping注解定义value属性来指定拦截的路径为/jsonToListmethod属性来指定拦截请求的类型为POST类型其他的类型不作拦截。紧接着定义了一个名为jsonToList的处理方法参数列表中定义了一个List< String>类型的变量名为hobby这其实就是前端一会传来的参数名字就是hobby并且有多个统一存放在List< String>类型的hobby变量中但是这里非常重要的一点就是这里使用了 @RequestBody 注解只有加上这个注解才能告诉springmvc底层它需要自动为我们完成参数的转换映射赋值否则我们定义的List类型的hobby变量是没有办法将前端传来的参数装到自己肚子里的这个工作是springmvc底层帮我们做的需要的就是 @RequestBody 这个注解 将它加载参数声明前面即可。
那这样后端就可以直接在方法中使用变量了。我们看一下前端发送参数时的代码
const path = '${pageContext.request.contextPath}';
// 将Json数据转换成List
function testJsonToList(){
//定义一个json数组 new Array()
let hobby = ['唱歌','跳舞','喝酒','烫头']
$.ajax({
url: path + '/param/jsonToList',
type: 'post',
data: JSON.stringify(hobby), //数组也是js对象
dataType: 'json', //简写 application/json
contentType: 'application/json',
success: res=>{
alert('code:' + res.code + ', msg:' + res.msg)
}
})
}
以上代码片段是写在前端< script>标签中的即脚本代码。这里进行一下剖析以便大家理解。
定义了一个名为testJsonToList的方法其中首先定义了一个名为hobby的数组这个数组有4个值分别是唱歌、跳舞、喝酒、烫头。数组名为hobby与后端controller中我们定义的List名称完全一致只有这样才能完成自动映射同时需要注意的是这里我们将参数是封装在一个数组对象中格式是用 [ ] 将内容包裹在内。思考一下为什么要用数组进行存储呢? 下面会给出解释。
接下来发送ajax请求指定 url 到实现约定的地址
方法中的几个属性解释
- type 指定为POST类型
- data 为我们要传递的参数这里我们使用了 JSON.stringify() 方法这个发送是JSON所持有的作用是将一个js对象转换成JSON格式为什么要转换成JSON格式?这是本例在开头就说好的后端想要直接接收多个同名参数并存放到List集合中前端发送参数时就必须按规定发送规定就是JSON格式这里的JSON.stringify() 其作用就是帮助我们完成格式的转换换句话说就是我们可以直接在前端定义一个js支持的数组对象或者是Js对象都可以然后通过这个方法让它自动为我们完成转换当然我们也可以自己直接将内容写成JSON格式例如{“hobby:”:“唱歌”,“hobby”:“跳舞”,“hobby”:“喝酒”,“hobby”:“烫头”}但是这样书写如果字段比较多的时候会比较的繁琐所以不如直接写一个js中的数组对象然后使用 JSON.stringify() 来帮助我们完成格式的转换。
- dataType 为我们传递数据的格式毋庸置疑是JSON格式所以写成’application/json’当然简写就是’json‘.
- contentType 为指定编码格式。服务端通常是根据请求头headers中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码再对主体进行解析。使用 POST 提交数据方案包含了 Content-Type 和消息主体编码方式两部分。application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上现在越来越多的人把它作为请求头用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify服务端语言也都有处理 JSON 的函数。
- sucess 为回调函数此处代码与本例无关不作过多解释。
3、总结
发送JSON格式直接到后端Controller无非需要注意两点
① 后端Controller的执行方法中需要给参数列表加上@RequestBody注解并确保存储变量与前端发送参数的name保持一致。
② 前端发送的内容必须是JSON格式可以自己手写JSON格式也可以使用JSON.stringify来转换成JSON格式。ajax请求中需要修改dataType和contentType值都是’application/json’可以不使用简写。
4.4.5.2 Map集合
接受Map集合的参数同样也是有两种方法
- 将Map集合定义在Java Bean中
- 接收JSON格式对象
1、将集合定义在Java Bean中
有了List集合的介绍这里直接上代码先看一下Bean对象
public class User {
private Map<String, String> userMap;
public Map<String, String> getUserMap() {
return userMap;
}
public void setUserMap(Map<String, String> userMap) {
this.userMap = userMap;
}
}
定义一个Map类型的结合用于存储前端传过来的名为userMap的多个参数。
接下来看一下Controller中的处理方法
@PostMapping("/testMap")
public String testMap(User user){
Map<String, String> userMap = user.getUserMap();
System.out.println(userMap);
...
}
这里剖析一下@PostMapping注解为指定只拦截POST请求同时拦截的Url为 /testMap执行方法名成testMap参数列表中直接定义User user对象与List集合完全相同。于是在方法中就可以直接使用user对象使用getUserMap()方法就可以拿取到userMap这个对象中的内容我们使用System.out.println()将其遍历打印出来。
接下来看一下前端页面的传递参数代码
const path = '${pageContext.request.contextPath}';
function testMap(){
$.ajax({
url: path + '/param/testMap',
type: 'POST',
data: "userMap['id']=100&userMap['name']=zs", // Map<String, String> {id=1,name=zs}
dataType: 'text',
success: function(res){
alert(res)
},
error: function (err) {
alert(err)
}
})
}
这里对上述代码片段进行一下剖析在前端< script >代码中定义名为testMap的方法
- url: 发送ajax请求到指定的Url与后台的controller中的testMap方法对应。
- type 指定请求类型为POST
- data: 指定参数传递的内容这里的格式需要大家记住Map集合由于是键值对类型的格式所以在发送中需要采用 name[‘key’]=value的格式进行发送name为当前属性的name值在这里也就是userMap对应了后台controller方法中的Map集合类型的userMap变量[‘id’]为第一个Key对应的value就是100而第二个key是’name’ 对应的值为 zs这里需要注意的是如果参数大于1个那么参数直接需要用&符号进行连接最终将所有的内容写在双引号中。
- dataType: 此时的数据类型仅仅为text文本格式
2、接收JSON格式对象
除了上述通过规范传递内容的格式来发送传递内容外还可以将发送内容的格式定义为JSON格式。JSON格式就不需要我们手动的规范传递的参数格式了也就不再需要自己去写 这种data: "userMap['id']=100&userMap['name']=zs",代码因为一旦字段多的时候写起来非常的麻烦。
接下来看一下后台Controller的代码
@PostMapping("/jsonToMap")
public User jsonToMap(@RequestBody Map<String, Object> map){
// {id: 1, hobby: ['踢球','跳舞'] user: {id:1, name:'zs'}} -- json格式
System.out.println(map);
...
}
此时我们在参数列表中定义了 一个Map集合名为mapkey的泛型为Stringvalue的泛型为Object也就是说value的类型可以是任意类型。其实这里的变量名称map可以是任意的名称不需要再与前端保持一致了因为这里的映射规则是key和value的键值对了。Spring MVC就会将从前端传过来的JSON字符串解析成Map类型的对象赋值给方法参数。
接下来看一下前端页面的代码
const path = '${pageContext.request.contextPath}';
//将json数据转换成Map
function testJsonToMap(){
let obj = {id: 1, hobby: ['唱歌', '跳舞', '打游戏'], user: {id: 1, name:'四哥'}}
$.ajax({
url: path + '/param/jsonToMap',
type: 'POST',
data: JSON.stringify(obj),
dataType: 'json',
contentType: 'application/json',
success: res=>{
alert(res.id + res.name + res.age)
}
})
}
可以看出由于此时后端接收参数的类型变成了Map集合类型也就是键值对类型需要有一个key和一个value来组合对应。那么前端拥有这种格式的类型数组已经无法满足了因为数组的值是一个整体没有办法体现出一个个键值对Key=value此时前端唯一能有这种格式的就剩下Js对象了使用js对象就可以模拟出key=value的格式如上代码中所写let obj = {id: 1, hobby: ['唱歌', '跳舞', '打游戏'], user: {id: 1, name:'四哥'}} 定义了一个js对象名为obj这个js对象的键值对之间都用逗号隔开第一对为id:1key为idvalue为1第二对key为hobbyvalue为数组对象 [‘唱歌’, ‘跳舞’, ‘打游戏’]第三对key为uservalue为js对象{id: 1, name:‘四哥’}。
最终整个对象被{ }包裹由此可以看出此时参数传递对应的关键所在已经变成了key和value的对应此时的key在后端会被自动映射成String类型而value则被映射成Object类型如果我们需要将Value的Object类型进行进一步的转换可以在后端使用强制类型转换非常的方便。
同时此时我们也不需要自己手写key=value的格式数据了直接使用js对象即可。
接下来就是发送ajax请求
- url 指定到规定的请求地址
- type 必须修改成post
- data 传递的参数这里使用JSON.stringify()来帮助我们将js对象转换成json格式的数据
- dataType 数据的类型当然是’json’这是简写。
- contentType 请求头中规定的编译格式为’application/json’
- success 回调函数
3、总结
发送JSON格式直接到后端Controller无非需要注意两点
① 后端Controller的执行方法中需要给参数列表加上@RequestBody注解并确保存储变量与前端发送参数的name保持一致。
② 前端发送的内容必须是JSON格式可以自己手写JSON格式也可以使用JSON.stringify来转换成JSON格式。ajax请求中需要修改dataType和contentType值都是’application/json’可以不使用简写。
4.4.6 JSON 格式
这里所谓的JSON格式传递参数其实已经在上一节介绍过了这里做一下总结。
- JSON格式传递参数我们可以手写JSON格式或者是将js中的数据、对象进行转换采用的是JSON.stringify()方法可以节省我们手写的繁琐。
- http协议的请求方法使用posthttp协议的请求头的contentType是application/json
- Spring MVC底层在帮我们进行JSON格式的解析和与Java属性之间的映射Spring MVC使用jackson作为默认的json转换器所以需要我们引入maven依赖。总共有3个jar包jackson-core.jar jackson-databind.jarjackson-annotation.jar。而jackson-databind.jar依赖于其他两个jar于是我们在引入的时候只需要引入jackson-databind.jar这一个jar包即可另外两个自动引入。
- @RequestBody注解可以将json格式数据转换成JavaBean、list、map。@ResponseBody注解可以将Object类型返回值转成json格式的数据。
4.4.7 获取URL中的参数
URL参数或者叫请求路径参数是基于URL模板获取到的参数例如/user/{userId}是一个URL模板(URL模板中的参数占位符是{})实际请求的URL为/user/1那么通过匹配实际请求的URL和URL模板就能提取到userId为1。
在Spring MVC中URL模板中的路径参数叫做PathVariable对应注解@PathVariable对应的参数处理器为PathVariableMethodArgumentResolver。
注意一点是@PathVariable的解析是按照value(name)属性进行匹配和URL参数的顺序是无关的。举个简单的例子
后台的控制器如下
@GetMapping(value = "/user/{name}/{age}")
// @PathVariable注解中的value需要和@GetMapping中URL的占位名一致
public String findUser1(@PathVariable(value = "age") Integer age,
@PathVariable(value = "name") String name) {
String content = String.format("name = %s,age = %d", name, age);
log.info(content);
return content;
}
这种用法被广泛使用于Representational State Transfer(REST)的软件架构风格个人觉得这种风格是比较灵活和清晰的(从URL和请求方法就能完全理解接口的意义和功能)。下面再介绍两种相对特殊的使用方式。
4.4.7.1 带条件的URL参数
其实路径参数支持正则表达式例如我们在使用/sex/{sex}接口的时候要求sex必须是F(Female)或者M(Male)那么我们的URL模板可以定义为/sex/{sex:M|F}代码如下
@GetMapping(value = "/sex/{sex:M|F}")
public String findUser2(@PathVariable(value = "sex") String sex) {
log.info(sex);
return sex;
}
只有/sex/F或者/sex/M的请求才会进入findUser2控制器方法其他该路径前缀的请求都是非法的会返回404状态码。
再来一个例子
/**
* @PathVariable注解的作用就是从URL里面读取参数值GET请求方式
*
* @PathVariable注解一般用于只传递一个参数的场景当然也可以传递多个参数。
*
* @param param1 占位符{}添加了正则表达式限定5位数值如果传递过来的参数不合要求则不会执行方法的代码。
* @param param2
* @return
*/
@GetMapping("/testGet2_1/{param1:[0-9]{5}}/{param2}")
public String testGet2_1(@PathVariable String param1,@PathVariable String param2){
System.out.println("param1:"+param1);
System.out.println("param2:"+param2);
return param1+","+param2;
}
这里仅仅是介绍了一个最简单的URL参数正则表达式的使用方式更强大的用法可以自行摸索。
4.4.7.2 @MatrixVariable的使用
MatrixVariable也是URL参数的一种对应注解@MatrixVariable不过它并不是URL中的一个值(这里的值指定是两个"/"之间的部分)而是值的一部分它通过";"进行分隔通过"="进行K-V设置。说起来有点抽象举个例子假如我们需要打电话给一个名字为doge性别是男分组是码畜的程序员GET请求的URL可以表示为/call/doge;gender=male;group=programmer我们设计的控制器方法如下
@GetMapping(value = "/call/{name}")
public String find(@PathVariable(value = "name") String name,
@MatrixVariable(value = "gender") String gender,
@MatrixVariable(value = "group") String group) {
String content = String.format("name = %s,gender = %s,group = %s", name, gender, group);
log.info(content);
return content;
}
当然如果你按照上面的例子写好代码尝试请求一下该接口发现是报错的400 Bad Request - Missing matrix variable 'gender' for method parameter of type String。这是因为@MatrixVariable注解的使用是不安全的在Spring MVC中默认是关闭对其支持。要开启对@MatrixVariable的支持需要设置RequestMappingHandlerMapping#setRemoveSemicolonContent方法为false
@Configuration
public class CustomMvcConfiguration implements InitializingBean {
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Override
public void afterPropertiesSet() throws Exception {
requestMappingHandlerMapping.setRemoveSemicolonContent(false);
}
}
除非有很特殊的需要否则不建议使用@MatrixVariable。
4.5 Spring MVC向前端页面传递数据
Spring MVC可以通过request域从后台向前端传递值
后台向前端通过request域传递值的方式
- Servlet api 中的 HttpServletRequest对象
- ModelAndView 对象
- ModelMap 对象
- Model 对象
- Map 集合
4.5.1 Servlet api 中的 HttpServletRequest对象
代码
//servlet请求
@RequestMapping("/servlet")
// 返回值是String,返回的是视图名称
public String servlet(HttpServletRequest request){
String name = request.getParameter("name");
System.out.println("name:" + name);
// 以下为本例代码
request.setAttribute("result", "hello " + name);
// 要跳转到的视图页面
return "hello";
}
这种方式是最传统的方式直接向request域中进行参数的传递通过setAttribute()方法来指定key和value即可在前端页面hello.jsp中直接获取到request域中的内容。如何获取代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h2>${result}</h2>
</body>
</html>
4.5.2 ModelAndView 对象
方法中返回 ModelAndView 对象此对象中设置模型数据并指定视图。它有两个常用方法
- addObject(String key, Object value)设置共享数据的 key 和 value。
- addObject(Object value)设置共享数据的 valuekey 为该 value 类型首字母小写。
代码片段
// Controller中的方法返回值设置为ModelAndView
public ModelAndView modelAndView(){
ModelAndView mv = new ModelAndView();
// 向request域传值
mv.addObject("result","Hello spring mvc");
// 设置视图名称 prefix + viewName + suffix (/jsps/hello.jsp)
mv.setViewName("hello");
// 返回ModelAndView会将要传递给的数据和视图页面一并返回用户就会跳转到相应的页面看到返回的数据
return mv;
}
这是Spring MVC独有的ModelAndView对象通过声明一个ModelAndView类型的对象就可以使用这个对象来传值或者是指定跳转的页面路径。采用addObject()方法来规定key和value即可在前端页面中根据key拿取到对应value的值。通过采用setViewName()方法来传入一个页面的名称Spring MVC底层的视图解析器就会将ModelAndView中的view视图进行前缀和后缀的拼接加工并给最终的页面跳转路径。当然这里执行方法的返回类型需要是ModelAndView类型方法最终需要将mv对象return 。
参数列表中也可以提前声明ModelAndView对象可直接在方法中使用。如
public ModelAndView modelAndView(ModelAndView mv){
// 向request域传值
mv.addObject("result","Hello spring mvc");
// 设置视图名称 prefix + viewName + suffix (/jsps/hello.jsp)
mv.setViewName("hello");
return mv;
}
4.5.3 ModelMap 对象
@RequestMapping("/simple")
public String simple(int id, String name, ModelMap modelMap){
// modelMap 可以向request域传值【ModelAndView,Request,ModelMap】
System.out.println("id:" + id);
System.out.println("name:" + name);
// 以下为本例的代码
modelMap.addAttribute("result", "Hello " + name + "," + id);
return "hello";
}
这里的ModelMap对象与request对象相似可以采用addAttribute()方法来传递参数向ModelMap对象中添加key和value其实就是在向request域中添加key和value只不过ModelMap是又将request进行了一层封装原因是彻底与Servlet的内容分离仅用Spring MVC的对象就可以完成对request域的赋值。
视图解析器会将modelMap中的数据解析到视图页面上。
4.5.4 Model 对象
返回 String 类型使用广泛此时如果我们需要共享数据那么就需要用到HttpServlet对象Spring帮我们封装好了一个对象Model 。组合使用用其往作用域或模型中存入数据。
Model类型参数的处理器是ModelMethodProcessor实际上处理此参数是直接返回ModelAndViewContainer实例中的Model(ModelMap类型)因为要桥接不同的接口和类的功能因此回调的实例是BindingAwareModelMap类型此类型继承自ModelMap同时实现了Model接口。举个例子
@RequestMapping("/bean")
public String testBean(User user, Model model){
System.out.println(user);
// 使用Model对象向request域传值
model.addAttribute("result", "hello " + user.getName());
return "hello";
}
Model对象与ModelMap对象基本上是一样的。ModelMap或者Model中添加的属性项会附加到HttpRequestServlet中带到页面中进行渲染。
4.5.5 Map 集合
我们先来简单介绍一下它的常见用法。
@PostMapping("/array")
public String array(Map<String, String> map){
//使用 java.util.Map 向request域传值
map.put("result","测试数组传参");
return "hello";
}
在执行方法的参数列表中声明一个Map集合并向其中put()进key和value就可以被Spring MVC解析成向request域中传递值。底层就是使用request.setAttribute()在赋值。而我们无需引入request对象仅仅使用一个简单的map集合就可以完成对request对象的操作。彻底与Servlet解耦。
这个Map的数据就会被传递给视图在页面上显示出来。
下面我们再详细讲一下Map。Map类型参数的范围相对比较广对应一系列的参数处理器注意区别使用注解的Map类型和完全不使用注解的Map类型参数两者的处理方式不相同。下面列举几个相对典型的Map类型参数处理例子。
1、不使用任何注解的Map<String,Object>参数
这种情况下参数实际上直接回调ModelAndViewContainer中的ModelMap实例参数处理器为MapMethodProcessor往Map参数中添加的属性将会带到页面中。
2、使用@RequestParam注解的Map<String,Object>参数
这种情况下的参数处理器为RequestParamMapMethodArgumentResolver使用的请求方式需要指定ContentType为x-www-form-urlencoded不能使用application/json的方式
控制器代码为
@PostMapping(value = "/map")
public String mapArgs(@RequestParam Map<String, Object> map) {
log.info("{}", map);
return map.toString();
}
3、使用@RequestHeader注解的Map<String,Object>参数
这种情况下的参数处理器为RequestHeaderMapMethodArgumentResolver作用是获取请求的所有请求头的Key-Value。
4、使用@PathVariable注解的Map<String,Object>参数
这种情况下的参数处理器为PathVariableMapMethodArgumentResolver作用是获取所有路径参数封装为Key-Value结构。
五、文件上传与下载
5.1 文件上传
回顾之前使用 Servlet3.0 来解决文件上传的问题编写上传表单POST、multipart/form-data还在处理方法 doPost 中编写解析上传文件的代码。但是在Spring MVC是可以帮我们简化文件上传的步骤和代码。
5.1.1 编写表单
注意请求数据类型必须是multipart/form-data且请求方式是POST。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
文件:<input type="file" name="pic"><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
5.1.2 修改web.xml
我们可以在web.xml中指定上传文件的大小。
<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:mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!-- 文件上传配置 -->
<multipart-config>
<!--<max-file-size>标签用于设置单个文件的最大值单位为字节这里设置为50M-->
<max-file-size>52428800</max-file-size>
<!--<max-request-size>标签用于设置上传文件的总大小单位为字节这里设置为50M-->
<max-request-size>52428800</max-request-size>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
5.1.3 配置上传解析器
在springmvc.xml中配置上传解析器。要想使用Spring MVC中multipartfile接收客户端上传的文件就必须配置文件上传解析器且解析的id必须为multipartResolver。
<!--将文件上传解析器装配到Spring中-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--控制文件上传大小单位字节 默认没有大小限制 这里是2-->
<property name="maxUploadSize" value="2097152"/>
</bean>
5.1.4 编写上传控制器
@Controller
public class UploadController {
// Spring 容器存在 ServletContext 类型的对象所以定义好 ServletContext 类型字段贴@Autowired 注解即可获取到
@Autowired
private ServletContext servletContext;
@RequestMapping("/upload")
public ModelAndView upload(Part pic) throws Exception {
System.out.println(pic.getContentType()); // 文件类型
System.out.println(pic.getName()); // 文件参数名
System.out.println(pic.getSize()); // 文件大小
System.out.println(pic.getInputStream()); // 文件输入流
// FileCopyUtils.copy(in, out)一个 Spring 提供的拷贝方法
// 获取项目 webapp 目录下 uploadDir 目录的绝对路径
System.out.println(servletContext.getRealPath("/uploadDir"));
return null;
}
}
5.1.5 批量文件上传MultipartFile集合
批量文件上传的时候我们一般需要接收一个MultipartFile集合可以有两种选择
- 1、使用MultipartHttpServletRequest参数直接调用getFiles方法获取MultipartFile列表。
- 2、使用@RequestParam注解修饰方法参数中的MultipartFile列表参数处理器是RequestParamMethodArgumentResolver其实就是第一种的封装而已。
控制器方法代码如下
@PostMapping(value = "/parts")
public String partArgs(@RequestParam(name = "file") List<MultipartFile> parts) {
log.info("{}", parts);
return parts.toString();
}
5.2 文件下载
文件下载将服务器上的文件下载到当前用户访问的计算机的过程称之为文件下载。
5.2.1 编写下载控制器
下载时必须设置响应的头信息指定文件以何种方式保存另外下载文件的控制器不能存在返回值代表响应只用来下载文件信息。
/**
* 测试文件下载
* @param fileName 要下载文件名
* @return
*/
@RequestMapping("download")
public String download(String fileName, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取下载服务器上文件的绝对路径
String realPath = request.getSession().getServletContext().getRealPath("/down");
// 根据文件名获取服务上指定文件
FileInputStream is = new FileInputStream(new File(realPath, fileName));
// 获取响应对象设置响应头信息
response.setHeader("content-disposition","attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
// 获取响应输出流
ServletOutputStream os = response.getOutputStream();
IOUtils.copy(is,os);
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
return null;
}
六、Spring MVC常用注解及其作用
下面来总结一下Spring MVC的常用注解。
@Controller标识这个类是一个控制器
@RequestMapping给控制器方法绑定一个uri
@ResponseBody将java对象转成json并且发送给客户端
@RequestBody将客户端请求过来的json转成Java对象
@RequestParam当表单参数和方法形参名字不一致时做一个名字映射
@PathVarible用于获取uri中的参数比如user/1中1的值
Rest风格的新api
@RestController相当于@Controller+ @ResponseBody
@GetMapping @DeleteMapping @PostMapping @PutMapping接收四种请求类型的注解
其他注解
@SessionAttribute声明将什么模型数据存入session
@CookieValue获取cookie值
@ModelAttribute将方法返回值存入model中
@HeaderValue获取请求头中的值
七、总结
7.1 为什么要使用Spring MVC
Spring MVC是一种基于Java实现了Web MVC设计模式请求驱动类型的轻量级Web框架即使用了MVC架构模式的思想将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型框架的目的就是帮助我们简化开发Spring MVC也是要简化日常Web开发。处理业务数据的对象和显示业务数据的视图之间存在紧密耦合用MVC也是为了解耦。
7.2 Spring MVC的特点
- 清晰的角色划分控制器controller、验证器validator、 命令对象command object、表单对象formobject、模型对象model object、 Servlet分发器DispatcherServlet、处理器映射handler mapping、视图解析器view resolver等。每一个角色都可以由一个专门的对象来实现。
- 强大而直接的配置方式将框架类和应用程序类都能作为JavaBean配置支持跨多个context的引用例如在web控制器中对业务对象和验证器validator的引用。
- 可适配、非侵入可以根据不同的应用场景选择合适的控制器子类 simple型、command型、form型、wizard型、multi-action型或者自定义而不是从单一控制器 比如Action/ActionForm继承。
- 可重用的业务代码可以使用现有的业务对象作为命令或表单对象而不需要去扩展某个特定框架的基类。
- 可定制的绑定binding 和验证validation比如将类型不匹配作为应用级的验证错误 这可以保存错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中你只能使用字符串表单对象需要手动解析它并转换到业务对象。
- 可定制的handlermapping和view resolutionSpring提供从最简单的URL映射 到复杂的、专用的定制策略。与某些webMVC框架强制开发人员使用单一特定技术相比Spring显得更加灵活。
- 灵活的model转换在Springweb框架中使用基于Map的 键/值对来达到轻易地与各种视图技术的集成。
- 可定制的本地化和主题theme解析支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity不需要额外的中间层等等。
- 简单而强大的JSP标签库SpringTag Library支持包括诸如数据绑定和主题theme 之类的许多功能。
- JSP表单标签库在Spring2.0中引入的表单标签库使得在JSP中编写 表单更加容易。
- Spring Bean的生命周期可以被限制在当前的HTTP Request或者HTTP Session。
7.3 Spring MVC的优点
- 让我们能非常简单的设计出干净的Web层和薄薄的Web层
- 进行更简洁的Web层的开发
- 天生与Spring框架集成如IoC容器、AOP等可以和Spring框架无缝整合
- 提供强大的约定大于配置的契约式编程支持
- 非常灵活的数据验证、格式化和数据绑定机制
- 注解式开发更高效。
- 支持Restful风格
- Spring MVC是一个典型的轻量级MVC框架在整个MVC架构中充当控制器框架相对于之前的struts2框架SpringMVC运行更快其注解式开发更高效灵活。
7.4 Spring MVC和Struts2的对比
框架机制Spring MVC的入口是servlet而Struts2是filter。
拦截机制
Struts2
- Struts2框架是类级别的拦截每次请求就会创建一个Action和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype否则会出现线程并发问题然后通过settergetter吧request数据注入到属性
- 一个Action对应一个requestresponse上下文在接收参数时可以通过属性接收说明属性参数是让多个方法共享的
- Action的一个方法可以对应一个url而其类属性却被所有方法共享这也就无法用注解或其他方式标识其所属方法了。
SpringMVC:
- SpringMVC是方法级别的拦截一个方法对应一个Request上下文所以方法直接基本上是独立的独享requestresponse数据。而每个方法同时又何一个url对应参数的传递是直接注入到方法中的是方法所独有的。处理结果通过ModeMap返回给框架
- 在Spring整合时Spring MVC的Controller Bean默认单例模式Singleton所以默认对所有的请求只会创建一个Controller有应为没有共享的属性所以是线程安全的如果要改变默认的作用域需要添加@Scope注解修改
Struts2有自己的拦截Interceptor机制SpringMVC这是用的是独立的Aop方式这样导致Struts2的配置文件量还是比SpringMVC大。
性能方面
SpringMVC实现了零配置由于SpringMVC基于方法的拦截有加载一次单例模式bean注入。而Struts2是类级别的拦截每次请求对应实例一个新的Action需要加载所有的属性值注入所以Spring MVC开发效率和性能高于Struts2。
配置方面
Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高当然Struts2也可以通过不同的目录结构和相关配置做到Spring MVC一样的效果但是需要xml配置的地方不少
Spring MVC可以认为已经100%零配置。
设计思想
Struts2更加符合OOP的编程思想 Spring MVC就比较谨慎在servlet上扩展。
集成方面
Spring MVC集成了Ajax。
注意Spring MVC是单例模式的框架但它是线程安全的因为Spring MVC没有成员变量所有参数的封装都是基于方法的属于当前线程的私有变量因此是线程安全的框架所以效率高。
struts action是多例的。所以可以使用成员变量获取参数。所以效率低。