一、Sping MVC的介绍
1. 使用Front(前端)设计模式
Front(前端)设计模式就是有一个前端(不是前端专业那个前端,是最前面的意思)统一入口,在统一入口根据请求url调用自己的编写的普通方法。
这样带来的好处是:
-
只需要在一个Servlet中编写获取容器Bean的代码,减少了代码冗余。
-
不需要为每个控制器都创建一个类,而是可以在一个普通Java类中提供普通实例方法代表以前servlet中的service方法。
-
因为可以自己编写普通Java类,这类可以放入到Spring容器中,注入Service更方便。
-
同时因为是自己编写的Java,所以可以进行一些封装,对其他操作进行简化。
2. Spring MVC介绍
Spring MVC 虽然在平时认为是一个独立的框架。但其本质为Spring 框架的一个扩展 ,可以认为Spring MVC属于Spring Framework的二级子项目。Spring MVC是基于Front设计模式;EmpController在Spring MVC称为控制器类(Handler),里面的方法称为:控制单元(HandlerMethod)
M:在模型层包含:数据校验。
V:在视图层包含:国际化、标签库。
C:在控制层包含的功能就更多了:转发重定向、参数、拦截器、作用域等。
3. Spring中的父子容器问题
因为Spring MVC属于Spring的子框架,所以Spring MVC中可以使用Spring框架的全部内容。
Spring MVC子容器可以调用Spring 父容器的全部内容。但是Spring父容器不能调用Spring MVC子容器内容。
二、Spring MVC环境搭建
1. 创建项目并添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>springmvc1</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- 依赖了Spring框架核心功能的5个依赖以及Spring整合Web的依赖spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/bjsxt</path>
<port>8081</port>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 创建Spring MVC配置文件
在src/main/resources中新建Spring MVC框架配置文件springmvc.xml。这个文件的名称是随意的,只要和web.xml中配置对应上就可以。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 扫描控制器类,千万不要把service等扫描进来,也千万不要在Spring配置文件扫描控制器类所在包 -->
<context:component-scan base-package="com.bjsxt.controller"></context:component-scan>
<!-- 让Spring MVC的注解生效 不要引错xsd-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
3. 编写web.xml内容
web.xml的配置是为了让前端控制器DispatcherServlet生效。并且加载Spring MVC的配置文件。
<?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">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 参数名称必须叫做:contextConfigLocation。单词和大小写错误都导致配置文件无法正确加载 -->
<param-name>contextConfigLocation</param-name>
<!-- springmvc.xml 名称自定义,只要和后面创建的文件名称对应就可以了。 -->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Tomcat启动立即加载Servlet,而不是等到访问Servlet才去实例化DispatcherServlet -->
<!-- 配置上的效果:Tomcat启动立即加载Spring MVC框架的配置文件-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- /表示除了.jsp结尾的uri,其他的uri都会触发DispatcherServlet。此处前往不要写成 /* -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
4. 创建控制器类
Spring MVC自定义控制器类都是以Controller结尾。
这些类都是放在controller的包中。
控制单元方法的访问权限修饰符没有强制要求,但是多写成public的。
控制单元的方法名没有要求,只要满足Java方法名定义要求就可以。
@Controller// 放入到Spring MVC容器中
public class FirstController {
/*
* 官方标准写法:
* 返回值是ModelAndView,对象中存储跳转资源路径及作用域值
*/
// 当前方法的映射路径
@RequestMapping("/first")
public ModelAndView test1(){
ModelAndView modelAndView = new ModelAndView("first.jsp");
return modelAndView;
}
/*
* 简化写法(平时使用的方式)
* 返回值是String,表示跳转的资源路径
*/
@RequestMapping("/first2")
public String test2(){
return "first.jsp";
}
}
三、@RequestMapping注解
@RequestMapping注解可以写在控制器类上,也可以写在控制单元方法上。
如果写在类上,表示当前类所有控制单元的映射路径,都以指定路径开头。
如果写在方法上,表示当前方法的映射路径。最终访问这个控制单元的映射路径为:类上@RequestMapping映射路径+方法上@RequestMapping映射路径。
1. 所有属性总览
2. value属性
value:定义映射路径。URL中出现指定映射路径时会执行当前控制单元。支持一个方法多个映射路径。value属性名可以省略不写,且Java的注解中,如果属性是数组类型,且取值只有一个时,{}可以省略不写。所以一共有四种写法。
取值前面的/
表示映射到项目根目录,可以省略不写,但是从规范上建议写上。
@RequestMapping("/first3")
public String first3(){
return "first.jsp";
}
@RequestMapping({"/first4","first5"})
public String first4(){
return "first.jsp";
}
3. name属性
name:给控制单元定义一个名称。可以理解name是控制单元的注释。
@RequestMapping(value = "/testName",name = "测试下name属性,给程序员看的")
public String testName(){
return "first.jsp";
}
4. path属性
path属性和value属性使用方式是相同的,都是设置控制单元的映射路径。
@RequestMapping(path = "/testPath")
public String testPath(){
return "first.jsp";
}
5. method属性
method属性类型是RequestMethod[],RequestMethod是枚举类型,支持HTTP协议中绝大多数请求类型。
当设置了method属性后,表示只有指定类型请求方式才能访问这个控制单元方法,其他的请求方式访问时,响应会出现405状态码。
// 请求方式只能是DELETE和POST类型。
@RequestMapping(value = "/testMethod",method = {RequestMethod.DELETE,RequestMethod.POST})
public String testMethod(){
return "first.jsp";
}
简写:
@PostMapping("/first")
等效于 @RequestMapping(value = "/first",method = RequestMethod.POST)
@GetMapping("/first")
等效于 @RequestMapping(value = "/first",method = RequestMethod.GET)
@DeleteMapping("/first")
等效于 @RequestMapping(value = "/first",method = RequestMethod.DELETE)
@PutMapping("/first")
等效于 @RequestMapping(value = "/first",method = RequestMethod.PUT)
@PatchMapping("/first")
等效于 @RequestMapping(value = "/first",method = RequestMethod.PATCH)
6. params属性
params属性类型是String[],表示请求中必须包含指定名称的请求参数。
@RequestMapping(value="/testParam",params = {"name"})
public String testParam(){
return "first.jsp";
}
请求中没有包含指定类型参数,响应会出现400状态码。并且明确提示在实际的请求参数中没有明确设置name属性。
7. headers属性
@RequestMapping(value="/testHeaders",headers = "aa")
public String testHeaders(){
return "first.jsp";
}
如果请求头中没有指定的请求头参数,浏览器会报404。
8. consumes属性
consumers表示处理请求内容(Content-Type)的类型,平时多不设置,由Spring MVC自动判断。
9. produces属性
produces类型是String[],作用是设置@ResponseBody注解的响应内容类型。且仅当请求头中Accept中包含的值才生效。
produces = "text/html;charset=utf-8"解决中文乱码问题
四、映射路径
1. 映射路径介绍
映射路径无论是在Servlet中还是在Spring MVC中,都表示:当URL中出现指定路径时会执行Servlet的方法或执行Spring MVC的控制单元。
2. 多级路径
在Spring MVC 的映射路径也支持多层写法
3.1 多层路径中最优写法
只需要在返回值中使用绝对路径就可以减少出错的情况。
跳转时 / 表示项目根目录,也就是webapp目录的根目录。
@RequestMapping("/test/test2")
public String test2(){
return "/first.jsp";
}
4. Ant风格的映射路径
在Spring MVC中支持Ant风格的映射路径写法。所谓的Ant风格就是支持三种特殊的符号。
符号 | 解释 |
---|---|
? | 匹配任意单字符 |
* | 匹配0或者任意数量的字符 |
** | 匹配0或者更多数量的目录 |
使用Ant的特殊符号时,表示模糊匹配。可能出现客户端发送过来的URL能匹配上多个映射路径,这时的优先级为:
固aa1) > ?形式(aa?) > 一个*
号(/*
) > (/**)
形式
// 优先级最高
@RequestMapping("/aa1")
public String testAnt1(){
System.out.println("bjsxt");
return "/first.jsp";
}
// 优先级低于bjsxt1。总长度为6,bjsxt开头,后面跟个任意内容符号
@RequestMapping("/aa?")
public String testAnt2(){
System.out.println("bjsxt");
return "/first.jsp";
}
// 优先级低于?。一层路径,任意内容
@RequestMapping("/*")
public String testAnt3(){
System.out.println("11111");
return "/first.jsp";
}
// 优先级低于*。任意层路径
@RequestMapping("/**")
public String testAnt4(){
System.out.println("22222");
return "/first.jsp";
}
五、Spring MVC 中的转发和重定向
1. 转发和重定向复习
转发和重定向 都是出现在资源之间相互跳转的。
两者区别:
(1)转发为一次请求,tomcat内部跳转。重定向为多次请求,不是tomcat内部跳转。
(2)转发是一次请求,无论服务器内部转发多少次,请求对象都不变。所以转发可以共享请求域的值。同时对于客户端浏览器URL是不变的。
重定向后需要客户端重新发起请求,和重定向之前不是一个请求。所以重定向后不能获取到之前设置在请求域的值。同时客户端浏览器URL是改变的。
(3)转发只能跳转到当前项目内部资源。重定向可以跳转到外部资源。例如:从自己的项目中跳转到百度应该使用重定向。
(4)转发时资源路径如果是绝对路径,第一个 / 表示当前项目根目录。
重定向时资源路径时绝对路径,第一个 / 表示 Tomcat 的 webapps目录,即:当前项目的上层目录。
2. Spring MVC中的转发和重定向
在Spring MVC框架中,默认情况下都使用转发进行寻找资源。
@RequestMapping("/test/test2")
public String test2(){
return "/first.jsp";
}
在资源路径前面添加forward: 表示转发。因为写不写forward:都是转发,所以为了代码写起来简单一些,多省略forward:
@RequestMapping("/test/test2")
public String test2(){
return "forward:/first.jsp";
}
如果希望使用重定向跳转到其他资源,只能在资源路径最前面明确添加redirect:
3. Spring MVC转发和重定向时绝对路径
在Spring MVC中无论是转发还是重定向,使用绝对路径时/都表示项目根目录。
六、WEB-INF目录资源
可以把资源放入到WEB-INF目录中。因为在Java Web项目中规定:WEB-INF中资源是不允许被客户端直接访问,需要先访问控制器,通过控制器的转发来访问这些资源。这种目录结构也更加负责MVC开发模式,更能体现出控制器的作用。
需要提供一个控制单元方法,转发到JSP页面中。
@RequestMapping("/showSuiyi")
public String showSuiyi(){
return "/WEB-INF/page/suiyi.jsp";
}
分析:
项目中所有的JSP、CSS、JavaScript、图片都放入到WEB-INF中,那所有的资源都必须先执行控制器。实现起来更加复杂了。但是从项目角度上却是更加安全了 。
七、视图解析器
1. 视图解析器和视图
Spring MVC的控制单元 支持 ModelAndView、String 等多种类型的返回值,但无论控单元的返回值是哪种类型,Spring MVC 内部最终都会将它们封装成一个 ModelAndView 对象,它由 model(模型数据)和 view(逻辑视图名)两部分组成,所以 Spring MVC 需要借助 ViewResolver(视图解析器)将 逻辑视图名解析为真正的 View 视图对象,然后才能响应给客户端展示。
Spring MVC 定义了ViewResolver
和View
接口:
-
ViewResolver
视图解析器视图解析器用来解析逻辑视图,将其解析成真正的视图对象。
SpringMVC 提供了一个视图解析器的接口 ViewResolver,所有具体的视图解析器必须实现该接口。
2.View
解决数据在移交给特定视图技术之前的准备工作。视图技术不有Jsp,还有Thymeleaf,freemaker等,当我们使用不同的视图技术的时候,需要我们编写不同的视图代码来实现将数据显示给用户,SpringMVC为了解决这个问题,由ViewResolver来解析单元方法的处理结果,创建对应的View的对象,然后再调用View的实现对象来完成相关的操作。
2. 自定义视图解析器
@RequestMapping("/showSuiyi")
public String showSuiyi(){
return "/WEB-INF/page/suiyi.jsp";
}
对于这种,项目结构比较固定,页面按照一定规则放在特定位置,且返回值内容比较长时,就可以配置视图解析器。在springmvc.xml进行配置
<!--配置自定义试图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/page/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
当配置了视图解析器后,控制单元返回值就不需要写前缀和后缀的内容了,写起来更加简单。
@RequestMapping("/showSuiyi")
public String showSuiyi(){
return "suiyi";// 由视图解析器拼接:prefix + suiyi + suffix。具体:/WEB-INF/page/ + suiyi + .jsp
}
3. 自定义视图解析器下跳转到其他控制器
如果控制单元执行完,并不希望跳转到视图,而是跳转到控制器,这时需要在返回值前面明确添加forward:或redirect: ,这样就不走视图解析器了。
@RequestMapping("/showSuiyi3")
public String showSuiyi3(){
return "suiyi3";// 跳转到 /WEB-INF/page/suiyi3.jsp
}
@RequestMapping("/demo")
public String demo(){
return "forward:/showSuiyi3"; // 不走视图解析器了。跳转到/showSuiyi3控制器单元
}
八、静态资源放行
1. 静态资源放行
Spring MVC 支持静态资源配置,当URL满足指定路径要求时不再去找控制单元,而是直接转发到特定路径中静态资源。
需要在springmvc.xml中配置静态资源放行
<!--配置静态资源放行-->
<!--mapping:当URI是什么样格式时,不再执行控制器,而是寻找静态资源。 ** 是通配符,表示任意层路径 -->
<!--location:去哪个目录中寻找静态资源。mapping中**的值是什么,就去location目录中找对应资源-->
<!--例如URL是http://localhost:8080/js/jquery.js 其中mapping的**就是jquery.js,就会去location的/js/目录中寻找jquery.js -->
<mvc:resources mapping="/js/**" location="/js/"></mvc:resources>
<mvc:resources mapping="/css/**" location="/css/"></mvc:resources>
<mvc:resources mapping="/images/**" location="/images/"></mvc:resources>
九、控制单元的方法参数(接收请求参数)
1. 控制单元方法参数写法
控制单元方法参数一共有两种写法:
(1)紧耦方式。获取原生Servlet API,通过原生Servlet API获取请求参数、设置响应内容、设置作用域的值。
(2)解耦方式。使用Spring MVC提供的方式获取请求参数、设置响应内容、设置作用域的值。
2. 紧耦方式
在Spring MVC中,可以直接在控制单元的方法参数中按需注入HttpServletRequest、HttpServletResponse、HttpSession对象。
使用的是原生Servlet API,和原生Servlet API紧耦。
@RequestMapping("/demo")
public String demoServlet(HttpServletRequest req){
String name = req.getParameter("name");
return "index";
}
@RequestMapping("/out")
public void testOut(HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.print("aa");
}
作用域传值也是和之前Servlet中学习的一样,只需要在参数中注入作用域对象就可以了。但是需要注意ServletContext不能直接注入,需要通过其他对象获取。HttpServletRequest和HttpSession可以注入。
@RequestMapping("/scope")
public String testScope(HttpServletRequest req, HttpSession session){
ServletContext servletContext = req.getServletContext();
req.setAttribute("name1","value1");
session.setAttribute("name2","value2");
servletContext.setAttribute("name3","value3");
return "first";
}
3. 解耦方式
解耦方式是Spring MVC独有方式。是Spring MVC给开发者提供的:
(1)获取请求中内容
(2)设置作用域值
(3)设置响应内容
3.1.1 获取普通表单参数
获取普通表单参数,只需要包含在控制单元中提供与请求参数同名的方法参数即可。
@RequestMapping("/testParam")
public String testParam(String name,int age){
System.out.println(name+","+age);
return "suiyi";
}
3.1.2 @RequestParam 注解
@RequestParam是方法参数级注解。每个控制单元方法参数前面都能写这个注解。在@RequestParam注解里面提供了四个属性,分别:
(1)name:当请求参数名和控制单元参数名不对应时,可以使用name指定请求参数名。这样方法参数就可以不与请求参数对应了。
@RequestMapping("/testParam")
public String testParam(@RequestParam(name="name") String name123, Integer age){
System.out.println(name123+","+age);
return "suiyi";
}
(2)value:是name属性的别名。功能和name属性相同。
(3)defaultValue:默认值。表示当请求参数中没有这个参数时给与的默认值。defaultValue类型是String类型,Spring MVC会对值进行类型转换,转换成参数类型。
@RequestMapping("/testParam")
public String testParam(@RequestParam("name") String name123,@RequestParam(defaultValue = "16") Integer age){
System.out.println(name123+","+age);
return "suiyi";
}
(4)required:boolean类型,表示请求中是否必须包含参数。
@RequestMapping("/testParam")
public String testParam(@RequestParam("name") String name123,@RequestParam(required = true) Integer age){
System.out.println(name123+","+age);
return "suiyi";
}
如果设置为true,且请求中没有这个参数,响应时出现400状态码。
3.1.3 使用JavaBean作为参数(使用类对象作为控制单元参数)
JavaBean:就是具体(非抽象)公共(public)的类,一个包含私有属性,getter/setter方法和无参构造方法Java类。是不是感觉和实体类特别像。其实写法上和实体类相同。唯一区别是实体类是数据库层面的概念,类型中属性要和数据库字段对应。而JavaBean的属性是灵活的,不是必须和哪里对应的。
@RequestMapping("/testBean")
public String testBean(People peo){
System.out.println(peo);
return "suiyi";
}
当JavaBean中属性特别多时,原本希望使用普通属性类型接收参数,但是JavaBean中还存在个同名属性,且这个属性的类型和参数类型不一样。Spring MVC会进行类型转换,如果能够转换没有问题。如果无法转换会出现400。
3.1.5 接收多个同名表单参数
在提交表单数据时,可能在里面包含复选框。当选中多个复选框时会出现多个同名参数。在Spring MVC中可以使用数组和List接收多个同名参数。
@RequestMapping("/testHover")
public String testHovers(String [] hovers){
System.out.println(Arrays.toString(hovers));
return "suiyi";
}
@RequestMapping("/testHover")
public String testHovers(@RequestParam("hovers") List<String> hovers){
System.out.println(hovers);
return "suiyi";
}
3.1.6 接收日期类型参数
如果希望使用Date类型接收客户端传递过来的数据,默认情况下必须保证客户端参数格式和服务器日期格式一致。
所以只要保证客户端传递过来的日期是yyyy/MM/dd hh:mm:ss的格式,Spring MVC会自动进行类型转换。其中小时分钟秒可以省略不写。
@RequestMapping("/testDate")
public String testDate(Date date){
System.out.println(date.toLocaleString());
return "suiyi";
}
如果觉得默认的格式无法满足要求,可以使用@DateTimeFormat自定义时间格式。@DateTimeFormat可以写在控制单元Date类型参数之前,也可以写在JavaBean的属性上面。
@RequestMapping("/testDate")
public String testDate(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
System.out.println(date.toLocaleString());
return "suiyi";
}
3.1.7 接收请求头数据
在HTTP协议中,请求头参数会有很多。如果希望接收请求头数据可以使用@RequestHeader进行接收。
@RequestMapping("/testHeader")
public String testHeader(@RequestHeader String Accept){
System.out.println(Accept);
return "suiyi";
}
3.2 设置作用域的值
Spring MVC 中 提供了request作用域的解耦写法。没有提供Session作用域和Application作用域的解耦写法。也就是说当像给Request作用域设置内容时有两种写法,给Session和Application作用域设置值只有紧耦方式。
3.2.1 使用Model设置请求域的值
@RequestMapping("/testScope1")
public String testScope1(Model model){
// 设置一个作用域值
model.addAttribute("name","value");
// 设置多个作用域值
Map<String,Object> map = new HashMap<>();
map.put("name2","value2");
model.addAllAttributes(map);
return "suiyi";
}
3.2.2 使用Map设置请求域的值
直接在控制单元方法上添加一个Map,然后向Map对象中put值就可以了。
@RequestMapping("/testScope2")
public String testScope2(Map<String,Object> map){
map.put("name","value");
return "suiyi";
}
十、Spring MVC 中文乱码问题
1. 中文乱码解决方案
需要根据请求的方式的类型去选择对应的中文乱码解决方案。 常见的两种请求方式就是GET和POST。
2. GET方式中文乱码解决
Tomcat8及之后的版本已经将Get请求乱码进行了处理,不需要额外处理。
Tomcat7默认的接收请求GET方式的是IOS-8859-1,如果希望正确的显示中文,还需要手动把内容转换为UTF-8编码。
@RequestMapping("/testEncoding")
public String testEncoding(String name) throws UnsupportedEncodingException {
System.out.println("接收到name:"+name);
String newName = new String(name.getBytes("iso-8859-1"),"utf-8");
System.out.println("转换后的name:"+newName);
return "suiyi";
}
3. POST方式中文乱码解决
在Spring MVC提供了一个类CharacterEncodingFilter,里面直接写好了POST方式中文乱码解决代码。
所以想让CharacterEncodingFilter生效,就需要在web.xml文件中配置下面内容。
表示所有的请求编码都设置为UTF-8编码。
<!--配置字符编码过滤器-->
<filter>
<filter-name>code</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>code</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>