Universe Infinity Inc.
在SpringMVC的学习中,我们首先要明确其核心内容是稳定的,而其他内容则需要在探索中逐渐掌握。对于那些不熟悉Spring的底层知识,你无需过于担心,可以先学会如何使用它,然后在你单独学习Spring的基础知识时再去深入理解其背后的原理。同样,如果你对Servlet的知识感到困惑,可能会让你对请求和响应的处理感到迷茫。此时,你不应停滞不前,去深入研究Servlet的知识,可以先学会如何使用SpringMVC,然后再去深入了解Servlet的相关知识。重要的是要明确,SpringMVC的知识点并不包含那些内容。我们在学习的过程中切忌偏离主题,不要因为关注一些细枝末节而忽略了真正重要的核心内容。
看懂下面这个图
1、知道SpringMVC的基本原理。
DIspatcherServlet、HandlerMapping、HandlerAdapter、Handler、ViewResovler几个内容的含义及理解
DefaultServletHandler、HandlerInterceptor、ExceptionHandler等内容的理解
2、SpringMVC接入参数
param入参、json入参、路径传参、原生API获取、请求头和请求体获取
3、SpringMVC响应数据
返回json数据、返回页面视图、转发和重定向、静态资源的访问处理DefaultServletHandler
4、SpringMVC的HandlerInterceptor、ExceptionHandler配置
SpringMVC6、JDK17、Tomcat10+
项目整体的架构
<dependencies>
<!-- springIoc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.2</version>
</dependency>
<!-- springMVC相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.2</version>
</dependency>
<!-- web相关依赖 -->
<!--SpringMVC6中使用jakarta EE为Servlet,需要配置对应的依赖-->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope> <!--这里打个疑问,为什么要配置成provide呢?-->
</dependency>
</dependencies>
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 全注解开发模式,配置Spring容器的信息
*/
@Configuration
@ComponentScan("com.universe")
public class SpringConfig {
}
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* 全注解开发模式,替代web.xml
* 通过继承AbstractAnnotationConfigDispatcherServletInitializer实现容器的初始化
*/
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定service / mapper层的配置类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
System.out.println("getRootConfigClasses");
return new Class[0];
}
/**
* 指定Spring的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
System.out.println("getServletConfigClasses");
return new Class[]{SpringConfig.class}; //这里指定了Spring的配置类,将会在web启动时加载
}
/**
* 设置dispatcherServlet的处理路径!
* 一般情况下为 / 代表处理所有请求!
*/
@Override
protected String[] getServletMappings() {
System.out.println("getServletMappings");
return new String[]{"/"};
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
@RequestMapping("/index")
@ResponseBody
public String index(){
return "Welcome to UII!";
}
}
param传参形式,使用==@RequestParam==注解
- param传参是什么形式,在url后加?形式
- 单值传参、多值传参、同名值传参、实体类接收param、Map接收param
@Data
public class Product {
String id;
String name;
String price;
}
@Controller
@ResponseBody
@RequestMapping("product")
public class ProductController {
/**
* 1、使用param传参形式测试
* 1)使用注解@RequestParam表示param传参
* 2)当URL传递的参数名和方法形参名相同时,会自动接收
* 3)可以使用required表示是否必须,并使用defaultValue设置默认值
* 4)特殊情况
* a、一名多值情况
* b、实体类接收
* c、Map接收
*/
//http://localhost:8080/product/update1?name=mobile
@RequestMapping("update1")
public String updateProduct1(@RequestParam("name") String name) {
System.out.println(name);
return name;
}
//http://localhost:8080/product/update2?name=mobile&price=200
@RequestMapping("update2")
public String updateProduct2(String name, String price) { //这种情况因配置问题会报错,暂且忽略
System.out.println("name=" + name + ",price=" + price);
return name + " " + price;
}
//http://localhost:8080/product/update3?name=mobile&price=200
//通过required指定参数是否为必须,默认true必须。使用defaultValue指定默认值
@RequestMapping("update3")
public String updateProduct3(@RequestParam(value = "name") String productName,
@RequestParam(value = "price", required = false, defaultValue = "0") String productPrice) {
System.out.println("name=" + productName + ",price=" + productPrice);
return productName + " " + productPrice;
}
//http://localhost:8080/product/update4a?name=mobile&name=computer
//多个相同传参情况下,直接使用list接收
@RequestMapping("update4a")
public String updateProduct4a(@RequestParam("name") List<String> list) {
System.out.println(list.toString());
return list.toString(); //[mobile, computer]
}
//http://localhost:8080/product/update4b?name=mobile&price=999
//通过实体类接收param,实体类的成员变量和param传参名相同,mvc会自动封装
@RequestMapping("update4b")
public String updateProduct4b(Product product) {
System.out.println(product.toString());
return product.toString(); //Product(id=null, name=mobile, price=999)
}
//http://localhost:8080/product/update4c?name=mobile&price=999
//通过Map接收param,自动封装为map
@RequestMapping("/update4c")
public String updateProduct4c(@RequestParam Map<String, Object> map){
for (Map.Entry<String, Object> stringObjectEntry : map.entrySet()) {
System.out.println(stringObjectEntry.getKey()+":"+stringObjectEntry.getValue());
}
return map.toString(); //{name=mobile, price=999}
}
}
json传参形式,使用==@ResponseBody来接收数据,SpringMVC会自动封装json到实体类
json转实体类需要配置json转换器。使用@EnablewebMvc添加json处理器,该注解同时也会添加HandlerMapping和HandlerAdapter==处理器不用手工创建。
- 知道如何配置json处理器,知道如何使用json传参
- json传参封装到String
- 简单json传参封装到实体类
- 复杂json传参封装到实体类
- json传参封装到Map中
//用户
@Data
public class User {
String name;
int age;
}
//订单详情
@Data
public class OrderDetails {
int id; //订单编号
List<Product> productList; //订单的产品列表
int date; //下单日期
}
@Controller
@ResponseBody
@RequestMapping("user")
public class UserController {
/**
* 2、使用json传参
* json传参需要先配置web的json转换器,先引入依赖
*<dependency>
* <groupId>com.fasterxml.jackson.core</groupId>
* <artifactId>jackson-databind</artifactId>
* <version>2.15.0</version>
* </dependency>
* 在Spring配置类中加上注解@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
* 这里留一个问题:@EnableWebMvc的原理是什么呢?
*/
/**
* http://localhost:8080/user/data1
* {"name": "tang","age":"22"}
* 1) 输入的json直接转为String
*/
@RequestMapping("data1")
public String updateUser1(@RequestBody String json) {
System.out.println(json);
return json;
}
/**
* http://localhost:8080/user/data2
* {"name": "tang","age":"22"}
* 2) 将简单参数直接封装到实体类
*/
@RequestMapping("data2")
public String updateUser2(@RequestBody User user) {
System.out.println(user.toString());
return user.toString(); //User(name=tang, age=22)
}
/**
* http://localhost:8080/user/data3
{"id":"001",
"productList":[
{"id":"1","name":"apple","price":"999"},
{"id":"2","name":"hw","price":"1999"},
{"id":"3","name":"mobile","price":"9999"}],
"date":"20240116"}
* 3) 将复杂的参数封装到实体类
*/
@RequestMapping("data3")
public String updateUser3(@RequestBody OrderDetails orderDetails) {
System.out.println(orderDetails.toString());
for (Product product : orderDetails.getProductList()) {
System.out.println(product.toString());
}
return orderDetails.toString();
//OrderDetails(id=1, productList=[Product(id=1, name=apple, price=999), Product(id=2, name=hw, price=1999), Product(id=3, name=mobile, price=9999)], date=20240116)
}
/**
* http://localhost:8080/user/data4
(1) {"name": "tang","age":"22"}
(2) {"id":"001",
"productList":[
{"id":"1","name":"apple","price":"999"},
{"id":"2","name":"hw","price":"1999"},
{"id":"3","name":"mobile","price":"9999"}],
"date":"20240116"}
* 4)使用map接收json数据
*/
@RequestMapping("data4")
public String updateUser4(@RequestBody Map<String,Object> map){
for (Map.Entry<String, Object> stringObjectEntry : map.entrySet()) {
System.out.println(stringObjectEntry.getKey()+":"+stringObjectEntry.getValue());
}
return map.toString();
//{name=tang, age=22}
//{id=001, productList=[{id=1, name=apple, price=999}, {id=2, name=hw, price=1999}, {id=3, name=mobile, price=9999}], date=20240116}
}
}
路径传参的使用在后续的RestFul风格的开发中会经常用到
其主要的注解是==@PathVariable==
- 知道路径传参的输入格式,后续怎么处理扥个
@Controller
@ResponseBody
@RequestMapping("order")
public class OrderDetailsController {
/**
* http://localhost:8080/order/1/20230116
* 3、使用路径传参的形式
* 路径传参主要在RestFul风格开发中用到
* 使用@PathVariable注解表示
* 简单的展示下
*/
@RequestMapping("/{id}/{date}")
public String selectOrder(@PathVariable("id") String id,@PathVariable("date") String date){
System.out.println(id+" "+date);
return id+"_"+date;
}
}
SpringMVC也支持原生的API的使用,直接在Handler中参数中传
请求头使用==@RequestHeader==
请求体使用==@RequestBody==,这一点可以看到,json接收数据仅仅是接收请求体数据的一部分
接收Cookies可以使用==@CookieValue==
- 请求头数据也可以获取到
- 请求体数据可以是任何内容,json只是其中的一部分
- 想要使用原生Servlet的话,直接在handler参数中声明即可
- cookies可以使用特定注解获取,当然也有其它方式
@Controller
@ResponseBody
@RequestMapping("native")
public class NativeController {
/**
* 4、请求头,请求体,cookies等内容
* 使用原生的Servlet API获取数据
*/
//获取请求头:http://localhost:8080/native/header1
//(1)获取请求头中的单个内容,@RequestHeader("accept")获取的是accept
@RequestMapping("header1")
public String getHerder1(@RequestHeader("accept") String header){
System.out.println(header);
return header; //text/html,application/xhtml+x...
}
//http://localhost:8080/native/header2
//(2)获取全部请求头内容
@RequestMapping("header2")
public String getHerder2(@RequestHeader Map<String, Object> headers){
System.out.println(headers.toString());
return headers.toString();
}
//http://localhost:8080/native/body
//使用postman,请求体可以是任何内容
//(3)获取请求体,对应的json传参情况只是获取请求体过程中的一个例外
@RequestMapping("body")
public String getBody(@RequestBody String body){
System.out.println(body);
return body;
}
//http://localhost:8080/native/setcookies
//(4)获取cookies,当然在获取cookies的时候需要先存放cookies
@RequestMapping("setcookies")
public String setCookies(HttpServletRequest req, HttpServletResponse resp){
Cookie cookies1 = new Cookie("cookies1", "Universe_Infinity_inc.");
resp.addCookie(cookies1);
return "Ok!";
}
//http://localhost:8080/native/cookies1
@RequestMapping("cookies1")
public String getCookies1(@CookieValue(value = "cookies1",required = false,defaultValue = "") String cookies){
System.out.println(cookies);
return cookies; //Universe_Infinity_inc.
}
//http://localhost:8080/native/cookies2
@RequestMapping("cookies2")
public String getCookies2(@CookieValue Map<String,Object> map){ //这个请求会失败!
System.out.println(map.toString());
return map.toString();
}
}
在前后端分离的开发模式下,响应数据仅仅需要返回字符串,这时使用==@ResponseBody==直接返回字符串
在非前后端分离的开发模式下,响应数据需要返回具体的视图,这时需要对视图进行一系列的配置
- 返回JSON数据情况,配置好
@Data
public class User {
String name;
String age;
}
@Controller
@RequestMapping("user")
public class UserController {
}
关键注解==@ResponseBody==
/**
* http://localhost:8080/user/get
* request:{"name":"tang","age":"22"}
* 1、前后端分离的情况下,响应数据为json形式情况,使用@ResponseBody注解
* 使用@EnableWebMvc注解,配置json处理器
*/
@PostMapping("/get")
@ResponseBody
public User getUser(@RequestBody User user){
System.out.println(user.toString());
return user;
}
编写页面视图
文件路径:/webapp/WEB-INF/view/page.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>这是JSP生成的页面</p>
${msg}
</body>
</html>
配置页面处理
@EnableWebMvc
@Configuration
@ComponentScan("com.response")
public class SpringConfig implements WebMvcConfigurer {
//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/view/", ".jsp");
}
}
编写handler
注意不要加==@ResponseBody注解,同样不能使用@RestController==注解
/**
*http://localhost:8080/user/page?name=tang&age=22
* 2、非全后端分离的情况下,返回页面视图。注意不要加@ResponseBody注解!!
* 在容器中添加好视图解析器
* public void configureViewResolvers(ViewResolverRegistry registry) {
* //快速配置jsp模板语言对应的
* registry.jsp("/WEB-INF/view/", ".jsp");
* }
* 直接返回的page会自动拼装为"/WEB-INF/view/page.jsp",同时也会对model中的数据解析
*/
@RequestMapping("page")
public String getUserPage(User user,Model model){
System.out.println(user.toString());
model.addAttribute("msg",user.toString());
return "page";
}
前台展示
什么是转发,什么是重定向呢?
转发和重定向在返回视图页面时使用。
转发直接在返回的字符串前加forward:
重定向直接在返回的字符串加redirect:
/**
* http://localhost:8080/user/forward?name=tang&age=22
* 此请求返回的是:User(name=tang, age=22)
* 3、使用forward进行转发,注意不要加@ResponseBody注解
* 转发时服务器的操作,用户请求一次。全程只有一个HttpServletRequest对象,请求参数可以传递
* 转发可以访问到WEB-INF下受保护的资源
*/
@RequestMapping("forward") //转发
public String forward() {
return "forward:/user/page";
}
/**
* http://localhost:8080/user/redirect?name=tang&age=22
* 此请求返回的是:User(name=null, age=null)
* 4、使用redirect进行重定向,注意不要加@ResponseBody注解
* 重定向是服务端告诉客户端去请求其它路径的操作,客户端至少请求两次。会产生多个请求对象,参数不可以传递
* 重定向可以访问项目外的资源
*
*/
@RequestMapping("redirect") //重定向
public String redirect() {
return "redirect:/user/page";
}
添加静态资源处理器
@EnableWebMvc
@Configuration
@ComponentScan("com.response")
public class SpringConfig implements WebMvcConfigurer {
//http://localhost:8080/image/lyf.jpg
//请求在最初的时候会通过SpringMVC的机制转给RequestMapping查找对应的Handler,然而查不到静态资源,会直接报错
//开启静态资源处理 <mvc:default-servlet-handler/>,添加DefaultServletHandler再解析
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
放个静态资源
/webapp/image/lyf.jpg
http://localhost:8080/image/lyf.jpg 直接访问就可以访问到了
/**
* SpringMVC异常处理机制的步骤:
* 1、编写专门处理异常的类,标注注解@ControllerAdvice或@RestControllerAdvice。
* 2、编写异常处理的方法,方法上标注@ExceptionHandler(异常.class)
* 3、异常处理的方法中编写异常处理的逻辑等
* 异常处理一般单独放在一个包内
*/
@RestControllerAdvice //这个是@ControllerAdvice和@ResponseBody的整合形式
public class ExceptionHandlerConfig {
//在其中编写异常处理的类
//使用@ExceptionHandler来注解处理异常的类
@ExceptionHandler(Exception.class)
public String allExcetpion(Exception e){
System.out.println(e.getMessage());
return "serious mistake!";
}
@ExceptionHandler(NullPointerException.class)
public String nullPointerExcetpionHandler(NullPointerException e){
System.out.println(e.getMessage());
return "null error!";
}
}
@RestController
@RequestMapping("exception")
public class ExceptionController {
//http://localhost:8080/exception/exce1
@RequestMapping("exce1")
public String exception1() {
int i = 1 / 0;
return "exception1";
}
//http://localhost:8080/exception/exce2
@RequestMapping("exce2")
public String exception2() {
User user = null;
user.toString();
return "exception2";
}
}
无异常处理的时候
有异常处理机制
SpringMVC拦截器是MVC容器内部的处理器。其主要是与过滤器对比。
1、如何编写SpringMVC拦截器,实现HandlerInterceptor接口
2、HandlerInterceptor接口的三个方法的生效位置
3、拦截器的作用范围。addPathPatterns(),excludePathPatterns()的使用
/**
* SpringMVC的拦截器编写步骤:
* 1、编写自定义的拦截类,实现HandlerInterceptor接口
* 2、HandlerInterceptor接口中有三个方法,用于请求拦截
* 3、直接实现三个方法即可
* 4、将拦截器加到配置类中
* //配置拦截器
* @Override
* public void addInterceptors(InterceptorRegistry registry) {
* registry.addInterceptor(new MyInterceptor());
* }
* 5、拦截器可以配置拦截的路径,针对性拦截
*/
public class MyInterceptor implements HandlerInterceptor {
/**
* preHandle在HandlerAdapter寻找Handler前执行
* @param request 请求
* @param response 响应
* @param handler 方法
* @return true放行 false拦截
* @throws Exception 异常处理
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor.preHandle");
System.out.println(handler.toString());
return true;
}
/**
* postHandler在Handler执行后执行,handler报错不执行
* @param request 请求
* @param response 响应
* @param handler 方法
* @param modelAndView 视图
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
/**
* afterCompletion在视图渲染(如果有)后执行
* @param request 请求
* @param response 响应
* @param handler 方法
* @param ex 异常
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
@EnableWebMvc
@Configuration
@ComponentScan("com.universe")
public class SpringConfig implements WebMvcConfigurer {
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RootInterceptor()).addPathPatterns("/rest/*"); //拦截特定的url
registry.addInterceptor(new MyInterceptor());
}
}