SpringMVC框架
一、SpringMVC介绍
1. 官方文档
- 地址:spring-framework-5.3.8/docs/reference/html/web.html#spring-web
2. SpringMVC 基本介绍
2.1 SpringMVC特点和概述
- SpringMVC 从易用性、效率上 比曾经流行的Struts2更好
- SpringMVC 是Web层的框架【SpringMVC接管了Web层的组件,比如控制器、视图、视图解析器,返回给用户的数据格式,同时支持MVC的开发模式/开发架构】
- SpringMVC通过注解,让pojo(普通的Java类)成为控制器,不需要继承类或者实现接口
- SpringMVC采用低耦合的组件设计方式,具有更好的扩展和灵活性
- 支持REST格式的URL请求风格
- SpringMVC 是基于Spring的,也就是SpringMVC是在Spring基础上的。SpringMVC的核心包spring-webmvc-xx.jar和spring-web-xx.jar
2.2 梳理Spring、SpringMVC和SpringBoot的关系
- SpringMVC只是Spring处理WEB层请求的一个模块/组件,SpringMVC 的基石是Servlet
- SpringBoot 是为了简化开发者的使用,推出的封神框架(约定优于配置,简化了Spring的配置流程),SpringBoot包含很多组件/框架,Spring就是最核心的内容之一,也包含SpringMVC
- 它们的关系大概是:SpringBoot > Spring > SpringMVC
3. 快速入门
3.1 需求说明
- 需求说明:完成一个最基本的测试案例,登录案例
3.2 代码实现
3.2.1 Controller包
package com.leon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* ClassName:UserController
* Package:com.leon.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/13 8:42
* @Version: 1.0
*/
@Controller
public class UserServlet {
/*
* 1.login() 方法用于响应用户的登录请求,其被RequestMapping注解标注
* 2.RequestMapping注解可以将一个方法变成可以访问的方法,通过指定的URL进行请求
* value属性值表示请求的url,也可以理解为在web.xml文件中配置的<url-pattern></url-pattern>
* 3.注意这个注解可以在类上进行标注,也可以给类进行value赋值,这时就需要加上类的URL
* 4.即当用户在浏览器输入 http://localhost:8080/web工程路径/{类的URL}/login
* 5.return "login_ok" 表示返回结果给视图解析器(InternalResourceViewResolver)
* 视图解析器会根据配置,来决定跳转到哪个界面
* <!--配置SpringMVC的视图解析器-->
* <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
* <!--配置前缀,是要访问的包-->
* <property name="prefix" value="/WEB-INF/pages/"></property>
* <!--配置后缀,是要访问的文件类型 -->
* <property name="suffix" value=".jsp"></property>
* </bean>
* 根据上面的配置,return "login_ok",将会被转发到/WEB-INF/pages/login_ok.jsp
* */
@RequestMapping(value = "/login")
public String login(){
System.out.println("登录成功~~~~~");
return "login_ok";
}
}
3.2.2 ioc文件
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置要扫描的包路径-->
<context:component-scan base-package="com.leon.controller"/>
<!--配置SpringMVC的视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀,是要访问的包-->
<property name="prefix" value="/WEB-INF/pages/"></property>
<!--配置后缀,是要访问的文件类型 -->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
3.2.3 web.xml文件
<?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">
<!--配置前端控制器/中央处理器/分发控制
1.用户的请求会经过它的处理器
-->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置ioc容器文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext_mvc.xml</param-value>
</init-param>
<!--配置加载顺序-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!--
1.这里配置的url-pattern是 / ,表示用户的请求都经过 DispatcherServlet
2.这样配置也支持rest 风格的url请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.2.4 jsp文件
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/4/13
Time: 8:39
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form action="login" method="post">
u:<input type="text" name="user"><br>
p:<input type="password" name="pwd"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
======================================================================================
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/4/13
Time: 9:12
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>登录成功~~~</h1>
</body>
</html>
3.3 注意事项和使用细节
UserServlet 需要注解成Controller,称之为handler处理器
RequestMapping注解在指定url时,这个value属性名可以省略
关于SpringMVC 的DispatcherServlet的init-param中的contextConfigLocation配置,如果不在web.xml指定applicationcontext_mvc.xml,默认会在/WEB-INF/springDispatcherServle-servlet.xml找这个配置文件
4. SpringMVC执行流程
二、SpringMVC的使用
1. @RequestMapping 注解
1.1 基本使用
- @RequestMapping 注解可以指定控制器/处理器的某个方法的请求的url,RequestMapping:请求映射
1.1.1 代码
/*
* 1.login() 方法用于响应用户的登录请求,其被RequestMapping注解标注
* 2.RequestMapping注解可以将一个方法变成可以访问的方法,通过指定的URL进行请求
* value属性值表示请求的url,也可以理解为在web.xml文件中配置的<url-pattern></url-pattern>
* 3.注意这个注解可以在类上进行标注,也可以给类进行value赋值,这时就需要加上类的URL
* 4.即当用户在浏览器输入 http://localhost:8080/web工程路径/{类的URL}/login
* 5.return "login_ok" 表示返回结果给视图解析器(InternalResourceViewResolver)
* 视图解析器会根据配置,来决定跳转到哪个界面
* <!--配置SpringMVC的视图解析器-->
* <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
* <!--配置前缀,是要访问的包-->
* <property name="prefix" value="/WEB-INF/pages/"></property>
* <!--配置后缀,是要访问的文件类型 -->
* <property name="suffix" value=".jsp"></property>
* </bean>
* 根据上面的配置,return "login_ok",将会被转发到/WEB-INF/pages/login_ok.jsp
* */
@RequestMapping(value = "/login")
public String login(){
System.out.println("登录成功~~~~~");
return "login_ok";
}
1.2 @RequestMapping 其它使用方式
1.2.1 @RequestMappping 可以修饰方法和类
- 说明
- @RequestMapping 注解可以修饰方法,还可以修饰类当同时修饰类和方法时,请求的url就是类和方法的value组合,格式为:/类请求值/方法请求值
1.2.1.1 代码
package com.leon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* ClassName:UserHandler
* Package:com.leon.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/13 13:34
* @Version: 1.0
*/
@RequestMapping("/user")
@Controller
public class UserHandler {
/*
* 1. method=RequestMethod.POST: 表示请求buy目标方法必须是POST,
* 如果不是POST请求方式的话则会报错
* 2. RequestMethod 常用的四个选项POST,GET,PUT,DELETE
* 3. SpringMVC 控制器默认支持GET和POST两种方式
* 4. buy方法的请求URL:http://localhost:8080/springmvc/user/buy
* */
@RequestMapping(value = "/buy")
public String buy(){
System.out.println("购买商品~~~~");
return "success";
}
}
======================================================================================
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/4/13
Time: 8:39
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form action="user/buy" method="post">
u:<input type="text" name="user"><br>
p:<input type="password" name="pwd"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
1.2.2 @RequestMapping 可以指定请求方式
- 说明
- @RequestMapping还可以指定请求的方式(POST/GET/PUT/DELETE),请求的的方式要和指定的一样,否则会报错
- SpringMVC 控制器默认支持GET和POST两种方式,也就是你不指定Method,可以接收GET和POST请求
- 还可以直接使用@GetMapping,@PostMapping,@PutMapping,@DeleteMapping等这些注解,它们类似于 @RequestMapping(value = url , method = RequestMethod.请求方式)
1.2.2.1 代码
package com.leon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* ClassName:UserHandler
* Package:com.leon.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/13 13:34
* @Version: 1.0
*/
@RequestMapping("/user")
@Controller
public class UserHandler {
/*
* 1. method=RequestMethod.POST: 表示请求buy目标方法必须是POST,
* 如果不是POST请求方式的话则会报错
* 2. RequestMethod 常用的四个选项POST,GET,PUT,DELETE
* 3. SpringMVC 控制器默认支持GET和POST两种方式
* 4. buy方法的请求URL:http://localhost:8080/springmvc/user/buy
* */
@RequestMapping(value = "/buy",method = RequestMethod.POST)
public String buy(){
System.out.println("购买商品~~~~");
return "success";
}
@GetMapping(value = "shopping")
public String shopping(){
System.out.println("逛商城~~~~");
return "success";
}
}
1.2.3 @RequestMapping可以指定params和headers支持简单表达式
- 说明
- params ="xxx",表示请求必须包含名为xxx的请求参数
- params != "xxx",表示请求不能包含名为xxx的请求参数
- params = "xxx != value",表示请求包含名为xxx的请求参数,但不能为value
- param = { "xxx = value","ttt"}:表示请求必须包含为xxx和ttt的两个请求参数,且xxx参数的值必须为value
- 注意:方法中的形参名必须要和params的param一样才可以,否则形参值是null,也就是赋值失败
1.2.3.1 代码
/*
* 1. params="bookId" 表示请求该方法时,必须给一个bookId参数,只没有要求,请求时,所
* 携带的参数值会赋值给方法中形参
* 2. 注意:形参名要和params="bookId"中的请求参数名要一样否则获取不到
* 3. params = "bookId=100" 表示请求该方法时,必须给一个bookId参数,且值必须是100
*
* */
@RequestMapping(value = "/find",params = "bookId=100",method = RequestMethod.GET)
public String search(String bookId){
System.out.println("要买的书的id=="+bookId);
return "success";
}
1.2.4 @RequestMapping 支持Ant风格资源地址
- 说明
- ? : 配文件中的一个字符
- * :匹配文件中的任意字符
- ** :匹配多层路径
- Ant风格的URL地址举例
- /user/*/createUser:匹配 /user/aaa/createUser、/user/bbb/createUser等URL
- /user/**/createUser:匹配/user/createUser、/user/aaa/bbb/createUser等URL
- /user/createUser??:匹配/user/createUseraa 、/user/createUserbb等URL
1.2.4.1 代码
@RequestMapping(value = "message/**")
public String im(){
System.out.println("发送信息");
return "success";
}
======================================================================================
<h1>Ant的风格使用</h1>
<a href="user/message/aa">发送信息</a>
<a href="user/message/aa/dd">发送信息</a>
1.2.5 RequestMapping 可配合@PathVariable 映射URL绑定占位符
- 说明
- @RequestMapping还可以配合@PathVariable映射URL绑定占位符
- @PathVariable的value值要和占位符的名称一样,否则会报错
1.2.5.1 代码
/*
* 1.{userName} 表示一个占位符,名称为userName,在收到请求后会默认将Url中的值赋值
* 请求方法的形参,但是需要使用@PathVariable这个注解,且它的value值必须和占位符的
* 名称一样,否则会报错
* */
@RequestMapping(value = "/reg/{userName}/{userId}")
public String register(@PathVariable("username")String name,
@PathVariable("userId")Integer id){
System.out.println("注册成功=用户:" + name + "id:" +id);
return "success";
}
======================================================================================
<h1>PathVariable的使用</h1>
<a href="user/reg/lihua/200">发送信息</a>
1.2.6 注意事项和使用细节
- 映射的URL,不能重复
2. Rest优雅的Url请求风格
2.1 基本介绍
- REST:即Representational State Transfer。(资源)表现层状态状态转化。是目前流行的请求方式,它结构清晰,很多网站采用
- HTTP协议中,四个表示操作方式的动词:GET、POST 、PUT、 DELETE ,它们分别对应四种基本操作:GET用来获取资源,PSOT用来新建资源,PUT用来更新资源,DELETE用来删除资源
- 实例
- 传统方式
- getBook?id=1 GET
- delete?id=1 GET
- update POST
- update POST
- 说明:传统的Url是通过参数来说明crud的类型,rest是通过get/post/put/delete来说明crud的类型
- REST的核心过滤器
- 当前浏览器只支持post/get请求,因此为了得到put/delete的请求方式需要使用Spring提供的HiddenHttpMethodFilter过滤器进行转换
- HiddenHttpMethodFilter:浏览器form表单只支持GET与POST请求,而DELETE、PUT等Method并不支持,Spring添加了一个过滤器,可以将这些请求转换成为标准的HTTP方式,使得支持GET、POST、PUT、DELETE请求
- HiddenHttpMethodFilter能对POST请求方式进行转换,因此我们需要特别注意这一点
- 过滤器需要再web.xml中配置
2.2 代码实现
2.2.1 Controller
package com.leon.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* ClassName:BookHandler
* Package:com.leon.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/14 13:53
* @Version: 1.0
*/
@RequestMapping("/book")
@Controller
public class BookHandler {
@RequestMapping(value = "/get/{id}",method = RequestMethod.GET)
public String getBook(@PathVariable("id") Integer id) {
System.out.println("要查询的书的id = " + id);
return "success";
}
@PostMapping(value = "/post")
public String addBook( String name) {
System.out.println("要添加的书的name = " + name);
return "success";
}
@PutMapping(value = "/put")
public String updateBook( String name) {
System.out.println("要修改的书的name = " + name);
//return "success";
return "redirect:/book/success";//这个是重定向,会被解析成/springmvc/book/success
}
@DeleteMapping(value = "/delete/{id}")
public String deleteBook(@PathVariable("id") Integer id) {
System.out.println("要删除的书的id = " + id);
//return "success"; 这样会报错,因为浏览器解析不了DElETE请求
/*
* 1. redirect:/book/success 重定向
* 2. 这个会被视图解析器解析成:/springmvc/book/success 然后返回给浏览器
* */
return "redirect:/book/success";//这个是重定向,会被解析成/springmvc/book/success
}
@RequestMapping("/success")
public String success(){
return "success";
}
}
2.2.2 web.xml
<!--配置HiddenHttpMethodFilter
1.用与将POST请求转换为PUT、DELETE
-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<!--
1. /* 表示所有请求都需要经过HiddenHttpMethodFilter过滤器
-->
<url-pattern>/*</url-pattern>
</filter-mapping>
2.2.3 ioc文件:applicationcontext_ioc.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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置要扫描的包路径-->
<context:component-scan base-package="com.leon.controller"/>
<!--配置SpringMVC的视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀,是要访问的包-->
<property name="prefix" value="/WEB-INF/pages/"></property>
<!--配置后缀,是要访问的文件类型 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!--加入两个常规配置-->
<!--配置支持SpringMVC的高级功能,比如JSR303校验,映射动态请求-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--配置将SpringMVC不能处理的请求,交给Tomcat处理,比如css,js-->
<mvc:default-servlet-handler/>
</beans>
2.2.4 页面
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/4/14
Time: 13:48
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<script type="text/javascript" src="script/jquery-3.7.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
//获取id=deleteBook的a标签
$("#deleteBook").click(function (){
// 获取href的属性值
var href = this.href;
// 设置action属性
$("#hiddenFrom").attr("action",href);
// 给隐藏的input进行赋值
$("#hiddenFrom :hidden").val("DELETE");
// 提交数据
$("#hiddenFrom").submit();
// 取消默认行为
return false;
})
});
</script>
<body>
<h1>rest风格</h1>
<h2>rest风格查询一本书[get]</h2>
<a href="book/get/100">查询一本书</a>
<h2>rest风格添加一本书[post]</h2>
<form action="book/post" method="post">
<input type="text" name="name"><br>
<input type="submit" value="提交">
</form>
<h2>rest风格删除一本书[delete]</h2>
<a href="book/delete/100" id="deleteBook">删除一本书</a>
<form action="" method="post" id="hiddenFrom">
<input type="hidden" name="_method">
</form>
<h2>rest风格修改一本书[put]</h2>
<form action="book/put" method="post">
<input type="text" name="name"><br>
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="提交">
</form>
</body>
</html>
2.3注意事项和使用细节
- HiddenHttpMethodFilter,在将post转成DElETE/PUT请求时,是按_method参数名来读取的,然后再根据对应的值修改成DElETE/PUT请求
- 如果web项目是运行在Tomcat8及以上,会发现被过滤成DElETE和PUT请求,到达控制器时能顺利执行,但是返回时请求转发(forward)会报405的错误提示:消息JSP只允许GET、POST或HEAD
- 解决方式1:使用Tomcat7
- 解决方式2:将请求转发(forward)改为请求重定向(redirect):重定向到一个Handler,由Handler转发到页面
3. SpringMVC 映射请求数据
3.1 RequestParam获取参数值
3.1.1 代码
/*
* 1. @RequestParam 表示会接收提交的参数,它有value和required的属性
* 2. value = "name" 表示提交参数的参数名称为name
* 3. required = "false" 表示参数可以没有,默认是true,表示必须要有这个参数,如果没有则会报错
* 4. @RequestParam(value = "name",required = false) 表示请求的参数名可以和形参名称不一样也能获取到请求参数的值,
* 但是请求参数要和value的名称一样否则会赋值失败
* */
@RequestMapping(value = "/test1")
public String test01(@RequestParam(value = "name",required = false) String userName){
System.out.println("用户名称==" + userName);
return "success";
}
3.2 获取Http请求消息头
3.2.1 代码
/*
* 1. 如果想获取请求头的信息可以使用RequestHeader注解
* 2. value属性值表示你要获取请求头的什么部分的信息,然后赋值给形参
* */
@RequestMapping(value = "/test2")
public String test02(@RequestHeader("Accept-Encoding") String ae,
@RequestHeader("Host") String host){
System.out.println("Accept-Encoding ===>" + ae);
System.out.println("Host ===>" + host);
return "success";
}
3.3 获取JavaBean象
3.3.1 代码
/*
* 将提交过来的数据封装成JavaBean对象
* 1. 方法的形参用对应的类型(JavaBean对象)来指定即可,SpringMVC会自动完成封装
* 2. 如果使用自动完成封装,要求提交的数据的参数名和对象的属性名保持一致,否则会赋值失败,而对象的属性值则是默认值
* 3. 如果属性是对象,这里仍然是通过属性名.属性名,例如Master中的【pet】 ,即提交的数据参数名是:pet.id pet.name
* 这就是级联操作
* 4. 如果提交的数据的参数名和对象的属性名不匹配,则对象的属性就是默认值
* */
@RequestMapping(value = "/test3")
public String test03(Master master){
System.out.println(master);
return "success";
}
3.4 使用Servlet API
3.4.1 说明
- 开发中,我们可能需要使用到原生的Servlet API
- 使用Servlet API ,需要引入servlet-api.jar包
3.4.2 代码
/*
* 1. 如果想要使用Servlet的API直接在形参列表创建对应的对象
* */
@RequestMapping(value = "/test4")
public String test04(HttpServletRequest request,
HttpServletResponse response){
String name = request.getParameter("name");
String id = request.getParameter("id");
System.out.println("name==" + name);
System.out.println("id==" + id);
return "success";
}
3.4.3 使用注意事项
- 除了HttpServletRequest,HttpServletResponse还可以有其它对象,也可以用这样的形式获取例如:HttpSession,java.security.Principal,InputStream,OutputStream,Reader,Writer
- 其中一些对象也可以通过HttpServletRequest,HttpServletResponse对象获取,比如Session对象,既可以通过参数传入,也可以通过request.getSession()获取,效果一样,推荐使用参数形式传入,更加简单明了
4. 数据模型
4.1 模型数据处理-数据放入request域中
4.1.1 默认机制放入到request域中
/*
* 1. SpringMVC 会自动将提交过来的数据封装成JavaBean对象,然后再放入到request域中
* 并且在request域中的key的值是形参对应的类型(类名)名称首字母小写,value则是对象
* 2. 也可以理解成SpringMVC会自动的将Model模型,放入到request域中,其key值是其类型(类名)首字母小写
* 3. 在方法中修改master对象的属性值是会影响到request域中的数据的,因它们公用这一个引用
* */
@RequestMapping(value = "/test5")
public String test05(Master master){
return "request_view";
}
4.1.2 通过HttpServletRequest放入request域
/*
* 1. SpringMVC 会自动将提交过来的数据封装成JavaBean对象,然后再放入到request域中
* 并且在request域中的key的值是形参对应的类型名称首字母小写,value则是对象
* 2. 也可以理解成SpringMVC会自动的将Model模型,放入到request域中,其key值是其类型首字母小写
* 3. 在方法中修改master对象的属性值是会影响到request域中的数据的,因它们公用这一个引用
* */
@RequestMapping(value = "/test5")
public String test05(Master master,HttpServletRequest request){
try {
//设置编码
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
//保存数据到request中
request.setAttribute("address","天津");
//这里修改是会影响到request域中的数据的
master.setName("丽丽");
return "request_view";
}
4.1.3 通过请求的方法参数Map<String,Object> 放入到request域中
/*
* 1. SpringMVC 会自动将提交过来的数据封装成JavaBean对象,然后再放入到request域中
* 并且在request域中的key的值是形参对应的类型名称首字母小写,value则是对象
* 2. 也可以理解成SpringMVC会自动的将Model模型,放入到request域中,其key值是其类型首字母小写
* 3. 在方法中修改master对象的属性值是会影响到request域中的数据的,因它们公用这一个引用
* */
@RequestMapping(value = "/test6")
public String test06(Master master, Map<String,Object> map){
//添加数据进map集合中
/*
* 原理分析
* 1. 首先SpringMVC会将封装好的Master对象放入到map集合中
* 2. 然后再将Map集合中的数据进行遍历,查看收否数据
* 3. 如果有数据,则会将该数据放入到request域中,如果没有则不进行数据存放
* 4. 存放方式:map的key对应request域的key,map的value对应request域的value
* */
map.put("address","yunnan");
//如果这样操作则会将request域中的master置空
//map.put("master",null);
return "request_view";
}
4.1.4 通过返回ModelAndView放入到request域中
4.1.4.1 代码
@RequestMapping(value = "/test7")
public ModelAndView test07(Master master){
/*
* 原理分析
* 1. SpringMVC会将返回的ModelAndView进行扫描判断是否有数据
* 2. 如果有数据则将数据存放进Request域中,如果没有则不存放
* 3. 存放的格式是ModelAndView中的attributeName对应Request的key,
* attributeValue对应的是request的Value
* 4. 在默认情况下,如果你的方法返回的是String,其底层最后也是封装成一个ModelAndView对象
* */
//创建一个ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
//保存数据
modelAndView.addObject("address","beijing");
//设置视图,你要跳转的视图
modelAndView.setViewName("request_view");
return modelAndView;
}
4.1.4.2 注意事项
- 从本质看,请求响应的方法return "xx" ,是返回了一个字符串,其实其本质是返回了一个ModelAndView对象,只是默认被封装起来了
- ModelAndView既可以包含Model数据包,也可以包含视图信息
- ModelAndView对象的addObject方法可以添加key-val数据,默认在Request域中
- ModelAndView对象的setViewName方法可以设置视图名称
4.2 模型数据处理-数据放入Session域中
4.2.1 代码
@RequestMapping(value = "/test8")
public String test08(Master master,HttpSession session){
//将数据放入Session域中
session.setAttribute("master",master);
//设置数据
session.setAttribute("address","jiangxi");
return "request_view";
}
4.3 模型数据-@ModelAttribute
4.3.1 基本说明
- 开发中,有时需要使用某个前置方法(比如prepareXxx(),方法名由程序员定义)给目标方法准备一个模型对象
- @ModelAttribute注解可以实现这样的需求
- 在某个方法上,增加了@ModelAttribute属性后,调用这个类的任何一个方法时,都会先调用这个方法
4.3.2 代码
/*
* 1. 当Handler的方法被标识@ModelAttribute,就视为一个前置方法,
* 每当这个类中的其他方法被执行前都会执行这个方法
* 2. 这个方法类似于AOP中的前置通知
* */
@ModelAttribute
public void prepareModel(){
System.out.println("===================================prepareModel===================================");
}
@ModelAttribute最佳实践
- 修改用户信息(就是经典的使用这种机制的应用)流程如下:
- 在修改前,在前置方法中从数据库查询出这个用户
- 在修改方法(目标方法)中,可以使用前置方法从数据库查询的用户
- 如果表单中对用户的某个属性修改了,则以新的数据为准,如果没有修改,则以数据库的信息为准,比如,用户的某个属性不能修改,就保持原来的值
5. 视图和视图解析器
5.1 基本介绍
- 在SpringMVC中的目标方法最终返回都是一个视图(有各种视图)
- 返回的视图都会由一个视图解析器来处理(视图解析器有很多种)
5.2 代码
5.2.1 Handler(Controller)
package com.leon.viewresolver;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* ClassName:GoodsHandler
* Package:com.leon.viewresolver
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/16 16:08
* @Version: 1.0
*/
@RequestMapping(value = "/goods")
@Controller
public class GoodsHandler {
@RequestMapping(value = "/test1")
public String test01(){
System.out.println("GoodsHandler===test01()");
//返回自定以视图,这里需要是自定义视图的Bean的id
return "goodsView";
}
}
5.2.2 view视图
package com.leon.viewresolver;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* ClassName:GoodsView
* Package:com.leon.viewresolver
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/16 16:10
* @Version: 1.0
*/
@Component
public class GoodsView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("自定义的视图······");
// 跳转页面
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
}
}
5.2.3 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置要扫描的包路径-->
<context:component-scan base-package="com.leon"/>
<!--配置SpringMVC的视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀,是要访问的包-->
<property name="prefix" value="/WEB-INF/pages/"></property>
<!--配置后缀,是要访问的文件类型 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!--加入两个常规配置-->
<!--配置支持SpringMVC的高级功能,比如JSR303校验,映射动态请求-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--配置将SpringMVC不能处理的请求,交给Tomcat处理,比如css,js-->
<mvc:default-servlet-handler/>
<!--配置自定视图解析器
1. 配置自定义视图解析器BeanNameViewResolver
2. BeanNameViewResolver可以去解析我们自定义的视图
3. 配置 order属性,表示视图解析器执行的顺序,值越小,优先级越高,
order属性的默认值是最低优先级,值为Integer.MAX_VALUE
-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" >
<property name="order" value="99"/>
</bean>
</beans>
5.2.4 自定义视图步骤
- 自定义视图:创建一个View的Bean,该Bean需要继承自AbstractView,并实现renderMergedOutputModel方法
- 并把自定义View加入到IOC容器中
- 自定义视图的视图处理器,使用BeanNameViewResolver,这个视图处理器也需要配置到IOC容器
- BeanNameViewResolver的调用优先级需要设置一下,设置order比Integer.MAX_VALUE小的值,以确保其在InternalResourceViewResolver之前被调用
5.2.5 自定义视图解析器执行流程
- SpringMVC调用目标方法,返回自定义View在IOC容器中的id
- SpringMVC调用BeanNameViewResolver解析视图:从IOC容器中获取,自定义的View的对象(通过目标返回的id值对应的Bean)
- SpringMVC调用自定义视图的renderMergedOutputModel方法渲染视图
- 如果在SpringMVC调用目标方法,返回自定义View在IOC容器中的id,该View不存在,则仍然按照默认的视图处理机制处理
- 注意:如果默认的视图解析器优先级更高,则不管成功或者失败都不会执行自定义的视图解析器
6. 请求转发或者重定向
6.1 代码
@RequestMapping(value = "/test2")
public String test02(){
/*
* 解析
* 1. forward关键字,表示请求转发 redirect关键字,表示重定向
* 2. 注意重定向不能访问WEB-INF,否则会报错
* 3. return "forward:/WEB-INF/pages/success.jsp" 会被解析成这个URL:
* http://localhost:8080/springmvc/WEB-INF/pages/success.jsp
* 4. redirect:/login.jsp 会被解析成这个URL:http:/localhost:8080/springmvc/login.jsp
* 5. redirect:/login.jsp 这个可能会和之前学习的路径有冲突,之前学的没有错,是因为SpringMVC将/login.jsp 解析成
* /springmvc/login.jsp返回给浏览器
* */
//请求转发
//return "forward:/WEB-INF/pages/success.jsp";
//会报错,访问不了WEB-INF
//return "redirect:/WEB-INF/pages/success.jsp";
//重定向
return "redirect:/login.jsp";
}
7. 数据格式化
7.1 基本介绍
在我们提交数据(比如表单时)SpringMVC怎样对提交的数据进行装换和处理的?
基本数据类型可以和字符串之间自动完成装换,比如:
SpringMVC 上下文中内建了很多装换器,可能完成大多数Java类型的转换工作。【相互转换,这里只列出部分】
转换 装换类 java.lang.Boolean ---> java.lang.String ObjectToStringConverter java.lang.Character ---> java.lang.Number CharacterToNumberFactory java.lang.Character ---> java.lang.String ObjectToStringConverter java.lang.Enum ---> java.lang.String EnumToStringConverter java.lang.Number ---> java.lang.Character NumberToCharacterConverter java.lang.Number---> java.lang.Number NumberToNumberConverterFactory java.lang.Number---> java.lang.String ObjectToStringConverter java.lang.String---> java.lang.Boolean StringToBooleanConverter java.lang.String---> java.lang.Character StringToCharacterConverter java.lang.String---> java.lang.Enum StringToEnumConverterFactory java.lang.String---> java.lang.Number StringToNumberConverterFactory java.lang.String---> java.lang.Locale StringToLocalConverter java.lang.String---> java.lang.Properties StringToPropertiesConverter java.lang.String---> java.lang.UUID StringToUUIDConverter java.lang.Locale---> java.lang.String ObjectToStringConverter java.lang.Properties---> java.lang.String PropertiesToStringConverter java.lang.UUID---> java.lang.String ObjectToStringConverter
7.2 基本数据类型和字符串自定转换
7.2.1 Controller
package com.leon.dataconverter;
import com.leon.dataconverter.entity.Monster;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
/**
* ClassName:MonsterHandler
* Package:com.leon.dataconverter
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/26 9:43
* @Version: 1.0
*/
/*
* @Scope(value = "prototype") 表示每次请求MonsterHandler时都会生成一个新的对象
* */
@RequestMapping(value = "monsterConversion")
@Controller
@Scope(value = "prototype")
public class MonsterHandler {
@RequestMapping(value = "/add")
//显示添加monster的界面
public String addMonsterUI(Map<String, Object> map) {
/*
* 解读
* 1. 这里的表单,我们使用SpringMVC的标签来完成
* 2.SpringMVC 表单标签在显示之前必须在request域中有一个bean,该bean的属性要和表单标签的path对应
* request中的key对应form标签的modelAttribute属性值,例如monster。
* 解读:意思是,如果在request域中存放Monster对象,Monster对象在request域中对应的key值,要和modelAttribute
* 的属性值一样,也就是保持对应,并且其属性名称要和form标签中的path属性值一样
* */
map.put("monster", new Monster());
/*
* 1. 如果跳转的页面使用了SpringMVC的form标签,就需要准备一个对象,放入request域中,这个对象的key值monster对应的
* 是SpringMVC表单标签的modelAttribute="monster"
* */
return "dataconverter/monster_addUI";
}
@RequestMapping(value = "/save")
public String saveMonster(Monster monster) {
System.out.println("==================Monster================== :"+monster);
return "dataconverter/add_success";
}
}
7.2.2 entity
package com.leon.dataconverter.entity;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import java.util.Date;
/**
* ClassName:Monster
* Package:com.leon.dataconverter.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/26 9:22
* @Version: 1.0
*/
public class Monster {
private String id;
private String name;
private Integer age ;
private String email ;
//特殊格式的装换
/*
* 1.使用NumberFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
*
* */
@NumberFormat(pattern = "##,###.##")
private float salary ;
//特殊格式的装换
/*
* 1.使用DateTimeFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
*
* */
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
public Monster() {
}
public Monster(String id, String name, Integer age, String email, float salary, Date birthday) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
this.salary = salary;
this.birthday = birthday;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Monster{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", salary=" + salary +
", birthday='" + birthday + '\'' +
'}';
}
}
7.2.3 前端主页面
<%@ taglib prefix="from" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/4/26
Time: 9:31
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--
为了方便回显这里使用,SpringMVC的标签来完成
1. SpringMVC 表单标签在显示之前必须在request域中有一个bean,该bean的属性要和表单标签的path对应
2. SpringMVC 的 form:form 标签的action 属性值中的第一个 / 代表WEB应用的根目录
--%>
<h3>添加妖怪</h3>
<from:form action="save" method="post" modelAttribute="monster">
妖怪名字:<form:input path="name"/><br><br>
妖怪年龄:<from:input path="age"/><br><br>
妖怪生日:<from:input path="birthday"/><br><br>
妖怪工资:<from:input path="salary"/><br><br>
电子邮件:<from:input path="email"/><br><br>
<input type="submit" value="添加妖怪">
</from:form>
</body>
</html>
7.2.4 测试图片
7.3 特殊数据类型和字符串间的装换
- 特殊格式的转换需要使用注解,这里使用了【@NumberFormat(pattern = "##,###.##")】和【@DateTimeFormat(pattern = "yyyy-MM-dd")】等
- 这些注解需要标注到要转换的JavaBean的属性上面,pattern的属性值是要转换的格式,如果没有按照指定的格式传输数据会报错
- 格式转换的注解只负责格式的转换,并不负责验证不要搞混了
- 注意:被标注的属性是被最终转换成的类型,所以要转换的类型要和注解所转换的类型匹配,否则会失效
7.3.1 代码同基本数据类型的转换一样,只不过是在指定的属性上添加了注解
//特殊格式的装换
/*
* 1.使用NumberFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
*
* */
@NumberFormat(pattern = "##,###.##")
private float salary ;
//特殊格式的装换
/*
* 1.使用DateTimeFormat注解进行格式转换,pattern值是格式要求,如果没有满足要求就会报错
*
* */
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
7.3.1 测试图片
8. 验证以及国际化
3.1 概述
对于输入的数据(比如表单数据),进行必要的验证,并给出相应的提示信息
对于验证表单数据,SpringMVC提供了很多实用的注解,这些注解由JSR 303验证框架提供
JSR 303 验证框架
JSR 303 是Java为Bean数据合法性校验提供的标准框架,它已经包含在JavaEE中
JSR 303 通过在Bean属性上标注类似于@NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证
JSR 303 提供的基本验证注解有:
注解 功能说明 @Null 被注解的元素必须为null @NotNull 被注释的元素必须不为null @AssertTrue 被注释的元素必须为true @AssertFalse 被注释的元素必须为false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字(小数),其值必须大于等于指定的最小值 @DecimalMin(value) 被注释的元素必须是一个数字(小数),其值必须小于等于指定的最大值 @Size(max,min) 被注释的元素的大小必须在指定的范围内 @Digits(integer,fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注释的元素必须是一个过去的日期 @Future 被注释的元素必须是一个将来的日期 @Pattern(value) 被注释的元素必须符合指定的正则表达式 Hibernate Validator扩展注解
Hibernate Validator 和Hibernate没有关系,只是JSR 303 实现的一个扩展
Hibernate Validator 是JSR 303 的一个参考实现,除了支持所有的标注校验注解外,它还支持以下的扩展注解
注解 功能说明 被注释的元素必须是电子邮箱地址 @Length 被注释的字符串的大小必须在指定的范围内(也就是长度) @NotEmpty 被注释的字符串必须是非空(还可以修饰其他的元素,详细看NotEmpty类) @Range 被注释的元素必须在合适的范围内
3.2 数据验证
- 需要在目标JavaBean的属性上标注验证的注解,然后在处理器的目标方法的形参列表中,在目标JavaBean的前面标注一个@Valid,Springmvc底层会自动处理和验证
- 每一个验证注解都有自己的一个message()属性,这个是用来发生错误时的提示信息,它们有默认的值,可以自己定义的错误验证信息
- 注解之间可以搭配使用
3.3 国际化
需要在IOC配置文件中配置一个bean,这个类的名字是:org.springframework.context.support.ResourceBundleMessageSource还需要配置一个basename属性,属性值是你配置在src下的properties文件的名称,一般定义为:i18nxxxx.properties
特别注意配置文件中的属性命名要求,验证类.request域中的key值.属性名称,例如:NotEmpty.monster.name,注意要是Unicode编码格式,完整的样式是
#用户名不能为空 NotEmpty.monster.name= \u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
3.4 代码
/*
* 1. @Valid Monster monster 表示对monster接收的数据进行校验
* 2. Errors errors 表示如果校验出现错误,将校验的错误信息保存errors
* 3. Map<String,Object> map 表示如果校验出现错误,将校验的错误信息保存map同时保存monster对象
* 4. 校验发生的时机:在Springmvc底层,反射调用目标方法时,会接收到Http请求的数据,然后根据注解来进行验证,
* 在验证过程中,如果出现了错误,就把错误信息填充errors和map。
* */
@RequestMapping(value = "/validate")
public String validateMonster(@Valid Monster monster , Errors errors,Map<String, Object> map) {
System.out.println("-------Monster:" + monster);
System.out.println("==============================map==============================");
//遍历集合
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("==============================Error==============================");
//获取所有的错误信息
List<ObjectError> allErrors = errors.getAllErrors();
//遍历错误信息
for (ObjectError allError : allErrors) {
System.out.println(allError);
}
return "dataconverter/monster_addUI";
}
======================================================================================
<!--配置国际化错误信息的资源处理bean-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<!--
配置国际化文件名字
1. 如果这样配置,表示messageSource去src/i18nxxxx.properties去读取错误信息
-->
<property name="basename" value="i18n"/>
</bean>
======================================================================================
#用户名不能为空
NotEmpty.monster.name= \u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
#请输入正确的范围
Range.monster.age= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u8303\u56f4
#请输入正确的生日格式
typeMismatch.monster.birthday= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u751f\u65e5\u683c\u5f0f
#请输入正确的薪资
typeMismatch.monster.salary= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u85aa\u8d44
3.5 数据验证和国际化细节
- 在需要验证的JavaBean/pojo的字段加上相应的验证注解
- 目标方法上,在JavaBean/pojo类型的参数前,添加@Valid注解,告知Springmvc该bean是需要验证的
- 在@Valid 注解之后,添加一个Errors或BindingResult类型的参数,可以获取到错误信息
- 需要使用<form:errors path="email"/>标签来显示错误消息,这个标签,需要写在<form:form > 标签内生效
- 错误消息的国际化文件i18n.properties,中文需要是Unicode编码,使用工具转码
- 格式:验证规则.表单modelAttribute值.属性名=消息
- NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
- typeMismatch.monster.birthday= \u8bf7\u8f93\u5165\u6b63\u786e\u7684\u751f\u65e5\u683c\u5f0f
- 注解@NotNull和@NotEmpty的区别说明
- 查看源码可以知道:@NotEmpty {Asserts that the annotated string, collection, map or array is not null or empty.}
- 查看源码可以知道:@NotNull {The annotated element must not be null. Accepts any type.}
- 有的时候验证注解会组合使用,因为它们功能单一,组合起来效果更好
3.6 数据类型转换校验核心类
SpringMVC通过反射机制对目标方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下
3.7 取消属性绑定
3.7.1 说明
- 在默认情况下,表单提交的数据会和pojo类型的JavaBean属性绑定,如果程序员在开发中,希望取消某个属性绑定,也就是说,不希望接收到某个表单属性的值,则可以通过@InitBinder注解取消绑定
- 编写一个方法,使用@InitBinder标识的该方法,可以对WebDataBinder对象进行初始化
- WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定
- @InitBinder方法不能有返回值,它必须声明为void
- @InitBinder方法的参数通常是WebDataBinder
3.7.2 代码
//取消绑定Monster的name表单提交的值给monster.name属性
@InitBinder
public void initBinder(WebDataBinder binder) {
/*
* 1. 方法上需要标注@InitBinder springmvc底层会初始化这个方法的形参WebDataBinder
* 2. 调用WebDataBinder.setDisallowedFields("name")表示取消指定属性的绑定
* 即:当表单提交字段name时,就不在把接收到的name值,填充到model数据monster的name属性
* 3. 机制:Springmvc在底层通过反射调用目标方法时。接收到Http请求的参数和值,使用反射+注解技术
* 取消对指定属性的填充
* 4. setDisallowedFields支持可变参数,可以填写多个字段
* 5. 如果我们取消某个属性绑定,验证就没有意义了,应当把验证的注解去掉
* */
binder.setDisallowedFields("name");
}
3.7.3 注意事项和细节说明
- setDisallowedFields() 是可变形参,可以指定多个字段
- 当将一个字段/属性,设置为disallowed,就不在接收表单提交的值,那么这字段/属性的值,就是该对象默认的值(具体看程序员定义时指定)
- 一般来说,如果不接收表单字段提交数据,则该对象字段的验证也就没有意义了可以注销掉
9.中文乱码处理
9.1 自定义中文乱码过滤器
9.1.1 代码
package com.leon.filter;
import javax.servlet.*;
import java.io.IOException;
/**
* ClassName:MyCharacterFilter
* Package:com.leon.filter
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/27 10:28
* @Version: 1.0
*/
public class MyCharacterFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//设置编码
servletRequest.setCharacterEncoding("UTF-8");
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
======================================================================================
<!--
配置处理中文乱码的过滤器
1.拦截所有请求,处理编码,提醒:如果是比较重要的过滤器,要配置在web.xml前面
-->
<filter>
<filter-name>MyCharacterFilter</filter-name>
<filter-class>com.leon.filter.MyCharacterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyCharacterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>1
9.2 Spring提供的过滤器处理中文
9.2.1 代码
<!--配置Spring提供的过滤器,解决中文乱码-->
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
11. 处理json和HttpMessageConverter< T>
11.1 处理JSON-@ResponseBody
- 需要在目标方法上标注注解@ResponseBody,表示这个方法返回的数据是以json的格式返回的
- 底层通过反射+io等来实现的,要注意这个注解只是将方法返回的数据以json格式返回,而不是把请求过来的数据封装成JavaBean或者json数据
- 返回的数据可以是集合
- @ResponseBody可以修饰Handler类,@Controller和@ResponseBody可以组合成@RestController,这个是注解包含这个两个注解的功能
11.1.1 代码
package com.leon.json;
import com.leon.json.entity.Dog;
import com.leon.json.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* ClassName:JsonHandler
* Package:com.leon.json
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/27 13:50
* @Version: 1.0
*/
@RequestMapping(value = "/json")
//@Controller
//@ResponseBody
@RestController
public class JsonHandler {
/*
* 1. @ResponseBody注解表示返回的数据是以json的格式
*
* */
@RequestMapping(value = "/dog")
//@ResponseBody
public Dog jsonDog(){
Dog dog = new Dog();
dog.setName("大黄");
dog.setAddress("小明家");
return dog ;
}
}
======================================================================================
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/4/27
Time: 13:45
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<script type="application/javascript" src="script/jquery-3.7.1.min.js"></script>
<script type="application/javascript">
$(document).ready(function () {
$("#requestJson").click(function () {
// 获取href元素值
var url = this.href;
// 为了每次都是新的请求,这里返回时间数据
var data = new Date();
$.ajax({
url:url,
type:"POST",
data:data,
success:function (data) {
console.log(data);
},
dataType:"json"
})
// 取消默认行为
return false ;
});
$("#submitJson").click(function () {
//创建一个Url
let url ="/springmvc/json/user";
//获取用户
let name = $("#name").val();
//获取年龄
let age = $("#age").val();
let data =JSON.stringify( {name:name,age:age});
$.ajax({
url:url,
type: "POST",
data:data,
success:function (data) {
console.log(data);
},
// 设置返回的数据格式
contentType:"application/json;charset=utf-8"
});
});
})
</script>
<body>
<h1>请求一个json数据</h1>
<a href="<%=request.getContextPath()%>/json/dog" id="requestJson">点击获取JSON数据</a>
<h1>发出一个json数据</h1>
u:<input type="text" id="name" name="username"><br><br>
a:<input type="text" id="age" name="age"><br><br>
<button id="submitJson">提交</button>
</body>
</html>
11.2 处理JSON-@RequestBody
- 需要标注在目标方法的形参中,要被封装成的目标bean形参之前,表示将请求过来的json数据封装成指定的JavaBean
- 底层是通过反射+io等实现的,需要注意这个注解只是将请求过来的数据封装成指定的对象类型,而不是把请求过来的数据封装成json格式数据。
- 封装的数据可以是集合
- 注意:被封装的对象的属性要和json中的key值对应,否则会封装失败
11.2.1 代码
package com.leon.json;
import com.leon.json.entity.Dog;
import com.leon.json.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* ClassName:JsonHandler
* Package:com.leon.json
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/27 13:50
* @Version: 1.0
*/
@RequestMapping(value = "/json")
//@Controller
//@ResponseBody
@RestController
public class JsonHandler {
/*
* 1. @ResponseBody注解表示返回的数据是以json的格式
*
* */
@RequestMapping(value = "/dog")
//@ResponseBody
public Dog jsonDog(){
Dog dog = new Dog();
dog.setName("大黄");
dog.setAddress("小明家");
return dog ;
}
/*
* 1.@RequestBody表示将请求的过来的json格式的数据封装成一个UserJavaBean
* */
@RequestMapping(value = "/user")
//@ResponseBody
public List<User> jsonUser(@RequestBody List<User> users){
System.out.println("user:===>" + users);
return users;
}
}
======================================================================================
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/4/27
Time: 13:45
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<script type="application/javascript" src="script/jquery-3.7.1.min.js"></script>
<script type="application/javascript">
$(document).ready(function () {
$("#requestJson").click(function () {
// 获取href元素值
var url = this.href;
// 为了每次都是新的请求,这里返回时间数据
var data = new Date();
$.ajax({
url:url,
type:"POST",
data:data,
success:function (data) {
console.log(data);
},
dataType:"json"
})
// 取消默认行为
return false ;
});
$("#submitJson").click(function () {
//创建一个Url
let url ="/springmvc/json/user";
//获取用户
let name = $("#name").val();
//获取年龄
let age = $("#age").val();
let data =JSON.stringify( {name:name,age:age});
$.ajax({
url:url,
type: "POST",
data:data,
success:function (data) {
console.log(data);
},
// 设置返回的数据格式
contentType:"application/json;charset=utf-8"
});
});
})
</script>
<body>
<h1>请求一个json数据</h1>
<a href="<%=request.getContextPath()%>/json/dog" id="requestJson">点击获取JSON数据</a>
<h1>发出一个json数据</h1>
u:<input type="text" id="name" name="username"><br><br>
a:<input type="text" id="age" name="age"><br><br>
<button id="submitJson">提交</button>
</body>
</html>
11.3 HttpMessageConverter机制介绍
11.3.1 基本说明
SpringMVC处理JSON底层实现是依靠HttpMessageConverter< T>进行转换的
工作机制图
处理JSON-底层实现(HttpMessageConverter< T>)
- 使用HttpMessageConverter< T>将请求信息转化并绑定到处理方法的入参中,或将响应结果转为对应类型的响应信息,Spring提供了两种途径
- 使用@ResponseBody/@RequestBody对目标方法进行标注
- 使用HttpEntity< T>/ResponseEntity< T>作为目标方法的入参或返回值
当控制器处理方法使用到@RequestBody/@ResponseBody或HttpEntity< T>/ResponseEntity< T>时,Spring首先根据请求头或者响应头的Accept属性选择匹配的HttpMessageConverter,进而根据参数类型或者泛型类型的过滤得到匹配的HttpMessageConverter,若找不到可用的HttpMessageConverter将报错
12.文件的下载
12.1 基本说明
- 需要把目标方法的返回值设置成ResponseEntity< T>,这样是告诉SpringMVC你返回的是一个文件
- 创建ResponseEntity类需要三个参数分别是:① Http的响应头Headers ②Http响应状态 ③下载的文件数据
- 方法的形参可以设置一个HttpSession方便获取文件的绝对路径
- 建议将泛型设置成byte类型,因为可以读成二进制文件,更方便
12.2 代码
@RequestMapping(value = "downFile")
public ResponseEntity<byte[]> downFile(HttpSession session){
//构建一个ResponseEntity 对象 需要: 1. Http的响应头Headers 2.Http响应状态 3. 下载的文件数据
//获取要下载的文件的绝对路径
InputStream resourceAsStream = session.getServletContext().getResourceAsStream("/img/background.jpg");
try {
//创建一个byte数组,resourceAsStream.available()表示获取文件的大小
byte[] bytes = new byte[resourceAsStream.available()];
//读取资源
resourceAsStream.read(bytes);
//创建返回的HttpStatus
HttpStatus ok = HttpStatus.OK;
//创建需要的Headers
HttpHeaders headers = new HttpHeaders();
//设置如何处理响应的内容
headers.add("Content-Disposition",
"attachment;filename=background.jpg");
//创建ResponseEntity
/*
* public ResponseEntity(@Nullable T body,
* @Nullable MultiValueMap<String, String> headers, HttpStatus status) {} 需要的构造器参数
* */
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes,headers,ok);
//返回数据
return responseEntity;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
13.文件的上传
13.1 基本介绍
- SpringMVC为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver实现的,Spring用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver
- SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件的上传工作,如果想使用Spring的文件上传功能,需要在上下文中配置MultipartResolver
13.2 代码
package com.leon.fileUpload;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
/**
* ClassName:FileUploadHandler
* Package:com.leon.fileUpload
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/3 19:53
* @Version: 1.0
*/
@Controller
@RequestMapping(value = "/file")
public class FileUploadHandler {
@RequestMapping(value = "/upload")
public String handleFileUpload(MultipartFile file,
HttpServletRequest request) {
//获取文件名
String filename = file.getOriginalFilename();
System.out.println("你上传的文件名称" + filename);
//获取保存的绝对路径
String path = request.getServletContext().getRealPath("/img/" + filename);
//创建文件
File saveToFile = new File(path);
//将上传的文件,转存到saveToFile
try {
file.transferTo(saveToFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
return "success";
}
}
======================================================================================
<%--
Created by IntelliJ IDEA.
User: ZGJ
Author: leon --> ZGJ
Version: 1.0
Date: 2024/5/3
Time: 19:46
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<h1>文件上传演示</h1>
<form action="<%=request.getContextPath()%>/file/upload" method="post" enctype="multipart/form-data">
文件介绍:<input type="text" name="introduce"><br>
选择文件:<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
=====================================================================================
<!--配置SpringMVC的文件上传解析器
1. 这个bean的id是通过MultipartResolver这个接口来获取的,所以id名必须设置成multipartResolver
-->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
13.3 注意事项
- 在配置CommonsMultipartResolver时要注意一个问题,因为SpringMVC是通过MultipartResolver接口来获取CommonsMultipartResolver的,所以id的名称必须是multipartResolver否则会报错
- 在Controller类中的文件上传方法中,MultipartFile 这个形参是一定要的,它的实现接口子类封装了文件上传的方法,这个形参的名称要和请求参数的file的类型的名称一样,否则会上传失败,也可以使用RequestParam注解来指定
- 注意在form表单中要设置为POST请求,因为POST请求对数据大小理论上没有限制,数据格式也要设置成enctype="multipart/form-data"
14. 自定义拦截器
14.1 自定义单个拦截器
14.1.1 说明
- SpringMVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能
- 自定义的拦截器必须实现HandlerIntercepior接口
- 自定义拦截器的三个方法
- preHandler():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理
- psotHandler():这个方法在目标方法处理完请求之后执行
- afterCompletion():这个方法在完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作
14.1.2 自定义拦截器流程分析
- 自定义拦截器执行流程说明
- 如果preHandle 方法,返回false,则不再执行目标方法,可以在此指定返回页面
- postHandle在目标方法被执行后执行,可以在方法中访问到目标方法返回的ModelAndView对象
- 若preHandle返回true,则afterCompletion 方法,在渲染视图之后被执行
- 若preHandle返回false,则afterCompletion 方法,不会被嗲用
- 配置拦截器时,可以指定该拦截器对那些请求生效,哪些请求不生效
14.1.3 代码
package com.leon.interceptor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* ClassName:FurnHandler
* Package:com.leon.interceptor
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/3 21:23
* @Version: 1.0
*/
@Controller
@RequestMapping(value = "/furn")
public class FurnHandler {
@RequestMapping(value = "/hi")
public String hi() {
System.out.println("====================FurnHandler[hi()]===================");
return "success";
}
@RequestMapping(value = "/hello")
public String holle() {
System.out.println("====================FurnHandler[holle()]===================");
return "success";
}
}
======================================================================================
package com.leon.interceptor;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ClassName:MyInterceptor01
* Package:com.leon.interceptor
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/3 21:10
* @Version: 1.0
*/
@Controller
public class MyInterceptor01 implements HandlerInterceptor {
/*
* 1. 该方法在执行目标方法之前被执行
* 2. 如果该方法返回为false,则不会在执行目标方法
* 3. 该方法可以获取到request,Response,目标Handler
* 4. 这里可以根据业务要求跳转到指定的页面
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor01 ==================preHandle()==================");
return true;
}
/*
* 1. 该方法在执行目标方法之后被执行
* 2. 该目标方法可以获取到目标方法返回的ModelAndView对象,也可以获取到目标Handler
* */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor01 ==================postHandle()==================");
}
/*
* 1.该方法在执行完DispatcherServlet的render方法之后被执行
**/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor01 ==================afterCompletion()==================");
}
}
======================================================================================
<!--配置拦截器-->
<mvc:interceptors>
<!--
1. 第一种配置方式:直接使用ref,引用对应的拦截器
2. 这种方式会拦截所有的目标方法
-->
<!--<ref bean="myInterceptor01"/>-->
<!--
1.第二种配置方式
mvc:mapping path="/hi" 指定要拦截的路径
通过ref来指定自定义拦截器
-->
<!--<mvc:interceptor>-->
<!-- <mvc:mapping path="/furn/hi"/>-->
<!-- <ref bean="myInterceptor01"/>-->
<!--</mvc:interceptor>-->
<!--
1.第二种配置方式:可以使用通配符来进行路径拦截
mvc:mapping path="/furn/h*" 表示要拦截的路径
mvc:exclude-mapping path="/furn/hi" 表示要排除哪个路径,可以使用通配符
通过ref来指定自定义拦截器
-->
<mvc:interceptor>
<mvc:mapping path="/furn/h*"/>
<mvc:exclude-mapping path="/furn/h?"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
14.1.4 注意事项和细节
默认配置是所有的目标方法都进行拦截,也可以指定拦截目标方法,比如只是拦截hi
<!-- 1.第二种配置方式 mvc:mapping path="/hi" 指定要拦截的路径 通过ref来指定自定义拦截器 --> <mvc:interceptor> <mvc:mapping path="/furn/hi"/> <ref bean="myInterceptor01"/> </mvc:interceptor>
mvc:mapping 支持通配符,同时可以指定不对哪些目标方法进行拦截
<!-- 1.第二种配置方式:可以使用通配符来进行路径拦截 mvc:mapping path="/furn/h*" 表示要拦截的路径 mvc:exclude-mapping path="/furn/hi" 表示要排除哪个路径,可以使用通配符 通过ref来指定自定义拦截器 --> <mvc:interceptor> <mvc:mapping path="/furn/h*"/> <mvc:exclude-mapping path="/furn/h?"/> <ref bean="myInterceptor01"/> </mvc:interceptor>
拦截器需要配置才生效,不配置是不生效的
如果preHandle()方法返回了false,就不会执行目标方法(前提是目标方法被拦截了),程序员可以在这个方法中根据业务指定需要跳转的页面
14.2 自定义多个拦截器
14.2.1 自定义多个拦截器执行流程
14.2.2 代码
package com.leon.interceptor;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ClassName:MyInterceptor01
* Package:com.leon.interceptor
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/3 21:10
* @Version: 1.0
*/
@Controller
public class MyInterceptor01 implements HandlerInterceptor {
/*
* 1. 该方法在执行目标方法之前被执行
* 2. 如果该方法返回为false,则不会在执行目标方法
* 3. 该方法可以获取到request,Response,目标Handler
* 4. 这里可以根据业务要求跳转到指定的页面
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor01 ==================preHandle()[01]==================");
return true;
}
/*
* 1. 该方法在执行目标方法之后被执行
* 2. 该目标方法可以获取到目标方法返回的ModelAndView对象,也可以获取到目标Handler
* */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor01 ==================postHandle()[01]==================");
}
/*
* 1.该方法在执行完DispatcherServlet的render方法之后被执行
**/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor01 ==================afterCompletion()[01]==================");
}
}
======================================================================================
package com.leon.interceptor;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ClassName:MyInterceptor01
* Package:com.leon.interceptor
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/3 21:10
* @Version: 1.0
*/
@Controller
public class MyInterceptor02 implements HandlerInterceptor {
/*
* 1. 该方法在执行目标方法之前被执行
* 2. 如果该方法返回为false,则不会在执行目标方法
* 3. 该方法可以获取到request,Response,目标Handler
* 4. 这里可以根据业务要求跳转到指定的页面
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor02 ==================preHandle()[02]==================");
return true;
}
/*
* 1. 该方法在执行目标方法之后被执行
* 2. 该目标方法可以获取到目标方法返回的ModelAndView对象,也可以获取到目标Handler
* */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor02 ==================postHandle()[02]==================");
}
/*
* 1.该方法在执行完DispatcherServlet的render方法之后被执行
**/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor02 ==================afterCompletion()[02]==================");
}
}
======================================================================================
package com.leon.interceptor;
import com.leon.json.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* ClassName:FurnHandler
* Package:com.leon.interceptor
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/3 21:23
* @Version: 1.0
*/
@Controller
@RequestMapping(value = "/furn")
public class FurnHandler {
@RequestMapping(value = "/hi")
public String hi() {
System.out.println("====================FurnHandler[hi()]===================");
return "success";
}
@RequestMapping(value = "/hello")
public String holle(User user) {
System.out.println("====================FurnHandler[holle()]===================");
return "success";
}
}
======================================================================================
<!--配置拦截器-->
<mvc:interceptors>
<!--
1. 第一种配置方式:直接使用ref,引用对应的拦截器
2. 这种方式会拦截所有的目标方法
-->
<!--<ref bean="myInterceptor01"/>-->
<!--
1.第二种配置方式
mvc:mapping path="/hi" 指定要拦截的路径
通过ref来指定自定义拦截器
-->
<!--<mvc:interceptor>-->
<!-- <mvc:mapping path="/furn/hi"/>-->
<!-- <ref bean="myInterceptor01"/>-->
<!--</mvc:interceptor>-->
<!--
1.第二种配置方式:可以使用通配符来进行路径拦截
mvc:mapping path="/furn/h*" 表示要拦截的路径
mvc:exclude-mapping path="/furn/hi" 表示要排除哪个路径,可以使用通配符
通过ref来指定自定义拦截器
-->
<mvc:interceptor>
<mvc:mapping path="/furn/h*"/>
<mvc:exclude-mapping path="/furn/h?"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
<!--
配置多个拦截器
-->
<mvc:interceptor>
<mvc:mapping path="/furn/hello"/>
<ref bean="myInterceptor02"/>
</mvc:interceptor>
</mvc:interceptors>
14.2.3 注意事项和使用细节
- 如果第1个拦截器的preHandle()返回false,后面不执行
- 如果第2个拦截器的preHandle()返回false,就直接执行第1个拦截器的afterCompletion()方法,如果拦截器更多,规则类似。
- 说明:前面说的规则,都是目标方法被拦截的
15. 异常处理
15.1 基本介绍
- SpringMVC 通过HandlerExceptionResolver处理程序的异常,包括Handler 映射、数据绑定以及目标方法执行时发生的异常
- 主要处理Handler中用@ExceptionHandler注解标注的定义的方法
- ExceptionHandlerMethodResolver内部若找不到@ExceptionHandler注解的话,会找@ControllerAdvice类的@ExceptionHandler注解方法吗,这样就相当于一个全局异常处理器
15.2 局部异常
15.2.1 代码
package com.leon.exception;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
/**
* ClassName:MyExceptionHandler
* Package:com.leon.exception
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/4 15:21
* @Version: 1.0
*/
@Controller
@RequestMapping(value = "/exception")
public class MyExceptionHandler {
/*
* 1. localException()用于处理局部异常的方法
* 2. 这里只处理了ArithmeticException和NullPointerException异常
* 3. 形参是Exception,SpringMVC会自动的传入生成的异常给Exception,通过Exception形参可以获取到
* 异常信息
*
* */
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String localException(Exception e, HttpServletRequest request) {
System.out.println("异常信息是: " + e.getMessage());
//将信息保存到request域中
request.setAttribute("reason", e.getMessage());
return "forward:/error.jsp";
}
@RequestMapping(value ="/test01")
public String test01(int num){
int i = 9 / num;
return "success";
}
}
15.2.1 注意事项和细节
- 如果不确定是什么异常可以直接使用Exception类
15.3 全局异常
15.3.1 代码
package com.leon.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
/**
* ClassName:MyGlobalException
* Package:com.leon.exception
* Description:
* 1. 被注解@ControllerAdvice标注以后,这个就是一个全局异常类
* @Author: leon-->ZGJ
* @Create: 2024/5/4 15:48
* @Version: 1.0
*/
@ControllerAdvice
public class MyGlobalException {
/*
* 1. 全局异常就是不管是哪个Handler抛出异常,都可以捕获
* 2. 如果不确定会发生什么异常,可以直接写一个Exception异常类,这样所有的异常都会处理
*
* */
@ExceptionHandler({NumberFormatException.class, ClassCastException.class})
public String globalException(Exception e, HttpServletRequest request){
System.out.println("全局异常信息是: " + e.getMessage());
//将信息保存到request域中
request.setAttribute("reason", e.getMessage());
return "forward:/error.jsp";
}
}
15.3.2 注意实现和细节
- 局部异常的优先级高于全局异常
- 如果不确定是什么异常可以直接使用Exception类
15.4 自定义异常
15.4.1 代码
package com.leon.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* ClassName:AgeException
* Package:com.leon.exception
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/5/4 16:07
* @Version: 1.0
*/
@ResponseStatus(reason = "年龄需要1-120之间",value =HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException{
public AgeException() {
}
public AgeException(String message) {
super(message);
}
}
======================================================================================
@RequestMapping(value = "/test02")
public String test02() {
throw new AgeException("年龄必须在1-120之间======");
}
15.4.2 注意事项和细节
- @ResponseStatus(reason = "年龄需要1-120之间",value =HttpStatus.BAD_REQUEST),这里的reason的属性值是提供给Tomcat使用的,如果想要在SpringMVC中获取到信息,需要通过构造器(或其他的方式)传入异常信息
15.5 SimpleMappingExceptionResolver
15.5.1 基本说明
- 如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver
- 它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常,需要在IOC容器中配置
- 注意:在IOC容器中配置的视图名是返回给视图解析器解析的
15.5.2 代码
<!--配置一个统一异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">forward:/arrError.jsp</prop>
<prop key="java.lang.Exception">forward:/allError.jsp</prop>
</props>
</property>
</bean>
15.6 处理异常优先级
- 局部异常 > 全局异常 > SimpleMappingExceptionResolver > Tomcat 异常机制
三、手动实现SpringMVC的部分功能
1. controller包
package com.leon.controller;
import com.leon.pojo.Monster;
import com.leon.service.MonsterService;
import com.leon.springmvc_v1.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* ClassName:MonsterController
* Package:com.leon.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/21 21:13
* @Version: 1.0
*/
@Controller
@RequestMapping(value = "/monster")
public class MonsterController {
@Autowired
private MonsterService monsterService;
@Autowired
private OrderController orderController;
//为了方便使用HttpServletRequest和HttpServletResponse两个形参
@RequestMapping(value = "/list")
public void monsterList(HttpServletRequest request,
HttpServletResponse response
){
List<Monster> monsterList = monsterService.monsterList();
for (Monster monster : monsterList) {
System.out.println(monster);
}
//设置文件类型和字符编码
response.setContentType("text/html;charset=utf-8");
//获取输出流
PrintWriter writer = null;
try {
writer = response.getWriter();
// 输出信息
writer.write("<h1>妖怪信息列表</h1>");
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//刷新数据
writer.flush();
//关闭资源
writer.close();
}
}
@RequestMapping(value = "/select")
public void monsterSelect(HttpServletRequest request,
HttpServletResponse response,
String username) {
System.out.println("username===> " + username);
//设置文件类型和字符编码
response.setContentType("text/html;charset=utf-8");
//获取输出流
PrintWriter writer = null;
try {
writer = response.getWriter();
// 输出信息
writer.write("<h1>妖怪信息列表</h1>");
List<Monster> monsterList = monsterService.selectMonsterByName(username);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<table border='1px' width='400px' style='border-collapse: collapse'>");
for (Monster monster : monsterList) {
stringBuilder.append("<tr>\n" +
" <td>"+monster.getId()+"</td>\n" +
" <td>"+monster.getName()+"</td>\n" +
" <td>"+monster.getSkill()+"</td>\n" +
" <td>"+monster.getAge()+"</td>\n" +
" </tr>");
}
stringBuilder.append("</table>");
//遍历
writer.write(stringBuilder.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//刷新数据
writer.flush();
//关闭资源
writer.close();
}
}
@RequestMapping(value = "/login")
public String monsterLogin( HttpServletRequest request,String username) {
request.setAttribute("name",username);
System.out.println(username);
boolean flag = monsterService.monsterLogin(username);
if(flag){
return "forward:/login_ok.jsp";
}
return "forward:/login_error.jsp";
}
@RequestMapping(value = "/json")
@ResponseBody
public List<Monster> monsterJson(){
List<Monster> monsterList = monsterService.monsterList();
System.out.println("monsterJson ==> ");
return monsterList;
}
}
======================================================================================
package com.leon.controller;
import com.leon.springmvc_v1.annotation.Controller;
import com.leon.springmvc_v1.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* ClassName:OrderController
* Package:com.leon.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 16:13
* @Version: 1.0
*/
@Controller
@RequestMapping(value = "/order")
public class OrderController {
@RequestMapping(value = "/list")
public void orderList(HttpServletRequest request, HttpServletResponse response){
//设置文件类型和字符编码
response.setContentType("text/html;charset=utf-8");
//获取输出流
PrintWriter writer = null;
try {
writer = response.getWriter();
// 输出信息
writer.write("<h1>订单信息列表</h1>");
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//刷新数据
writer.flush();
//关闭资源
writer.close();
}
}
@RequestMapping(value = "/add")
public void orderAdd(HttpServletRequest request, HttpServletResponse response){
//设置文件类型和字符编码
response.setContentType("text/html;charset=utf-8");
//获取输出流
PrintWriter writer = null;
try {
writer = response.getWriter();
// 输出信息
writer.write("<h1>添加订单·····</h1>");
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
//刷新数据
writer.flush();
//关闭资源
writer.close();
}
}
}
2. pojo包
package com.leon.pojo;
/**
* ClassName:Monster
* Package:com.leon.pojo
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 17:01
* @Version: 1.0
*/
public class Monster {
private String id ;
private String name ;
private String skill ;
private Integer age ;
public Monster() {
}
public Monster(String id, String name, String skill, Integer age) {
this.id = id;
this.name = name;
this.skill = skill;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Monster{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", skill='" + skill + '\'' +
", age=" + age +
'}';
}
}
3. service
package com.leon.service;
import com.leon.pojo.Monster;
import java.util.List;
/**
* ClassName:MonsterService
* Package:com.leon.service
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 10:20
* @Version: 1.0
*/
public interface MonsterService {
public List<Monster> monsterList();
public List<Monster> selectMonsterByName(String name);
public boolean monsterLogin(String name);
}
======================================================================================
package com.leon.service.impl;
import com.leon.pojo.Monster;
import com.leon.service.MonsterService;
import com.leon.springmvc_v1.annotation.Service;
import java.util.ArrayList;
import java.util.List;
/**
* ClassName:MonsterServiceImpl
* Package:com.leon.service.impl
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 10:20
* @Version: 1.0
*/
@Service(value = "monsterServiceImpl")
public class MonsterServiceImpl implements MonsterService {
@Override
public List<Monster> monsterList() {
List<Monster> monsters = new ArrayList<>();
monsters.add(new Monster("100", "牛魔王", "芭蕉扇", 400));
monsters.add(new Monster("200", "白骨精", "魅惑", 200));
return monsters;
}
@Override
public List<Monster> selectMonsterByName(String name) {
//类似数据库
List<Monster> monsters = new ArrayList<>();
monsters.add(new Monster("100", "牛魔王", "芭蕉扇", 400));
monsters.add(new Monster("200", "白骨精", "魅惑", 200));
monsters.add(new Monster("300", "黄袍怪", "吹风", 200));
monsters.add(new Monster("400", "金角大王", "吐金珠", 200));
monsters.add(new Monster("500", "豹妖", "大铁锤", 200));
// 查询到的数据
List<Monster> findMonster = new ArrayList<>();
//查询
for (Monster monster : monsters) {
if (monster.getName().contains(name)) {
findMonster.add(monster);
}
}
return findMonster;
}
@Override
public boolean monsterLogin(String name) {
if("白骨精".equals(name)){
return true ;
}
return false;
}
}
4. springmvc 包
4.1 annotion 包
package com.leon.springmvc_v1.annotation;
import java.lang.annotation.*;
/**
* ClassName:Autowired
* Package:com.leon.springmvc.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 19:27
* @Version: 1.0
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
}
======================================================================================
package com.leon.springmvc_v1.annotation;
import java.lang.annotation.*;
/**
* ClassName:Controller
* Package:com.leon.springmvc.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/21 21:01
* @Version: 1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
======================================================================================
package com.leon.springmvc_v1.annotation;
import java.lang.annotation.*;
/**
* ClassName:RequestMapping
* Package:com.leon.springmvc.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/21 21:01
* @Version: 1.0
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
======================================================================================
package com.leon.springmvc_v1.annotation;
import java.lang.annotation.*;
/**
* ClassName:RequestParam
* Package:com.leon.springmvc.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 20:34
* @Version: 1.0
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
======================================================================================
package com.leon.springmvc_v1.annotation;
import java.lang.annotation.*;
/**
* ClassName:ResponseBody
* Package:com.leon.springmvc.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/23 14:20
* @Version: 1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
======================================================================================
package com.leon.springmvc_v1.annotation;
import java.lang.annotation.*;
/**
* ClassName:Service
* Package:com.leon.springmvc.annotation
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 13:13
* @Version: 1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
4.2 context 包
package com.leon.springmvc_v1.context;
import com.leon.springmvc_v1.annotation.Autowired;
import com.leon.springmvc_v1.annotation.Controller;
import com.leon.springmvc_v1.annotation.Service;
import com.leon.springmvc_v1.utils.StringUtils;
import com.leon.springmvc_v1.xml.XMLParser;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* ClassName:WebAppliactionContext
* Package:com.leon.springmvc.context
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 10:06
* @Version: 1.0
*/
public class WebApplicationContext {
//用于存放扫描到的类的全路径
private Map<String, String> classFullPathMap = new HashMap<>();
//要扫描的XMl
private String xmlFileName;
//用于存放对象
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
public WebApplicationContext() {
}
public WebApplicationContext(String parameter) {
//初始化xmlFileName
xmlFileName = getFileName(parameter);
//调用初始化方法
init();
}
public void init() {
List<String> componentScan = XMLParser.getComponentScan(xmlFileName);
//判断是否有数据
if (!componentScan.isEmpty()) {
//遍历集合中的路径
for (String packagePath : componentScan) {
//扫描包
getClassFilePath(packagePath);
}
}
executeInjectionInstance();
executeAutoWried();
}
//扫描指定包下的所有类文件
public void getClassFilePath(String packagePath) {
//获取类加载器获取绝对路径
ClassLoader classLoader =
WebApplicationContext.class.getClassLoader();
//将路径中的"."替换成"/"
String filePath = packagePath.replace(".", "/");
//获取真实工作目录的绝对路径
URL workAbsolutePath = classLoader.getResource(filePath);
//获取目标目录
File targetFile = new File(workAbsolutePath.getFile());
//判断目标文件是否是一个目录
if (targetFile.isDirectory()) {
//获取目标目录下的所有子文件或者子目录
File[] files = targetFile.listFiles();
//循环遍历
for (File file : files) {
//判断是否是目录
if (file.isDirectory()) {
//如果是则使用递归将子目录文件遍历出来
getClassFilePath(packagePath + "." + file.getName());
} else {
//获取类名
String className = file.getName().split("\\.")[0];
//获取路径
String classFullPath = packagePath + "." + className;
//将路径保存
classFullPathMap.put(StringUtils.uncapitalize(className), classFullPath);
}
}
}
}
//将classFullPathMap中被标注(Controller,Service)的类注入到IOC容器中
public void executeInjectionInstance() {
//判断classFullPathMap是否有数据
if (classFullPathMap.isEmpty()) {
return;
}
//遍历classFullPathMap
for (String key : classFullPathMap.keySet()) {
//获取类的全路径
String classFullPath = classFullPathMap.get(key);
try {
//获取class类
Class<?> beanClass = Class.forName(classFullPath);
//判断是否有被标注
if (beanClass.isAnnotationPresent(Controller.class)
|| beanClass.isAnnotationPresent(Service.class)) {
//获取类名
String beanName = key;
//获取构造器
Constructor<?> declaredConstructor =
beanClass.getDeclaredConstructor();
//创建对象
Object instance = declaredConstructor.newInstance();
//判断是否是Controller注解
if (beanClass.isAnnotationPresent(Controller.class)) {
//获取注解对象
Controller controller =
beanClass.getDeclaredAnnotation(Controller.class);
//获取自己指定的名称
String assignName = controller.value();
//判断是否是默认值
if (!"".equals(assignName)) {
//将指定的名称赋值给beanName
beanName = assignName;
}
//将对象存放到IOC容器中
singletonObjects.put(beanName, instance);
}
//判断是否是Controller注解
if (beanClass.isAnnotationPresent(Service.class)) {
//获取注解对象
Service service =
beanClass.getDeclaredAnnotation(Service.class);
//获取自己指定的名称
String assignName = service.value();
//判断是否是默认值
if (!"".equals(assignName)) {
//将对象存放到IOC容器中
singletonObjects.put(assignName, instance);
} else {
//保存一个本身类名映射的对象
singletonObjects.put(beanName, instance);
}
//获取接口信息
Class<?>[] interfaces = beanClass.getInterfaces();
//遍历接口
for (Class<?> interfaceClass : interfaces) {
//获取接口名称
String interfaceClassSimpleName = interfaceClass.getSimpleName();
//保存对象
singletonObjects.put(StringUtils.uncapitalize(interfaceClassSimpleName), instance);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public ConcurrentHashMap<String, Object> getSingletonObjects() {
return singletonObjects;
}
//这是一个解析构造器传值过来的字符串,解析成一个XMl文件
public String getFileName(String parameter) {
//将字符串进行分割
String fileName = parameter.split(":")[1];
return fileName;
}
public void executeAutoWried(){
//判断容器中是否有数据
if(singletonObjects.isEmpty()){
return;
}
//遍历容器
for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
//获取对象
Object bean = entry.getValue();
//获取所有字段
Field[] fields = bean.getClass().getDeclaredFields();
//遍历所有字段或者属性
for (Field field : fields) {
//判断是否被Autowired标注
if(field.isAnnotationPresent(Autowired.class)){
//获取字段类型
Class<?> type = field.getType();
//获取字段类型名称
String typeSimpleName = type.getSimpleName();
//获取目标类型的bean对象
Object targetObject = singletonObjects.get(StringUtils.uncapitalize(typeSimpleName));
//判断是否为空
if(targetObject != null){
//允许强制访问
field.setAccessible(true);
try {
//进行赋值
field.set(bean,targetObject);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}
4.3 mapping 包
package com.leon.springmvc_v1.mapping;
import java.lang.reflect.Method;
/**
* ClassName:Handler
* Package:com.leon.springmvc.mapping
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 13:53
* @Version: 1.0
*/
public class Handler {
//记录Url
private String url;
//记录对应的Controller
private Object targetObject;
//记录对应的方法
private Method targetMethod;
public Handler() {
}
public Handler(String url, Object targetObject, Method targetMethod) {
this.url = url;
this.targetObject = targetObject;
this.targetMethod = targetMethod;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public Method getTargetMethod() {
return targetMethod;
}
public void setTargetMethod(Method targetMethod) {
this.targetMethod = targetMethod;
}
@Override
public String toString() {
return "Handler{" +
"url='" + url + '\'' +
", targetObject=" + targetObject +
", targetMethod=" + targetMethod +
'}';
}
}
======================================================================================
package com.leon.springmvc_v1.mapping;
import com.leon.springmvc_v1.annotation.Controller;
import com.leon.springmvc_v1.annotation.RequestMapping;
import com.leon.springmvc_v1.context.WebApplicationContext;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* ClassName:HandlerMapping
* Package:com.leon.springmvc.mapping
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 13:55
* @Version: 1.0
*/
public class HandlerMapping {
//用于存放映射关系的集合
private ConcurrentHashMap<String,Handler> handlerMap = new ConcurrentHashMap<>();
private WebApplicationContext ioc;
public HandlerMapping(WebApplicationContext ioc) {
//初始化IOC
this.ioc = ioc ;
//初始化handlerMap
initHandlerMapping();
}
//初始化handlerMap
public void initHandlerMapping(){
//获取IOC
ConcurrentHashMap<String, Object> singletonObjects = ioc.getSingletonObjects();
//判断是否为空
if(singletonObjects.isEmpty()){
return;
}
//循环遍历map
for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
//获取Value值
Object entryValue = entry.getValue();
//获取class对象
Class<?> valueClass = entryValue.getClass();
//判断是否有Controller注解
if(valueClass.isAnnotationPresent(Controller.class)){
//创建url
String classUrl = "";
//判断是否有RequestMapping注解
if(valueClass.isAnnotationPresent(RequestMapping.class)){
//创建RequestMapping对象
RequestMapping requestMapping =
valueClass.getDeclaredAnnotation(RequestMapping.class);
//获取指定的Url值
String assignUrl = requestMapping.value();
//判断是否指定了
if(!"".equals(assignUrl)){
classUrl = assignUrl;
}
}
//获取所有方法
Method[] methods = valueClass.getMethods();
//遍历methods
for (Method method : methods) {
//判断是否有RequestMapping注解
if(method.isAnnotationPresent(RequestMapping.class)){
//创建RequestMapping对象
RequestMapping requestMapping =
method.getDeclaredAnnotation(RequestMapping.class);
//获取指定的Url
String assignUrl = requestMapping.value();
//判断是否为空
if(!"".equals(assignUrl)){
String requestUrl = classUrl + assignUrl;
//将映射信息存放到handlerMap
handlerMap.put(requestUrl,new Handler(requestUrl,entryValue,method));
}
}
}
}
}
}
//用于返回handler对象
public Handler getHandler(HttpServletRequest request){
//判断映射关系的集合是否有数据
if(handlerMap.isEmpty()){
return null;
}
//获取请求的Url
String requestUrl = request.getRequestURI();
//获取项目工程路径
String contextPath = request.getContextPath();
//循环遍历handlerMap
for (Map.Entry<String, Handler> entry : handlerMap.entrySet()) {
//获取的完整的Url
String temp = contextPath + entry.getKey();
//判断key中是否包含Url
if(temp.equals(requestUrl)){
//返回handler
return entry.getValue();
}
}
return null;
}
}
4.4 servlet 包
package com.leon.springmvc_v1.servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leon.springmvc_v1.annotation.RequestParam;
import com.leon.springmvc_v1.annotation.ResponseBody;
import com.leon.springmvc_v1.context.WebApplicationContext;
import com.leon.springmvc_v1.mapping.Handler;
import com.leon.springmvc_v1.mapping.HandlerMapping;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
/**
* ClassName:DispatcherServlet
* Package:com.leon.springmvc.servlet
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/21 21:03
* @Version: 1.0
*/
public class DispatcherServlet extends HttpServlet {
//创建IOC容器对象
private WebApplicationContext ioc;
//创建映射器对象
private HandlerMapping handlerMapping;
@Override
public void init(ServletConfig config) throws ServletException {
//获取配置文件中的IOC文件名称
String parameter = config.getInitParameter("contextConfigLocation");
//初始化IOC
ioc = new WebApplicationContext(parameter);
//初始化HandlerMapping
handlerMapping = new HandlerMapping(ioc);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
executeDispatcher(req, resp);
}
private void executeDispatcher(HttpServletRequest request, HttpServletResponse response) {
//获取Handler对象
Handler handler = handlerMapping.getHandler(request);
//判断是否存在
if (null == handler) {
//设置404状态
response.setStatus(404);
} else {
//获取方法对象
Method targetMethod = handler.getTargetMethod();
//获取方法的形参列表
Class<?>[] parameterTypes = targetMethod.getParameterTypes();
//设置编码格式
try {
request.setCharacterEncoding("utf-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
//获取请求参数列表
/*
* 1. 这里Value是一个数组,是因为你请求的参数可能是一个多选框或者是一个复选框
* */
Map<String, String[]> parameterMap = request.getParameterMap();
//创建一个记录参数位置的数组
Object[] parameters = new Object[parameterTypes.length];
//遍历形参列表
for (int i = 0; i < parameters.length; i++) {
//判断是否是指定类型的HttpServletRequest参数
if (parameterTypes[i].isAssignableFrom(HttpServletRequest.class)) {
//赋值给记录数组指定位置
parameters[i] = request;
}
//判断是否是指定类型的HttpServletResponse参数
if (parameterTypes[i].isAssignableFrom(HttpServletResponse.class)) {
//赋值给记录数组指定位置
parameters[i] = response;
}
}
//通过获取请求参数给指定形参赋值
//遍历parameterMap
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//获取key值,也就是请求参数名称
String requestParamName = entry.getKey();
//获取Value值,也就是请求参数值,这里默认请求参数只有一个值
String requestParamValue = entry.getValue()[0];
//获取匹配的下标
int index = getRequestParamParameterIndex(targetMethod, requestParamName);
//判断是否存在
if (index != -1) {
//对参数值数组进行赋值
parameters[index] = requestParamValue;
} else {
//默认方式赋值
int paramIndex = getDefaultParamIndex(targetMethod, requestParamName);
//判断是否存在
if (paramIndex != -1) {
//对参数进行赋值
parameters[paramIndex] = requestParamValue;
}
}
}
try {
//执行方法
Object invoke = targetMethod.invoke(handler.getTargetObject(), parameters);
viewParser(invoke, targetMethod ,request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//用于返回方法中形参的指定下标
public int getRequestParamParameterIndex(Method method, String parameterName) {
//获取方法的形参
Parameter[] parameters = method.getParameters();
//遍历形参
for (int i = 0; i < parameters.length; i++) {
//判断是否有RequestParam注解
if (parameters[i].isAnnotationPresent(RequestParam.class)) {
//获取RequestParam的Value值
RequestParam param =
parameters[i].getDeclaredAnnotation(RequestParam.class);
//获取Value值
String paramName = param.value();
//判断是否为默认值
if (!"".equals(paramName)) {
//判断是否相同
if (paramName.equals(parameterName)) {
return i;
}
}
}
}
return -1;
}
public int getDefaultParamIndex(Method method, String parameterName) {
//获取参数列表
Parameter[] parameters = method.getParameters();
//循环遍历
for (int i = 0; i < parameters.length; i++) {
//获取参数的名称
String paramName = parameters[i].getName();
//判断是否相同
if (paramName.equals(parameterName)) {
//返回指定下标
return i;
}
}
return -1;
}
//视图解析器方法
public void viewParser(Object returnResult,Method method, HttpServletRequest request, HttpServletResponse response) {
try {
//判断是否是字符串
if (returnResult instanceof String) {
// 进行强转
String result = (String) returnResult;
//进行判断操作
if (result.contains(":")) {
//进行截取
String[] requestModeAndPath = result.split(":");
//获取请求方式
String mode = requestModeAndPath[0];
//获取请求路径
String path = requestModeAndPath[1];
//判断是否是请求转发
if ("forward".equals(mode)) {
//请求转发
request.getRequestDispatcher(path).forward(request, response);
}
//判断是否是重定向
if ("redirect".equals(mode)) {
//重定向
response.sendRedirect(request.getContextPath() + path);
}
} else {
//默认请求转发
request.getRequestDispatcher(result).forward(request, response);
}
}
//判断是否是List集合
if(returnResult instanceof List){
//判断该方法是否被ResponseBody所标注
if(method.isAnnotationPresent(ResponseBody.class)){
//获取转换对象
ObjectMapper objectMapper = new ObjectMapper();
//进行转换
String json = objectMapper.writeValueAsString(returnResult);
//设置编码格式
response.setContentType("text/html;charset=UTF-8");
//获取输出流
PrintWriter writer = response.getWriter();
//输出JSON格式字符串
writer.write(json);
//进行刷新
writer.flush();
//关闭资源
writer.close();
}
}
} catch (ServletException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
4.5 utils 包
package com.leon.springmvc_v1.utils;
/**
* ClassName:StringUtils
* Package:com.leon.springmvc.utils
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/22 9:51
* @Version: 1.0
*/
public class StringUtils {
public static String uncapitalize(String convertTarget){
//获取第一个字符
String target = convertTarget.charAt(0) + "";
//转换成首字母小写
convertTarget = convertTarget.replace(target,target.toLowerCase());
return convertTarget;
}
}
4.6 xml 包
package com.leon.springmvc_v1.xml;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* ClassName:XMLParser
* Package:com.leon.springmvc.xml
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/4/21 17:50
* @Version: 1.0
*/
public class XMLParser {
//用于存放要扫描的包
private static List<String> packegePath = new ArrayList<>();
public static List<String> getComponentScan(String fileName){
//创建一个解析器
SAXReader reader = new SAXReader();
//获取真实的工作目录的绝对路径,并获取输入流
InputStream resourceAsStream = XMLParser.class.getClassLoader().getResourceAsStream(fileName);
try {
//读取document树
Document document = reader.read(resourceAsStream);
//获取根元素
Element rootElement = document.getRootElement();
//获取指定元素
List<Element> elements = rootElement.elements("componentScan");
//循环遍历
for (Element element : elements) {
//获取属性值
String value = element.attributeValue("base-package");
//将属性值放入到集合中
packegePath.add(value);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return packegePath;
}
}
5. 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<componentScan base-package="com.leon.controller"></componentScan>
<componentScan base-package="com.leon.service"></componentScan>
</beans>
6. 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>
<!--配置前端分发器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>com.leon.springmvc_v1.servlet.DispatcherServlet</servlet-class>
<!--配置获取IOC文件属性-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--配置加载顺序-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--配置前端控制器-->
<!-- <servlet>-->
<!-- <servlet-name>DispatcherServlet</servlet-name>-->
<!-- <servlet-class>com.leon.springmvc_V2.servlet.DispatcherServlet</servlet-class>-->
<!-- <init-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value>classpath:springmvc.xml</param-value>-->
<!-- </init-param>-->
<!-- <load-on-startup>1</load-on-startup>-->
<!-- </servlet>-->
<!-- <servlet-mapping>-->
<!-- <servlet-name>DispatcherServlet</servlet-name>-->
<!-- <url-pattern>/</url-pattern>-->
<!-- </servlet-mapping>-->
<!-- -->
</web-app>