什么是SpringMVC
简单地讲,SpringMVC就是以Servlet为底层的轻量级Web框架,SpringMVC包含在SpringFramework中,用于实现MVC三层架构模式,即Model-View-Controller模式,该模式将数据、显示、业务逻辑分离,极大地降低了代码耦合度和维护成本
构建一个入门项目
似乎Tomcat10有什么大病,导入包全对硬是给我404,导出到lib也完全不行,排了两天bug还是™的404,大无语,我直接退回Tomcat9……
远离Tomcat10,会变得不幸
项目视图康康
新建一个普通的Maven项目,添加Web框架支持,导入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
web.xml中注册DispatcherServlet
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
resourse目录下新建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="***"/>
<!-- 不让静态资源请求被SpringMVC拦截 -->
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
<!-- 设置前后缀,用于请求的拼接 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello</title>
</head>
<body>
<%-- 取一下请求中的name值 --%>
Hi, ${name}, 114514, welcome to hello page
</body>
</html>
Hello.java
package com.hoppi.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class Hello {
@RequestMapping("/hello")
public String answer(Model model) {
//添加名为name的属性,其值为Hoppinging
model.addAttribute("name", "Hoppinging");
//会被视图解析器解析为 "/WEB-INF/jsp/hello.jsp"
return "hello";
}
}
run一下
Restful风格
什么是RESTful风格?
那么用SpringMVC怎么实现呢?
来一个不能再简单的示例
package com.hoppi.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* <p>
* 创建日期:2021-12-08 14:08
*
* @author Hoppinging
* @version 1.1
* @since 1.0
*/
@Controller
public class Calculate {
@RequestMapping("/add/{a}/{b}")
//@PathVariable为路径变量的注解,可以从路径中获取参数({}中为路径中参数名称,与方法中参数名称一致)
public String add(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("num", a + b);
return "result";
}
@RequestMapping("/differ/{a}/{b}")
public String differ(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("num", a - b);
return "result";
}
@RequestMapping("/multiply/{a}/{b}")
public String multiply(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("num", a * b);
return "result";
}
}
result.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>result</title>
</head>
<body>
The result is ${num}
</body>
</html>
我的项目发布路径=http://localhost:8080
当访问url="http://localhost:8080/add/1/2"时
可以看到,原来要计算"1 + 2 = 3"可能需要add/?a=1&b=2,而使用Restful风格后只需要add/1/2就行了,隐藏了变量名
当然,这个示例中add方法对所有种类的请求都只能计算a+b
可以通过改变注解来实现同一路径不同请求类型得到不同结果
仙来一个恶臭的类
package com.hoppi.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
/**
* <p>
* 创建日期:2022-01-16 16:18
*
* @author Hoppinging
* @version 1.1
* @since 1.0
*/
@Controller
public class RestfulView {
@GetMapping("/view")
public String view1(Model model) {
model.addAttribute("note", "杰哥不要啦");
return "stage";
}
@PostMapping("/view")
public String view2(Model model) {
model.addAttribute("note", "哼哼哼啊啊啊啊啊");
return "stage";
}
@DeleteMapping("/view")
public String view3(Model model) {
model.addAttribute("note", "114514");
return "stage";
}
@PutMapping("/view")
public String view4(Model model) {
model.addAttribute("note", "听话!给我康康");
return "stage";
}
}
这个类可以通过改变请求方式改变note属性的内容
来个stage.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Stage</title>
</head>
<body>
${note}
</body>
</html>
然后部署,启动Tomcat
利用FoxyProxy插件 + Burp(我用的社区版)抓包修改请求方式
① 正常请求(GET):
然后抓个包改一下请求方式
② forward得到(POST):
嗯,压力马斯内
再改成DELETE:
他奶奶滴
“JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS”
简单解决的方法是:修改stage.jsp,令isErrorPage=“true”
<%-- 下面这行taglib是IDEA自动生成的 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" isErrorPage="true"%>
<html>
<head>
<title>Stage</title>
</head>
<body>
${note}
</body>
</html>
但这样只是把stage.jsp当作出错页面处理,发生这种事的根本原因是Tomcat的约束问题,禁止JSP页面除GET/POST之外的请求方式
③ 修改之后(DELETE):
④ 然后(PUT):
转发与重定向
你可以删除你的视图解析器,通过原生的ServletAPI,就可以实现转发和重定向
在你的方法中传入一个HttpServletRequest类型的形参和一个HttpServletResponse类型的形参,和HttpServlet中的req和resp一样地使用它们
重定向为resp.sendRedirect("请求路径");
转发为req.getRequestDispatcher("请求路径").forward(req,resp);
或者使用SpringMVC来实现
SpringMVC默认的请求方式就是转发,重定向需要在请求路径前加上redirect:
,对应的,转发则是加forward:
有视图解析器的时候,重定向操作的请求路径如果直接写文件路径而不能被视图解析器解析则会爆一个警告,“无法解析控制器URL…”,不用管它就行
需要注意的是,如果你的视图解析器还在,那么转发操作要么可以被视图解析器解析(此时forward:
可有可无),要么需要在前面写forward:
,否则404
接收请求参数及数据回显
接收参数
后端接收到的参数可以是普通的,像int、String那样的类型,也可以是自定义的一个或多个对象
注意,如果接受了多个对象,它们的字段名必须不同,若出现相同,则String类型发生合并(即拼接字符串),int类型只有第一个传入参数有效。其他的类型没有测过
Demo:
package com.hoppi.controller;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class GetObject {
@GetMapping("/getCommon")
//@RequestParam注解用于命名参数,传入参数的参数名必须与命名的参数名相同
public String getCommon(@RequestParam("name") String name, Model model) {
System.out.println(name);
model.addAttribute("name", name);
//hello.jsp就是显示name的页面
return "hello";
}
@GetMapping("/getObject")
public String getObject(User user, Model model) {
System.out.println(user);
model.addAttribute("user", user);
return "info";
}
@GetMapping("/getObjects")
public String getObjects(User user,Enemy enemy, Model model) {
System.out.println(user);
System.out.println(enemy);
model.addAttribute("user", user);
model.addAttribute("enemy", enemy);
return "info";
}
}
//Lombok偷懒
@Data
//下面两行不加也行
@NoArgsConstructor
@AllArgsConstructor
class User {
private String name;
private int age;
private int id;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Enemy {
private String e_name;
private int e_id;
}
这是info.jsp, 用以显示user和enemy的内容
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Info</title>
</head>
<body>
${user}
${enemy}
</body>
</html>
url = “http://localhost:8080/getCommon?name=hoppinging”
url = “http://localhost:8080/getObject?name=hoppinging&age=114&id=514”
url = “http://localhost:8080/getObjects?name=hoppinging&age=114&id=514&e_name=George&e_id=1919”
数据回显
大致就是我们接收并处理前端传入数据并把处理结果传回前端的过程,包含接收数据、数据处理和视图跳转
视图跳转用到的主要就是Model、ModelMap和ModelAndView
package com.hoppi.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class DataEcho {
//第一种,使用ModelAndView
@GetMapping("/e1")
public ModelAndView echo() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("note", "ModelAndView回显成功");
//stage就是显示note的页面
modelAndView.setViewName("stage");
return modelAndView;
}
//第二种,使用Model
@GetMapping("/e2")
public String echo(Model model) {
model.addAttribute("note", "Model回显成功");
return "stage";
}
//第三种,使用ModelMap(可以做Model能做的任何事,并继承了LinkedHashMap,有LinkedHashMap的所有功能)
@GetMapping("/e3")
public String echo(ModelMap modelMap) {
modelMap.addAttribute("note", "ModelMap回显成功");
return "stage";
}
}
配置乱码过滤器
当使用Post方式请求时,可能产生中文乱码问题
如图,这是一个Post表单,
提交后转发一个显示输入数据的页面
成功乱码了
使用SpringMVC自带的乱码过滤器
在web.xml中加入
<filter>
<filter-name>encoding</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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Post乱码问题解决
JSON
什么是JSON
JSON,即
JavaScript Object Notation
, JS对象标记,是一种轻量级的数据交换格式
自己配过VS Code的小伙伴应该都了解过JSON
VS Code中修改设置本质上是在修改配置文件settings.json文件
大概长这样
简单地讲,JSON文件存储的就是一堆键值对,可以存对象和数组
格式类似于{"key": "value"}
,{"key1": "value1", "key2": "value2"}
,{"arrayKey": ["value1", "value2", ..., "valueX"]}
这样的
Jackson
Jackson是一种常见的JSON解析工具,使用时主要调用ObjectMapper对象方法进行json和java对象的转换
从Maven仓库导入Jackson依赖
简单使用方法
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;
public class TestJackson {
@Test
//对象转Json
public void test1() throws JsonProcessingException {
//1. 创建Jackson的ObjectMapper对象
ObjectMapper mapper = new ObjectMapper();
//2. 创建一个普通java对象
Person person = new Person(114514, "XianPai", "男");
//3. 转换为JSON格式的字符串
String json = mapper.writeValueAsString(person);
System.out.println(json);
}
@Test
//Json转对象
public void test2() throws JsonProcessingException {
//1. 得到JSON字符串
String json = "{\"id\":114514,\"name\":\"田所小姐\",\"gender\":\"男\"}";
//2. 创建Jackson的ObjectMapper对象
ObjectMapper mapper = new ObjectMapper();
//3. 得到对象
Person person = mapper.readValue(json, Person.class);
person.introduce();
}
}
//这个类之后会被我转移到com.hoppi.pojo包下
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
private int id;
private String name;
private String gender;
public void introduce() {
System.out.println("My name is " + name + " and my ID is " + id);
}
}
来一个简单的Controller类jsonTest
package com.hoppi.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hoppi.pojo.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class JsonTest {
@RequestMapping("/getJson")
//@ResponseBody注解的作用是使return的内容直接显示在页面中
//把类注解@Controller改成@RestController也能做到相同的事情
@ResponseBody
public String answer() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new Person(114514, "Hoppinging", "男"));
return json;
}
}
这里如果出现500错误且错误原因是无法创建这个类的bean
那可能是忘记在项目结构中把包导入到lib中
成功(?)
显然gender没有成功显示
接下来解决jackson中文乱码问题
第一种方式
在Controller的方法的Mapping(比如RequestMapping)注解中配置属性produces = "application/json;charset=utf-8"
第二种方式(推荐)
配置springmvc-servlet.xml中的<mvc:annotation-driven>
<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="failOnEmptyBeans" value="false"/> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
Jackson同样可以处理集合和数组
Jackson默认的时间格式是时间戳Timestamp
可以通过SimpleDateFormat
将其改为自定义的时间格式
具体用法为new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)
,format方法将返回一个固定格式的字符串
或者通过使用ObjectMapper的方法configure
修改ObjectMapper对象的属性SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
为false
(默认为true,即开启时间戳转换),使ObjectMapper不自动转换Date类型为时间戳
显然格式非常奇怪嗷
但ObjectMapper还提供了设置日期格式的方法setDateFormat
,该方法需要传入一个DateFormat
类型的对象,所以自然也可以传入一个SimpleDateFormat
对象
FastJson
这东西一听就很fast
o((>ω< ))o
主要用的方法就是toJSONString()
和parseObject()
,前者用于将对象转换为JSON字符串,后者用于将JSON字符串转换为对象
fastJson的时间类型也是时间戳
使用toJSONStringWithDateFormat
方法可以将时间改成想要的格式
用法是JSON.toJSONStringWithDateFormat(Date类型对象, "需要的日期格式如:yyyy-MM-dd HH:mm:ss");
,会返回一个JSON字符串