这里写自定义目录标题
1、 SpringMVC 介绍
1.2 SpringMVC-基本介绍
1.2.1 SpringMVC 特点&概述
- SpringMVC 从易用性,效率上 比曾经流行的 Struts2 更好
- SpringMVC 是 WEB 层框架【解读: SpringMVC 接管了 Web 层组件, 比如控制器, 视图, 视图解析, 返回给用户的数据格式, 同时支持 MVC 的开发模式/开发架构】
- SpringMVC 通过注解,让 POJO 成为控制器,不需要继承类或者实现接口
- SpringMVC 采用低耦合的组件设计方式,具有更好扩展和灵活性
- 支持 REST 格式的 URL 请求.
- SpringMVC 是基于 Spring 的, 也就是 SpringMVC 是在 Spring 基础上的。SpringMVC 的核心包 spring-webmvc-xx.jar 和 spring-web-xx.jar
1.2.2 梳理 Spring SpringMVC SpringBoot 的关系
- Spring MVC 只 是 Spring 处 理 WEB 层 请 求 的 一个 模 块 /组 件 , Spring MVC 的 基 石 是
Servlet[Java WEB] - Spring Boot 是为了简化开发者的使用, 推出的封神框架(约定优于配置,简化了 Spring的配置流程), SpringBoot 包含很多组件/框架,Spring 就是最核心的内容之一,也包含 SpringMVC
- 他们的关系大概是: Spring Boot > Spring > Spring MVC
1.3 SpringMVC-快速入门
1.3.1 需求说明
需求说明: 完成一个最基本的测试案例,登录案例, 使用 SpringMVC 完成
1.3.2 SpringMVC 登录流程分析
1.3.3 SpringMVC 登录-代码实现
-
创建 springmvc web 工程并配置 tomcat
-
导入 SpringMVC 开发需要的 jar 包
-
创建 src/applicationContext-mvc.xml 文件(就是 spring 的容器文件),文件名自己定
-
配置 WEB-INF/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>
<!--配置属性: contextConfigLocation,指定DispatcherServlet去操作的spring配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-mvc.xml</param-value>
</init-param>
<!-- 在web项目启动时,就自动的加载DispatcherServlet -->
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!--
1.这里我们配置的url-pattern是 /,表示用户的请求都经过DispatcherServ
2.这样配置也这次rest风格的url请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
5.在 web下面创建login.jsp
<%--
Created by IntelliJ IDEA.
User: zxy
Date: 2023/7/26
Time: 19:52
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<form action="login">
u:<input name="username" type="text"> <br/>
p:<input name="password" type="password"><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
- 创建 D:\lxc\javaProject\springMVC\src\com\codeSE\web\UserServlet.java
package com.codeSE.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 1.如果我们使用了SpringMVC,在一个类上标识@Controller
* 2.表示将该类视为一个控制器,注入到容器
* 3.比原生servlet开发要简化很多
* 4.这里配置了一个 @Controller就已经被 springMvc框架接管
*/
@Controller
public class UserServlet {
/**
* 1. login()方法是用于响应用户的登录请求
* 2. @RequestMapping(value = "/login")类似我们以前在原生的Servlet配置url-pattern
* 3,即当用户在浏览器输入 http://localhost:8080/web工程路径/login就能够访问到Login()
* 4. return "login_ok";表示返回结果给视图解析器(InternalResourceViewResolver),
* 视图解析器会根据配置,来决定跳转到哪个页面
* <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
* <property name="prefix" value="/WEB_INF/page/"/>
* <property name="suffix" value=".jsp"/>
*
* </bean>
* 根据上面的醚置return "login_ok" ;就是转发到/WEB-INF/pages/login_ok.jspl
*
* @return login_ok 页面
*/
@RequestMapping(value = "/login")
public String login(){
System.out.println("login OK...");
return "login_ok";
}
}
- 创建D:\lxc\javaProject\springMVC\web\WEB-INF\pages\login_ok.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>login_ok</title>
</head>
<body>
<h1>登录成功</h1>
</body>
</html>
- 配置 applicationContext-mvc.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"
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.codeSE.web"/>
<!-- 视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置属性 suffix和prefix -->
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
1.3.4 注意事项和细节说明
- 重点学习如何搭建一个 springmvc 项目,初步理解 springmvc 工作流程
- 这里的 UserServlet 需要注解成@Controller ,我们称为一个 Handler 处理器
- UserServlet 指定 url 时,还可以这样
- 关 于 SpringMVC 的 DispatcherServlet 的 配 置 文 件 , 如 果 不 在 web.xml 指 定applicationContext-mvc.xml, 默 认 在 /WEB-INF/springDispatcherServlet-servlet.xml 找 这个配置文件【简单看下 DispatcherServlet 的源码】。(推荐使用, 我们做下修改 , 并完成测
试)
2、 SpingMVC 执行流程
3、@RequestMapping
3.1 基本使用
● @RequestMapping 注解可以指定控制器/处理器
的某个方法的请求的 url, RequestMapping : 请求映射。
3.2 @RequestMapping 注解其它使用方式
3.2.1 @RequestMapping 可以修饰方法和类
- 说明: @RequestMapping 注解可以修饰方法,还可以修饰类 当同时修饰类和方法时,请求的 url 就是组合 /类请求值/方法请求值。
1)创建D:\lxc\javaProject\springMVC\src\com\codeSE\web\UserHandler.java
package com.codeSE.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器,注入到容臧
public class UserHandler {
/** 解读:
* 1. method=RequestMethod.POST:表示请求 buy目标方法必须是 post
* 2. RequestMethod,四个选项 POST, GET, PUT, DELETE
* 3. SpringMVC控制器默认支持 GET和 POST两种方式
* 4.buy()方法请求的url: http: //localhost:8080/工程路径/user/buy
* @return String
*/
@RequestMapping(value = "/buy",method = RequestMethod.POST)
public String buy(){
System.out.println("购买商品");
return "success";
}
}
- 创建D:\lxc\javaProject\springMVC\web\request.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>购买商品</title>
</head>
<body>
<h1>购买商品</h1>
<form action="user/buy" method="post">
购买人:<input type="text" name="username"><br>
购买量:<input type="text" name="nums"><br>
<input type="submit" value="购买">
</form>
</body>
</html>
- 创建D:\lxc\javaProject\springMVC\web\WEB-INF\pages\success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>操作成功</title>
</head>
<body>
<h1>恭喜, 操作成功~</h1>
</body>
</html>
- 完成测试(页面方式)
3.2.2 @RequestMapping 可以指定请求方式
- 说明: @RequestMapping 还可以指定请求的方式(post/get/put/delete…), 请求的方式需要和指定的一样,否则报错
- SpringMVC 控制器默认支持 GET 和 POST 两种方式, 也就是你不指定 method , 可以接收GET 和 POST 请求
3.当明确指定了 method , 则需要按指定方式请求, 否则会报错
3.2.3 @RequestMapping 可指定 params 和 headers 支持简单表达式
- param1: 表示请求必须包含名为 param1 的请求参数
- !=param1: 表示请求不能包含名为 param1 的请求参数
- param1 != value1: 表示请求包含名为 param1 的请求参数,但其值不能为 value1
- {“param1=value1”, “param2”}: 请求必须包含名为 param1 和 param2 的两个请求参数,且 param1 参数的值必须为 value1
- 应用实例
- 修改 UserHandler.java , 增加方法
- 修改 request.jsp , 增加代码
- 页面方式完成测试
- 小的细节说明和测试
- 需要有 bookId 参数,并且值为 100, 否则报错
@RequestMapping(value = "/find", params = "bookId=100", method = RequestMethod.GET)
- 需要有 bookId 参数,并且值不为 100, 否则报错
@RequestMapping(value = "/find", params = "bookId!=100", method = RequestMethod.GET)
3.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 - 应用实例
- 修改 UserHandler.java, 增加方法
/**
* 要求可以配置 /user/message/aaa, /user/message/aaa/bbb/ccc
* @return
*/
@RequestMapping(value = "/message/**")
public String im() {
System.out.println("发送消息");
return "success";
}
- request.jsp, 增加代码
<hr>
<h1>演示 Ant 风格的请求资源方式 </h1>
<a href="user/message/aaa">发送消息 1</a><br>
<a href="user/message/aaa/bbb/ccc">发送消息 2</a><br>
- 页面完成测试
3.2.5 @RequestMapping 可配合 @PathVariable 映射 URL 绑定的占位符
- @RequestMapping 还可以配合 @PathVariable 映射 URL 绑定的占位符。
- 这样就不需要在 url 地址上带参数名了,更加的简洁明了
比如: 我们的前端页面是 这样的, kristina 和 300 是参数值
<hr>
<h1>占位符的演示</h1>
<a href="user/reg/kristina/300">占位符的演示</a>
- 应用实例
- 修改 UserHandler.java, 增加方法, 注意 @PathVariable(“username”) 不能少
/**
* 我 们 希 望 目 标 方 法 获 取 到 username和 userid, value="/xx/{username}" ,---@PathVariable("username")
* 前端页面: <a href="user/reg/kristina/300">占位符的演示</a>
* //(value = "/reg/{username}/{userId}" ):表示kristina->{username} 300=>{userid}
* @return
*/
@RequestMapping(value = "/reg/{username}/{userId}" )
public String register(@PathVariable("username") String name,@PathVariable("userId") String id){
System.out.println("接收到参数--" + "username=" + name + " ,--" + "usreid=" + id);
return "success";
}
- 修改 request.jsp, 增加代码
<hr>
<h1>占位符的演示</h1>
<a href="user/reg/kristina/300">占位符的演示</a>
- 页面完成测试
3.2.6 注意事项和使用细节
- 映射的 URL, 不能重复
@RequestMapping(value = "/hi")
public String hi() {
System.out.println("hi");
return "success";
}
@RequestMapping(value = "/hi")
public String hi2() {
System.out.println("hi");
return "success";
}
服务端报错信息
:to { [/user/hi]}: There is already ‘userHandler’ bean method
- 各种请求的简写形式
- 说明
@RequestMapping(value = “/buy”,method = RequestMethod.POST) 等 价@PostMapping(value = “/buy”)
简写方式一览: @GetMapping@PostMapping@PutMapping@DeleteMapping - 应用实例
//@RequestMapping(value = "/buy",method = RequestMethod.POST)
@PostMapping(value = "/buy")
public String buy() {
System.out.println("postmapping.. 购买商品");
return "success";
}
- 如果我们确定表单或者超链接会提交某个字段数据比如(email), 要求提交的参数名和目标方法的参数名保持一致.
1)应用实例,增加方法
/**
* hello3(String email):如果我们的请求参数有 email=xx,就会将传递的值,赋给
* String email,要求名称保持一致,如果不一致,那么接收不到数据
* @param email
* @return
*/
@GetMapping(value = "/hello")
public String hello3(String email) {
System.out.println("hello" + email);
return "success";
}
- 测试 输入 localhost:9998/user/hello3?email=1234567890@qq.com, 一定要注入提交参数名和后台方法的形参名保持一致, 否则后端接收不到参数,为null。
3.3 Postman(接口测试工具)
4、 Rest-优雅的 url 请求风格
4.1 Rest-基本介绍
● 说明
- REST:即 Representational State Transfer。(资源)表现层状态转化。是目前流行的请求方式。它结构清晰, 很多网站采用
- HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
- 实例
传统的请求方法:
getBook?id=1 GET
delete?id=1 GET
update POST
add 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 中配置。
4.2 Rest 风格的 url-完成增删改查
4.2.1 需求说明
获取指定id的书
添加一本书bookname:
删除指定id的书
修改一本书
4.2.2 Rest 应用案例-代码实现
- 修改 web.xml 添加 HiddenHttpMethodFilter
<!--
1.配置 HiddenMethodFilter过滤器
2.可以将以 post方式提交的 delete和 put请求进行转换
-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 修改 springDispatcherServlet-servlet.xml
<!--加入两个常规配置 -->
<!-- 能支持 SpringMVC高级功能,比如 JSR303校验,映射动态请求 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!--将 SpringMVC不能处理的请求交给 Tomcat,比如请求 css,js等-->
<mvc:default-servlet-handler/>
- 创建D:\lxc\javaProject\springMVC\web\rest.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>rest</title>
<script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#deleteBook").click(function () {
// alert("ok");
$("#hiddenForm").attr("action", this.href);
$(":hidden").val("DELETE");
$("#hiddenForm").submit();//这里就是提交删除请求了
//这里必须返回 false,否则会提交两次
return false;
});
})
</script>
</head>
<body>
<h3>Rest 风格的 crud 操作案例</h3>
<br>
<hr>
<h3>rest 风格的 url 查询书籍[get]</h3>
<a href="user/book/200">点击查询书籍</a>
<br>
<hr>
<h3>rest 风格的 url 添加书籍[post]</h3>
<form action="user/addBook" method="post">
name:<input name="bookName" type="text"><br>
<input type="submit" value="添加书籍">
</form>
<br>
<!--
1.这里我们需要将删除方式(get)转成 delete的方式,需要使用过滤器和 jquery来完成
2. name="_method"名字需要写成 _method,因为后台的 HiddenHttpMethodFilter
就是按这个名字来获取 hidden域的值,从而进行请求转换的.
3. public static final String DEFAULT_METHOD_PARAM = "_method";
HiddenHttpMethodFilter过滤器可以对以Post方式提交的delete , put , patch进行转换,
成springmvc识别的RequestMethod .DELETE / RequestMethod .PUT / ...
4,我们需要将get <a href="user/book/600">删除指定id的书</a> 以post方式提交给后端handler,这样过滤器还会生效
5.我们可以同jquery来处理
-->
<hr>
<h3>rest 风格的 url, 删除一本书[delete]</h3>
<a href="user/delBook/600" id="deleteBook">删除指定 id 的书</a>
<form action="" method="POST" id="hiddenForm">
<input type="hidden" name="_method"/>
</form>
<br>
<hr>
<h3>rest 风格的 url 修改书籍[put]</h3>
<form action="user/updateBook/666" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="修改书籍~">
</form>
</body>
</html>
- 创建D:\lxc\javaProject\springMVC\src\com\codeSE\web\rest\BookHandler.java
package com.codeSE.web.rest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* BookHandler处理rest风格的请求-增删改查
*/
@RequestMapping("/user")
@Controller
public class BookHandler {
//查询[GET]
@RequestMapping(value = "/book/{id}",method = RequestMethod.GET)
public String getBook(@PathVariable("id") String id) {
System.out.println("查询书籍 id=" + id);
return "success";
}
//添加[POST]
@PostMapping("/addBook")
public String addBook(String bookName){
System.out.println("添加书籍 id=" + bookName);
return "success";
}
//删除[DELETE]
@RequestMapping(value = "/delBook/{id}",method = RequestMethod.DELETE)
public String delBook(@PathVariable("id") String id){
System.out.println("删除书籍 id=" + id);
//return "success";// 如果这样返回会报错 JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS
// "redirect:/user/success" 会解析重定向为 springMVC/user/success
return "redirect:/user/success" ;
}
//如果请求是/user/success ,就转发到 success.jsp
@RequestMapping(value = "/success")
public String successGenecal(){///由该方法转发到success.jsp页面
return "success";
}
//修改[PUT]
@PutMapping(value = "/updateBook/{id}")
public String updateBook(@PathVariable("id") String id){
System.out.println("修改书籍 id=" + id);
//return "success";
return "redirect:/user/success" ;
}
}
- 页面完成测试
4.2.3 注意事项和细节说明
1、HiddenHttpMethodFilter, 在将 post 转成 delete / put 请求时,是按_method 参数名 来读取的
2、如果 web 项目是运行在 Tomcat 8 及以上,会发现被过滤成 DELETE 和 PUT 请求,到达控制器时能顺利执行,但是返回时(forward)会报 HTTP 405 的错误提示:消息 JSP 只允许 GET、POST 或 HEAD。
- 解决方式 1: 使用 Tomcat7
- 解决方式 2: 将请求转发(forward)改为请求重定向(redirect):重定向到一个 Handler,由 Handler 转发到页面
3、页面测试时, 如果出现点击修改书籍,仍然走的是删除 url ,是因为浏览器原因(缓存等原因), 换成 chrome 即可。
5、SpringMVC 映射请求数据
5.1 获取参数值
5.1.1 说明
- 开发中,如何获取到 http://xxx/url?参数名=参数值&参数名=参数值
- 创建D:\lxc\javaProject\springMVC\src\com\codeSE\web\requestparam\VoteHandler.java
package com.codeSE.web.requestparam;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@RequestMapping(value = "/vote")
@Controller
public class VoteHandler {
/*
* @RequestParam ( value= "name " , required=false)
* 1.获取到超链接传递的数据请求,http://localhost:8080/springMVC/vote/vote01?name=codeSE123
* 2.@RequestParam表示会接收提交的参数
* 3. value="name”表示提交的参数名是name
* 4. required=false表示该参数可以没有,
*/
@RequestMapping(value = "vote01")
public String test01(@RequestParam(value = "name",required = false) String username){
System.out.println("得到的username= " + username);
return "success";
}
}
5.2 获取 http 请求消息头
● 说明:
- 开发中,如何获取到 http 请求的消息头信息
使用较少
● 应用实例
修改 VoteHandler.java, 增加方法
/**
* 需求:获取http 请求头信息,获取到Accept-Encoding和 Host
* @RequestHeader( "Http请求头字段")
*/
@RequestMapping(value = "vote02")
public String test02(@RequestHeader("Accept-Encoding") String ae,
@RequestHeader("Host") String host){
System.out.println("得到的Accept-Encoding=" + ae);//gzip, deflate, br
System.out.println("得到的Host=" + host);//127.0.0.1:8080
return "success";
}
5.3 获取 javabean 形式的数据
5.3.1 使用场景说明
● 开发中,如何获取到 javaben 的数据,就是以前的 entity/pojo 对象数据
5.3.2 应用实例
- 创建一个entity包,包下面两个类Pet.java和Master.java
package com.codeSE.web.requestparam.entity;
public class Pet {
private Integer id;
private String name;
public Pet() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Pet{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
package com.codeSE.web.requestparam.entity;
public class Master {
private Integer id;
private String name;
private Pet pet;//对象的属性是另外一个对象[涉及级联]
public Master() {
}
public Master(Integer id, String name, Pet pet) {
this.id = id;
this.name = name;
this.pet = pet;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
public String toString() {
return "Master{" +
"id=" + id +
", name='" + name + '\'' +
", pet=" + pet +
'}';
}
}
- 修改 VoteHandler.java, 增加方法
/*
* 演示如果获取到提交数据->封装成java对象
* 1.方法的形参用对应的类型来指定即可,Sp正ingMVC会自动的进行封装
* 2.如果自动的完成封装,要求提交的数据,参数名和对象的字段名保持一致
* 3.如果属性是对象,这里就是字段名,比如Master [pet],
* 即提交的数据参数名是pet.id pet.name,这就是级联操作
* 4.如果提交的数据的参数名和对象的字段名不匹配,则对象的属性值就是null
* */
@RequestMapping(value = "/vote03")
public String test03(Master master){
System.out.println("master"+master);
return "success";
}
- postman完成测试
5.3.3 使用注意事项
- 支持级联数据获取
- 表单的控件名称 name 需要和 javabean 对象字段对应, 否则就是 null
5.4 获取 servlet api
- 5.4.1 应用实例
● 说明
- 开发中, 我们可能需要使用到原生的 servlet api ,看看如何获取
- 使用 servlet api , 需要引入 tomcat/lib 下的 servlet-api.jar
● 应用实例
- 修改 VoteHandler.java, 增加方法
/*
* 使用原生servlet api,来获取提交的数据
* */
@RequestMapping(value = "/vote04")
public String test04(HttpServletRequest request, HttpServletResponse response){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("原生servlet api 获取 username="+username+" password="+password);
return "success";
}
- postman完成测试
- 5.4.2 使用注意事项
- 除了 HttpServletRequest, HttpServletResponse 还可以其它对象也可以这样的形式获取
- HttpSession、java.security.Principal,InputStream,OutputStream,Reader,Writer
- 其中一些对象也可以通过 HttpServletRequest / HttpServletResponse 对象获取,比如Session 对象 ,既可以通过参数传入,也以通过 request.getSession() 获取,效果一样,推荐使用参数形式传入,更加简单明了
- 举例说明
@RequestMapping(value = "/vote04")
public String test04(HttpServletRequest request, HttpServletResponse response, HttpSession httpSession){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("原生servlet api 获取 username="+username+" password="+password);
//可以看到 httpSession 和 request.getSession() 是同一个对象
System.out.println("httpSession=" + httpSession);//httpSession=org.apache.catalina.session.StandardSessionFacade@92aae3
System.out.println("httpSession2=" + request.getSession());//httpSession2=org.apache.catalina.session.StandardSessionFacade@92aae3
return "success";
}
6、 模型数据
6.1 模型数据处理-数据放入 request
● 说明
开发中, 控制器/处理器中获取的数据如何放入 request 域,然后在前端(VUE/JSP/…)取出显示
6.1.2
方式 1: 通过 HttpServletRequest 放入 request 域
- 创建 …/web\model_data.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试 模型数据</title>
</head>
<body>
<h1>添加主人信息</h1>
<!--
1.
这里的字段名称和对象的属性名保持一致,级联添加属性也是一样保持名字对应关系
2.如果只是添加主人信息,则去掉宠物号和宠物名输入框即可 ,pet为 null
-->
<form action="vote/vote05" method="post">
主人号:<input type="text" name="id"><br>
主人名:<input type="text" name="name"><br>
宠物号:<input type="text" name="pet.id"><br>
宠物名:<input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
</body>
</html>
- 创建 …c\web\WEB-INF\pages\vote_ok.jsp, 显示数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>vote_ok </title>
</head>
<body>
<h1>获取的的数据显示页面</h1>
<hr>
取出 request 域的数据
<br>
address:【${requestScope.address} 】<br>
主人名字= 【${requestScope.master.name }】
主人信息= 【${requestScope.master }】
宠物名字= 【${requestScope.master.pet.name }】
</body>
</html>
- 修改 VoteHandler.java, 增加方法
/*
* 1.演示将提交的数据-->封装到java对象-->springmvc会自动的将其放入到request域中
* 在request域中:("master" , master)
* 2.这样我们就可以再跳到到的页面取出数据.
* */
@RequestMapping(value = "/vote05")
public String test05(Master master,HttpServletRequest httpServletRequest) {
//1. springmvc会自动把获取model模型,放入到request域中,名字就是 master
// 2.也可以手动将master放入到request
httpServletRequest.setAttribute("address","beijing");
//3.希望修改master的属性值
master.setName("codeSE");
// 4.分析析一下springmvc默认存放对象到request域中,属性名是
// request域("master" , master100)属性名是类名/类型名 首字母小写
return "vote_ok";
}
postman完成测试
6.1.3
方式 2: 通过请求的方法参数 Map<String,Object> 放入 request 域
- 修改 model_data.jsp, 增加代码
<br/><hr/>
<h1>添加主人信息[测试 Map ]</h1>
<form action="vote/vote06" method="post">
主人号:<input type="text" name="id"><br>
主人名:<input type="text" name="name"><br>
宠物号:<input type="text" name="pet.id"><br>
宠物名:<input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
- 修改 VoteHandler.java , 增加方法
@RequestMapping(value = "/vote06")
public String test06(Master master, Map<String, Object> map) {
//1.需求是通过map对象,添加属性到request中
//2.原理分析: springmvc会遍历map,然后将map 的K-v,存放到request域
map.put("address","beijing...");
//map.put("master",null);//就会重新置空
return "vote_ok";
}
6.1.4
方式 3: 通过返回 ModelAndView 对象 实现 request 域数据
- 修改 model_data.jsp, 增加代码
<br/><hr/>
<h1>添加主人信息[测试 ModelAndView]</h1>
<form action="vote/vote07" method="post">
主人号:<input type="text" name="id"><br>
主人名:<input type="text" name="name"><br>
宠物号:<input type="text" name="pet.id"><br>
宠物名:<input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
- 修改 VoteHandler.java , 增加方法
/*
* 演示通过返回ModelAndView对象,将数据放入到request域
* */
@RequestMapping(value = "/vote07")
public ModelAndView test07(Master master) {
ModelAndView modelAndView = new ModelAndView();
//放入属性到modelAndView
modelAndView.addObject("address","shanghai");
modelAndView.addObject("master",null);//就会重新置空
//指定跳转的视图名称
modelAndView.setViewName("vote_ok");
return modelAndView;
}
- postman完成测试
- 使用注意事项
- 从本质看,请求响应的方法 return “xx”, 是返回了一个字符串,其实本质是返回了一个ModelAndView 对象,只是默认被封装起来的.
- ModelAndView 即可以包含 model 数据,也可以包含视图信息
- ModelAndView 对象的 addObject 方法可以添加 key-val 数据,默认在 request 域中
- ModelAndView 对象 setView 方法可以指定视图名称
6.2 模型数据处理-数据放入 session
6.2.1
● 说明
开发中, 控制器/处理器中获取的数据如何放入 session 域,然后在前端(VUE/JSP/…)取出显示
6.2.2 应用实例
- 修改 model_data.jsp, 增加代码
<br/><hr/>
<h1>添加主人信息[测试 session]</h1>
<form action="vote/vote08" method="post">
主人号:<input type="text" name="id"><br>
主人名:<input type="text" name="name"><br>
宠物号:<input type="text" name="pet.id"><br>
宠物名:<input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
- 修改 VoteHandler.java, 增加方法
/*
* 将 model(master对象)放入到 session域中
* */
@RequestMapping(value = "/vote08")
public String test08(Master master, HttpSession httpSession) {
ModelAndView modelAndView = new ModelAndView();
// master对象是默认放在request域,我们将master对象放入到session域
httpSession.setAttribute("master", master);
httpSession.setAttribute("address", "guangzhou");
return "vote_ok";
}
- 修改 vote_ok.jsp , 增加代码
<hr>
取出 session 域的数据 <br>
address:【${sessionScope.address} 】<br>
主人名字= 【${sessionScope.master.name }】
主人信息=【 ${sessionScope.master }】
宠物名字= 【${sessionScope.master.pet.name }】
- postman完成测试
6.3 @ModelAttribute 实现 prepare 方法
6.3.1 应用实例
● 基本说明
开发中,有时需要使用某个前置方法(比如 prepareXxx(), 方法名由程序员定)给目标方法准备一个模型对象
- @ModelAttribute 注解可以实现 这样的需求
- 在某个方法上,增加了@ModelAttribute 注解后
- 那么在调用该 Handler 的任何一个方法时,都会先调用这个方法
● 应用实例
修改 VoteHandler.java, 增加方法 并测试
/*
* 1.当在某个方法上,增加了@ModelAttribute注解
* 2.那么在调用该 Handler的任何一个方法时,都会先调用这个方法
* 3.类似spring 的AOP前置通知
* 4. prepareModel前置方法,会切入到其它方法前执行.
* */
@ModelAttribute
public void prepareModel() {
System.out.println("在同一个VoteHandler下面就会调用到prepareModel()-----完成准备工作-----");
}
6.3.2 @ModelAttribute 最佳实践
● 修改用户信息(就是经典的使用这种机制的应用),流程如下:
- 在修改前,在前置方法中从数据库查出这个用户
- 在修改方法(目标方法)中,可以使用前置方法从数据库查询的用户
- 如果表单中对用户的某个属性修改了,则以新的数据为准,如果没有修改,则以数据库的信息为准,比如,用户的某个属性不能修改,就保持原来的值
7、 视图和视图解析器
7.1 基本介绍
- 在 springMVC 中的目标方法最终返回都是一个视图(有各种视图)
- 返回的视图都会由一个视图解析器来处理 (视图解析器有很多种)
7.2 自定义视图
7.2.1 为什么需要自定义视图
- 在默认情况下,我们都是返回默认的视图, 然后这个返回的视图交由 SpringMVC 的InternalResourceViewResolver 视图处理器来处理的
- 在实际开发中,我们有时需要自定义视图,这样可以满足更多更复杂的需求.
7.2.2 自定义视图实例-代码实现
1.在 /WEB-INF/ 配置 springDispatcherServlet-servlet.xml , 增加自定义视图解析器
<!--配置自定义视图解析器-->
<!-- 解读:
1.配置自定义视图解析器BeanNameViewResolver
2. BeanNameViewResolver可以去解析我们自定义的视图
3.配置属性 order,表示视图解析器执行的顺序,值越小,优先级越高
4.属性order的默认值是最低优先级﹐值为Integer.MAX_VALUE,
int LOWEST_PRECEDENCE = 2147483647
-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="99"/>
</bean>
- 创建 D:\lxc\javaProject\springMVC\src\com\codeSE\web\viewresolver\MyView.java
package com.codeSE.web.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;
/*
* 1. MyView继承了AbstractView,就可以作为一个视图使用
* 2. @Component(value = "myView"),该视图会注入到容器中,名字/id是myView
* */
@Component(value = "codeSEView")
public class MyView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//完成视图渲染
//并且可以确定我们要跳转的页面,[请求转发]/WEB-INF/pages/my_view.jsp
System.out.println("进入到自己的视图。。。");
//1.下面就是进行请求转发到/WEB-INF/pages/my_view.jsp
//2. /WEB-INF/pages/my_view.jsp会被springmvc解析 /springMVC/WEB-INF/ pages/my_view.jsp
httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp").forward(httpServletRequest,httpServletResponse);
}
}
- 创建 D:\lxc\javaProject\springMVC\src\com\codeSE\web\viewresolver\GoodsHandler.java
package com.codeSE.web.viewresolver;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/goods")
@Controller
public class GoodsHandler {
@RequestMapping(value = "/buy")
public String buy() {
System.out.println("=======buy()=====");
return "codeSEView";//这里和自定义的名字保持一直
}
}
- 创建 D:\idea_java_projects\springmvc\web\view.jsp 和 /WEB-INF/pages/my_view.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>自定义视图测试</title>
</head>
<body>
<h1>自定义视图测试</h1>
<a href="goods/buy">点击到自定义视图</a>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>my_view自己的页面</title>
</head>
<body>
<h1>从自己的视图过来的</h1>
<hr/>
welcome to my_view!!!
</body>
</html>
- 完成测试(Postman 测试)
7.2.3 自定义视图工作流程小结
● 自定义视图-小结
- 自定义视图: 创建一个 View 的 bean, 该 bean 需要继承自 AbstractView, 并实现renderMergedOutputModel 方法.
- 并把自定义 View 加入到 IOC 容器中
- 自定义视图的视图处理器,使用 BeanNameViewResolver, 这个视图处理器也需要配置到 ioc 容器
- BeanNameViewResolver 的调用优先级需要设置一下,设置 order 比 Integer.MAX_VAL小的值. 以确保其在 InternalResourceViewResolver 之前被调用
● 自定义视图-工作流程
- SpringMVC 调用目标方法, 返回自定义 View 在 IOC 容器中的 id
- SpringMVC 调用 BeanNameViewResolver 解析视图: 从 IOC 容器中获取 返回 id 值对应的 bean, 即自定义的 View 的对象
- SpringMVC 调用自定义视图的 renderMergedOutputModel 方法渲染视图
- 说明: 如果在 SpringMVC 调用目标方法, 返回自定义 View 在 IOC 容器中的 id,不存在, 则仍然按照默认的视图处理器机制处理
7.3 目标方法直接指定转发或重定向
7.3.1 使用实例
● 目标方法中指定转发或者重定向
- 默认返回的方式是请求转发,然后用视图处理器进行处理,比如在目标方法中这样写
- 也可以在目标方法直接指定重定向或转发的 url 地址
- 如果指定重定向,不能定向到 /WEB-INF 目录中
● 应用实例-代码实现
- 修改 GoodsHandler.java, 增加方法 order()
@RequestMapping(value = "/order")
public String order() {
System.out.println("=======order()=====");
//这里直接指定转发到哪个页面
return "forward:/WEB-INF/pages/my_view.jsp";
//重定向,如果是重定向,就不能重定向到 /WEB-INF目录中
//return "redirect:/login.jsp";
}
- 修改 view.jsp
<body>
<h1>自定义视图测试</h1>
<a href="goods/buy">点击到自定义视图</a><br/>
<a href="goods/order">测试目标方法直接指定 重定向&请求转发</a><br/>
</body>
- 完成测试(页面测试)
7.3.2 指定请求转发流程-Debug 源码
7.3.3 指定重定向流程-Debug 源码
练习:略
8、自己实现 SpringMVC 底层机制
【核心分发控制器+ Controller 和 Service 注入容器 + 对象自动装配 + 控制器方法获取参数 + 视图解析 + 返回 JSON 格式数据 】
8.1 搭建 SpringMVC 底层机制开发环境
注意:
4、引入需要的基本的 jar 包
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--引入原生servlet依赖的jar-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--
1. scope标签表示引入的jar的作用范围
2. provided :表示该项目在打包,放到生产环境的时候,不需要带上servlet-api.jar
3.因为tomcat本身是有servlet的jar,到时直接使用tomcat本身的servlet-api.jar ,防止版本冲突
-->
<scope>provided</scope>
</dependency>
<!--引入原生servlet依赖的jar-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--引入常用工具类的jar-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
5、到此: 项目开发环境搭建 OK
8.2
【核心分发控制器+ Controller 和 Service 注入容器 + 对象自动装配 + 控制器方法获取参数 + 视图解析 + 返回 JSON 格式数据 】
8.2.1 实现任务阶段 1- 开发 HspDispatcherServlet
8.2.1.1 说明: 编写 HspDispatcherServlet 充当原生的 DispatcherServlet(即核心控制器)
8.2.1.2 分析+代码实现
● 分析示意图
● 代码实现
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java
package com.codeSE.lxcspringmvc.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*
*
* 1. HspDispatcherServlet充当原生DispatcherServlet
* 2.本质是一个Servlet,继承HttpServlet
* */
public class LxcDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("LxcDispatcherServlet的doGet()被调用...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("LxcDispatcherServlet的doPost()被调用...");
}
}
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\resources\lxcspringmvc.xml,充当
原生的 applicationContext-mvc.xml 文件(就是 spring 的容器配置文件
, 比如指定要扫描哪些包下的类), 先创建给空的文件
<!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>
<!--配置 LxcDispatcherServlet -->
<servlet>
<servlet-name>LxcDispatcherServlet</servlet-name>
<servlet-class>com.codeSE.lxcspringmvc.servlet.LxcDispatcherServlet</servlet-class>
<!--给LxcDispatcherServlet配置参数,指定要操作的spring容器配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:lxcspringmvc.xml</param-value>
</init-param>
<!--LxcDispatcherServlet在tomcat启动时,就自动加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>LxcDispatcherServlet</servlet-name>
<!--因为HspDispatcherServlet作为前端控制器,所以需要拦截所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
8.2.1.3 配置 Tomcat, 完成测试
8.2.2 实现任务阶段 2-- 完成客户端/浏览器可以请求控制层
8.2.2.1 创建 自己的 Controller 和自定义注解
● 分析示意图
● 代码实现
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\MonsterController.java
package com.codeSE.controller;
import com.codeSE.lxcspringmvc.annotation.Controller;
import com.codeSE.lxcspringmvc.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Controller
public class MonsterController {
//springmvc是支持原生的servlet api,为看到底层机制
@RequestMapping(value = "/list/monster")
public void monsterList(HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
try {
PrintWriter writer = response.getWriter();
writer.println("<h1>妖怪列表信邑</h1>");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- 创 建 自 定 义 注 解 D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\annotation\Controller.java和RequestMapping.java的两个注解
package com.codeSE.lxcspringmvc.annotation;
import java.lang.annotation.*;
/*
* 注解用于标识一个控制器组件
* */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
package com.codeSE.lxcspringmvc.annotation;
import java.lang.annotation.*;
/*
* RequestMapping注解用于指定控制器-方法的映射路径
* */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
8.2.2.2 配置 lxcspringmvc.xml
1 在该文件指定,我们的 springmvc 要扫描的包
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<!--指定要扫描的基本包以及子包的java类-->
<component-scan base-package="com.codeSE.controller,com.codeSE.service">
</component-scan>
</beans>
8.2.2.3 编写 XMLParser 工具类,可以解析 hspspringmvc.xml
完成功能说明:
-编写 XMLParser 工具类, 可以解析 hspringmvc.xml, 得到要扫描的包
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\xml\XMLParser.java
package com.codeSE.lxcspringmvc.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
/*
* XMLParser用于解析spring配置文件
* */
public class XMLParser {
public static String getBasePackage(String xmlFile){
SAXReader saxReader = new SAXReader();
//通过得到类的加载路径-->获取到spring配置文件.
InputStream inputStream = XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
try {
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Element componentScanElement = rootElement.element("component-scan");
Attribute attribute = componentScanElement.attribute("base-package");
String basePackage = attribute.getText();
return basePackage;
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
}
- D:\lxc\javaProject\lxc-springMVC\src\test\java\com\codeSE\test\LxcSpringMVCTest.java
package com.codeSE.test;
import com.codeSE.lxcspringmvc.xml.XMLParser;
import org.junit.Test;
public class LxcSpringMVCTest {
@Test
public void readXml(){
String basePackage1 = XMLParser.getBasePackage("lxcspringmvc.xml");
System.out.println("basePackage=="+basePackage1);
}
}
8.2.2.4 开发 HspWebApplicationContext,充当 Spring 容器-得到扫描类的全路径列表
完成的功能说明
- 把指定的目录包括子目录下的 java 类的全路径扫描到集合中,比如 ArrayList
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\context\LxcWebApplicationContext.java
package com.codeSE.lxcspringmvc.context;
import com.codeSE.lxcspringmvc.xml.XMLParser;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
/*
* LxcWebApplicationContext表示我们自己的spring容器
*
* */
public class LxcWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private ArrayList<String> classFullPathList = new ArrayList<>();
//扫描包
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
//下面是通过类的加载器,得到你指定的包对应的工作路径[绝对路径]
//"com.codeSE.controller"==>D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\
//1.不要直接使用Junit测试,否则url null 2.启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));
//System.out.println("url=="+url);
//根据得到的路径,对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path==" + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) { //是目录
scanPackage(pack + "." + f.getName());//递归
} else {
//筛选扫描的文件(要.class,并且是有注解),目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullPath = pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
//自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("lxcspringmvc.xml");
String[] packages = basePackage.split(",");
if(basePackage.length()>0){
for (String pack : packages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList=="+classFullPathList);
}
}
- 在D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java增加 init()
package com.codeSE.lxcspringmvc.servlet;
import com.codeSE.lxcspringmvc.context.LxcWebApplicationContext;
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;
/*
*
* 1. HspDispatcherServlet充当原生DispatcherServlet
* 2.本质是一个Servlet,继承HttpServlet
* */
public class LxcDispatcherServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
LxcWebApplicationContext lxcWebApplicationContext = new LxcWebApplicationContext();
lxcWebApplicationContext.init();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("-----LxcDispatcherServlet的doGet()被调用...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("-----LxcDispatcherServlet的doPost()被调用...");
}
}
- 启动 Tomcat 完成测试,看看扫描是否成功. 需要使用 Tomcat 启动方式完成测试,直接用 Junit 测试 URL 是 null
8.2.2.5 完善 LxcWebApplicationContext,充当 Spring 容器-实例化对象到容器中
完成功能说明
- 将扫描到的类, 在满足条件的情况下(即有相应的注解@Controller @Service…), 反射注入到 ioc 容器
- 修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\context\LxcWebApplicationContext.java
package com.codeSE.lxcspringmvc.context;
import com.codeSE.lxcspringmvc.annotation.Controller;
import com.codeSE.lxcspringmvc.xml.XMLParser;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
/*
* LxcWebApplicationContext表示我们自己的spring容器
*
* */
public class LxcWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private ArrayList<String> classFullPathList = new ArrayList<>();
//定义属性ioc,存放反射生成的Bean对象:Controller/Service...
public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
//自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("lxcspringmvc.xml");
String[] packages = basePackage.split(",");
if (basePackage.length() > 0) {
for (String pack : packages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList==" + classFullPathList);
//将扫描到的类,反射到ico容器
executeInstance();
System.out.println("扫描后的ioc容器== "+ioc);
}
//扫描包
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
//下面是通过类的加载器,得到你指定的包对应的工作路径[绝对路径]
//"com.codeSE.controller"==>D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\
//1.不要直接使用Junit测试,否则url null 2.启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));
//System.out.println("url=="+url);
//根据得到的路径,对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path==" + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) { //是目录
scanPackage(pack + "." + f.getName());//递归
} else {
//筛选扫描的文件(要.class,并且是有注解),目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullPath = pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
//编写方法,将扫描到的类,在满足条件的情况下,反射到ioc容器
public void executeInstance() {
//判断是否扫描到类
if (classFullPathList.size() == 0) {
return;
}
try {
for (String classFullPath : classFullPathList) {
Class<?> aClass = Class.forName(classFullPath);
if (aClass.isAnnotationPresent(Controller.class)) {//说明当前这个类有GController
//得到类名首字母小写
String beanName = aClass.getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT) +
aClass.getSimpleName().substring(1);
ioc.put(beanName,aClass.newInstance());
}
//如果有其它的注解,可以扩展
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
- 完成测试 (提示: 启动 tomcat 来加载 LxcDispatcherServlet 的方式测试)
8.2.2.6 完成请求 URL 和控制器方法的映射关系
将配置的@RequestMapping 的 url 和 对应的 控制器-方法 映射关系保存到集合中
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\handler\LxcHandler.java
package com.codeSE.lxcspringmvc.handler;
import java.lang.reflect.Method;
/*
* LxcHandler 对象记录请求的url和控制器方法映射关系
* */
public class LxcHandler {
private String url;
private Object controller;
private Method method;
public LxcHandler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "LxcHandler{" +
"url='" + url + '\'' +
", controller=" + controller +
", method=" + method +
'}';
}
}
- 修 改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java
package com.codeSE.lxcspringmvc.servlet;
import com.codeSE.lxcspringmvc.annotation.Controller;
import com.codeSE.lxcspringmvc.annotation.RequestMapping;
import com.codeSE.lxcspringmvc.context.LxcWebApplicationContext;
import com.codeSE.lxcspringmvc.handler.LxcHandler;
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.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/*
*
* 1. HspDispatcherServlet充当原生DispatcherServlet
* 2.本质是一个Servlet,继承HttpServlet
* */
public class LxcDispatcherServlet extends HttpServlet {
//定义属性 handlerList ,保存LxcHandler
private List<LxcHandler> lxcHandlerList = new ArrayList<>();
//定义属性lxcWebApplicationContext,自己的spring容器
LxcWebApplicationContext lxcWebApplicationContext = null;
@Override
public void init(ServletConfig config) throws ServletException {
lxcWebApplicationContext = new LxcWebApplicationContext();
lxcWebApplicationContext.init();
//调用initHandlerMapping,完成url和控制器方法的映射
initHandlerMapping();
System.out.println("lxcHandlerList初始化结果=="+lxcHandlerList);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("-----LxcDispatcherServlet的doGet()被调用...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("-----LxcDispatcherServlet的doPost()被调用...");
}
//编写方法,完成url和控制器方法的映射
public void initHandlerMapping(){
if(lxcWebApplicationContext.ioc.isEmpty()){
//判断当前的ioc容器是否为null
return;
}
//遍历ioc容器的bean对象,然后进行url映射处理
for (Map.Entry<String,Object> entry:lxcWebApplicationContext.ioc.entrySet()){
//先取出注入的Object 的aClass对象
Class<?> aClass = entry.getValue().getClass();
if(aClass.isAnnotationPresent(Controller.class)){
//取出所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
//遍历
for (Method declaredMethod:declaredMethods){
//判断该方法是否有GRequestMapping
if (declaredMethod.isAnnotationPresent(RequestMapping.class)){
//取出@RequestMapping 值->就是映射路径
RequestMapping requestMappingAnnotationl
= declaredMethod.getAnnotation(RequestMapping.class);
String url = requestMappingAnnotationl.value();
//创建LxcHandler对象
LxcHandler lxcHandler = new LxcHandler(url, entry.getValue(), declaredMethod);
lxcHandlerList.add(lxcHandler);
}
}
}
}
}
}
- 完成测试 (提示: 启动 tomcat 来加载 LxcDispatcherServlet 的方式测试)
8.2.2.7 完成 LxcDispatcherServlet 分发请求到对应控制器方法
当用户发出请求,根据用户请求 url 找到对应的控制器-方法
, 并反射调用
- 修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java
package com.codeSE.lxcspringmvc.servlet;
import com.codeSE.lxcspringmvc.annotation.Controller;
import com.codeSE.lxcspringmvc.annotation.RequestMapping;
import com.codeSE.lxcspringmvc.context.LxcWebApplicationContext;
import com.codeSE.lxcspringmvc.handler.LxcHandler;
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.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/*
*
* 1. HspDispatcherServlet充当原生DispatcherServlet
* 2.本质是一个Servlet,继承HttpServlet
* */
public class LxcDispatcherServlet extends HttpServlet {
//定义属性 handlerList ,保存LxcHandler
private List<LxcHandler> lxcHandlerList = new ArrayList<>();
//定义属性lxcWebApplicationContext,自己的spring容器
LxcWebApplicationContext lxcWebApplicationContext = null;
@Override
public void init(ServletConfig config) throws ServletException {
lxcWebApplicationContext = new LxcWebApplicationContext();
lxcWebApplicationContext.init();
//调用initHandlerMapping,完成url和控制器方法的映射
initHandlerMapping();
System.out.println("lxcHandlerList初始化结果==" + lxcHandlerList);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("-----LxcDispatcherServlet的doGet()/doPost()被调用...");
//凋用方法,完成分发请求
executeDispatch(req,resp);
}
//编写方法,完成url和控制器方法的映射
public void initHandlerMapping() {
if (lxcWebApplicationContext.ioc.isEmpty()) {
//判断当前的ioc容器是否为null
return;
}
//遍历ioc容器的bean对象,然后进行url映射处理
for (Map.Entry<String, Object> entry : lxcWebApplicationContext.ioc.entrySet()) {
//先取出注入的Object 的aClass对象
Class<?> aClass = entry.getValue().getClass();
if (aClass.isAnnotationPresent(Controller.class)) {
//取出所有方法
Method[] declaredMethods = aClass.getDeclaredMethods();
//遍历
for (Method declaredMethod : declaredMethods) {
//判断该方法是否有GRequestMapping
if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
//取出@RequestMapping 值->就是映射路径
RequestMapping requestMappingAnnotationl
= declaredMethod.getAnnotation(RequestMapping.class);
String url = requestMappingAnnotationl.value();
//创建LxcHandler对象
LxcHandler lxcHandler = new LxcHandler(url, entry.getValue(), declaredMethod);
lxcHandlerList.add(lxcHandler);
}
}
}
}
}
//编写方法,通过request对象,返回LxcHandler对象,没有就返回null
private LxcHandler getLxcHandler(HttpServletRequest request) {
//1.先获取的用户请求的uri,比如http://localhost:8080/springmvc/monster/list
//uri = /springmvc/ monster/list
//2.这里要注意得到uri和保存url是有一个工程路径的问题
//两个方案解决=>第一个方案:简单tomcat直接配置application context =>/
//第二个方案保存 lxchandler对象url 拼接getServletContext().getContextPath()
String requestURI = request.getRequestURI();
//遍历lxcHandlerList
for (LxcHandler lxcHandler : lxcHandlerList) {
if (requestURI.equals(lxcHandler.getUrl())) {//配置成功
return lxcHandler;
}
}
return null;
}
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request,HttpServletResponse response){
LxcHandler lxcHandler = getLxcHandler(request);
try {
if (null==lxcHandler){//说明用户请求的路径[资源不存在]
response.getWriter().println("<h1>404 NOT FOUND!!</h1>");
}else {//匹配成功
lxcHandler.getMethod().invoke(lxcHandler.getController(),request,response);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
- 完成测试(启动 Tomcat)
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\OrderController.java增 加 方 法 和 控 制 器
package com.codeSE.controller;
import com.codeSE.lxcspringmvc.annotation.Controller;
import com.codeSE.lxcspringmvc.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Controller
public class OrderController {
@RequestMapping(value = "/order/list")
public void listOrder(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/html; charset=utf-8");
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>订单列表信息</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
@RequestMapping(value = "/order/add")
public void addOrder(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/html; charset=utf-8");
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>添加订单信息</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
8.2.3 实现任务阶段 3- 从 web.xml 动态获取 lxcspringmvc.xml
8.2.3.1 说明: 前面我们加载 lxcspringmvc.xml 是硬编码, 现在做活, 从 web.xml 动态获取
8.2.3.2 分析+代码实现+测试
LxcDispatcherServlet 在创建并初始化 LxcWebApplicationContext,动态的从 web.xml 中获取到配置文件.
● 代码实现
-
修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\context\LxcWebApplicationContext.java
-
修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java
-
完成测试(启动 tomcat 方式)
8.2.4 实现任务阶段 4- 完成自定义@Service 注解功能
8.2.4.1 功能说明: 如果给某个类加上@Service, 则可以将其注入到我们的 Spring 容器
8.2.4.2 分析+代码实现+测试
- 给 Service 类标注@Service, 可以将对象注入到 Spring 容器中
- 并可以通过接口名支持多级, 类名来获取到 Service Bean
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\entity\Monster.java
package com.codeSE.entity;
public class Monster {
private Integer id;
private String name;
private Integer age;
private String skill;
public Monster(Integer id, String name, Integer age, String skill) {
this.id = id;
this.name = name;
this.age = age;
this.skill = skill;
}
public Integer getId() {
return id;
}
public void setId(Integer 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 getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\annotation\Service.java
package com.codeSE.lxcspringmvc.annotation;
import java.lang.annotation.*;
/*
* Service 注解,用于标识一个Service对象,并注入到spring容器
* */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\service\MonsterService.java
package com.codeSE.service;
import com.codeSE.entity.Monster;
import java.util.List;
public interface MonsterService {
public List<Monster> listMonsters();
}
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\service\impl\MonsterServiceImpl.java
package com.codeSE.service.impl;
import com.codeSE.entity.Monster;
import com.codeSE.lxcspringmvc.annotation.Service;
import com.codeSE.service.MonsterService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/*
* MonsterServiceImpl
* */
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public List<Monster> listMonsters() {
ArrayList<Monster> monsters = new ArrayList<>();
monsters.add(new Monster(100,"齐天大圣",599,"降妖除魔"));
monsters.add(new Monster(200,"混世魔王",666,"攻占天庭"));
return monsters;
}
}
-
修改D:\lxc\javaProject\lxc-springMVC\src\main\resources\lxcspringmvc.xml
-
修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\context\LxcWebApplicationContext.java
-
完成测试(启动 Tomcat, 自动加载 LxcDispatcherServlet, 完成 IOC 容器的注入)
8.2.5 实现任务阶段 5- 完成 Spring 容器对象的自动装配 -@Autowried
8.2.5.1 说明: 完成 Spring 容器中对象的注入/自动装配
8.2.5.2 分析+代码实现
● 完成任务说明
- 加入@AutoWired 注解, 进行对象属性的装配
1.创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\annotation\AutoWired.java
package com.codeSE.lxcspringmvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoWired {
String value() default "";
}
- 修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\MonsterController.java
package com.codeSE.controller;
import com.codeSE.entity.Monster;
import com.codeSE.lxcspringmvc.annotation.AutoWired;
import com.codeSE.lxcspringmvc.annotation.Controller;
import com.codeSE.lxcspringmvc.annotation.RequestMapping;
import com.codeSE.service.MonsterService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@Controller
public class MonsterController {
//属性
//@AutoWired表示要完成同性的装配.
@AutoWired
private MonsterService monsterService;
//springmvc是支持原生的servlet api,为看到底层机制
@RequestMapping(value = "/list/monster")
public void monsterList(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/html;charset=utf-8");
//调用morterService
try {
List<Monster> monsters = monsterService.listMonsters();
StringBuilder content = new StringBuilder("<h1>妖怪列表</h1>");
content.append("<table width='500px' style='border-collapse:collapse' border='1px'>");
for (Monster monster : monsters) {
content.append("<tr><td>" + monster.getId()+ "</td><td>"
+ monster.getName() + "</td><td>" + monster.getSkill()
+ "</td></tr>");
}
content.append("</table>");
PrintWriter printWriter = response.getWriter();
printWriter.println(content.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\context\LxcWebApplicationContext.java增加方法,在init()调用
//编写属性的自动装配
public void executeAutowired() {
//判断ioc有没有要装配的对象
if (ioc.isEmpty()) {
return;//可以抛出异常throw new RuntimeException("ioc 容器没有bean对象)
}
//遍历ioc容器中的所有注入的bean对象,然后获取到bean的所有字段/属性,判断是否需要装配
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//String key = entry.getKey();
Object bean = entry.getValue();
Field[] declaredFields = bean.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//当前这个字段的@AutoWired是否有
if (declaredField.isAnnotationPresent(AutoWired.class)) {
AutoWired autoWiredAnnotation = declaredField.getAnnotation(AutoWired.class);
String beanName = autoWiredAnnotation.value();
if ("".equals(beanName)) {//如果没有设置value,按照默认规则
//即得到字段类型的名称的首字母小写,作为名字来进行装配
Class<?> type = declaredField.getType();
beanName = type.getSimpleName().substring(0, 1).toLowerCase() + type.getSimpleName().substring(1);
}
//如果设置value,直接按照beanName来进行装配
//从ioc容器中获取到bean
if (null == ioc.get(beanName)) {//ioc容器中,不存在你要装配的bean
throw new RuntimeException("ioc容器中,不存在你要装配的bean");
}
//防止属性是private,我们需要暴力破解
declaredField.setAccessible(true);
//以装配属性
try {
declaredField.set(bean, ioc.get(beanName));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
- 启动 Tomcat, 完成测试,浏览器输入 http://localhost:8080/monster/list
8.2.6 实现任务阶段 6- 完成控制器方法获取参数-@RequestParam
8.2.6.1 功能说明:自定义@RequestParam 和 方法参数名获取参数
8.2.6.2 完成: 将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到参数数组,进行反射调用
● 代码实现, 老师说明,整个实现思路,就是参考 SpringMVC 规范
- 修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request,HttpServletResponse response){
// 将需要传递给目标方法的实参
LxcHandler lxcHandler = getLxcHandler(request);
try {
if (null==lxcHandler){//说明用户请求的路径[资源不存在]
response.getWriter().println("<h1>404 NOT FOUND!!</h1>");
}else {//匹配成功
//目标将: HttpServletRequest和 HttpServletResponse装到参数数组
//通过反射得到的参数数组->在反射调用方法时会使用到
//getParameterTypes或得到当前这个方法的所有参数信息
//1.得到目标方法的参数信息[对应的数组]
Class<?>[] parameterTypes = lxcHandler.getMethod().getParameterTypes();
//2.创建一个参数数组[对应实参数组],后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for (int i = 0;i<parameterTypes.length;i++){
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest,将request填充到params
if("HttpServletRequest".equals(parameterType.getSimpleName())){
params[i] = request;
} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
params[i] = response;
}
}
//改进: lxcHandler.getMethod().invoke(lxcHandler.getController(),request,response);
lxcHandler.getMethod().invoke(lxcHandler.getController(),params);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
- 完成测试(启动 tomcat), 浏览器输入 http://localhost:8080/monster/list , 仍然可以看到正确的返回
8.2.6.3 完成: 在方法参数 指定 @RequestParam 的参数封装到参数数组,进行反射调用
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\annotation\RequestParam.java
package com.codeSE.lxcspringmvc.annotation;
import java.lang.annotation.*;
/*
* RequestParam注解标注在目标方法的参数上,表示对应http请求的参数
* */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\service\MonsterService.java新增
public List<Monster> findMonsterByName(String name);
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\service\impl\MonsterServiceImpl.java新增
@Override
public List<Monster> findMonsterByName(String name) {
ArrayList<Monster> monsters = new ArrayList<>();
monsters.add(new Monster(100,"齐天大圣",599,"降妖除魔"));
monsters.add(new Monster(200,"混世魔王",666,"攻占天庭"));
monsters.add(new Monster(300,"白骨精",599,"降妖除魔"));
monsters.add(new Monster(400,"蜘蛛精",666,"攻占天庭"));
monsters.add(new Monster(500,"超天大圣",599,"降妖除魔"));
monsters.add(new Monster(600,"混世魔王1",666,"攻占天庭"));
List<Monster> findMonsters = new ArrayList<>();
for (Monster monster : monsters) {
if (monster.getName().contains(name)){
findMonsters.add(monster);
}
}
return findMonsters;
}
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\MonsterController.java新增
//增加方法,通过name返回对应的monster集合
@RequestMapping(value = "/monster/byName")
public void findMonsterByName(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "name") String name) {
response.setContentType("text/html;charset=utf-8");
System.out.println("————接收到的参数name—————" + name);
//调用monsterService
try {
List<Monster> monsters = monsterService.findMonsterByName(name);
StringBuilder content = new StringBuilder("<h1>妖怪列表</h1>");
content.append("<table width='500px' style='border-collapse:collapse' border='1px'>");
for (Monster monster : monsters) {
content.append("<tr><td>" + monster.getId() + "</td><td>"
+ monster.getName() + "</td><td>" + monster.getSkill()
+ "</td></tr>");
}
content.append("</table>");
PrintWriter printWriter = response.getWriter();
printWriter.println(content.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java新增
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
// 将需要传递给目标方法的实参
LxcHandler lxcHandler = getLxcHandler(request);
try {
if (null == lxcHandler) {//说明用户请求的路径[资源不存在]
response.getWriter().println("<h1>404 NOT FOUND!!</h1>");
} else {//匹配成功
//目标将: HttpServletRequest和 HttpServletResponse装到参数数组
//通过反射得到的参数数组->在反射调用方法时会使用到
//getParameterTypes或得到当前这个方法的所有参数信息
//1.得到目标方法的参数信息[对应的数组]
Class<?>[] parameterTypes = lxcHandler.getMethod().getParameterTypes();
//2.创建一个参数数组[对应实参数组],后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for (int i = 0; i < parameterTypes.length; i++) {
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest,将request填充到params
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
params[i] = response;
}
}
//将http请求参数封装到params数组中,老韩提示,要注意填充实参的时候,顺序问题
//1.获取http 请求的参数集合
//返回的Map<String ,String[]> String:表示http请求的参数名,string[]:表示http 请求的参数值
//这里统一处理中文乱码问题
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
//2.遍历parameterMap将请求参数,按照顺序填充到实参数组params
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lxcHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) { //找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
// 1.得到目标方法的所有形参名称
// 2.对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames = getParameterNames(lxcHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
//如果请求参数名和目标方法的形参名一样,说明匹配成功
if (name.equals(parameterNames.get(i))) {
params[i] = value;//填充到实参数组
break;
}
}
}
}
//改进: lxcHandler.getMethod().invoke(lxcHandler.getController(),request,response);
lxcHandler.getMethod().invoke(lxcHandler.getController(), params);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//编写方法,返回请求参数是目标方法的第几个形参
/**
* @param method 目标方法
* @param name 请求的参数
* @return 是目标方法的第几个形参
*/
public int getIndexRequestParameterIndex(Method method, String name) {
//1.得到method的所有形参参数
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
boolean annotationPresent = parameter.isAnnotationPresent(RequestParam.class);
if (annotationPresent) {
RequestParam requestParamAnnotation = parameter.getAnnotation(RequestParam.class);
String value = requestParamAnnotation.value();
if (name.equals(value)) {
return i;//找到请求的参数,对应的目标方法的形参的位置
}
}
}
return -1;
}
-
完 成 测 试 (Redeploy Tomcat 即 可 ) , 浏 览 器 输 入
http://localhost:8080/monster/find?name=XXX
8.2.6.4 完成: 在方法参数 没有指定 @RequestParam ,按照默认参数名获取值, 进行反射调用 -
修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\MonsterController.java
//增加方法,通过name返回对应的monster集合
@RequestMapping(value = "/monster/byName")
public void findMonsterByName(HttpServletRequest request, HttpServletResponse response,
String name) {
response.setContentType("text/html;charset=utf-8");
System.out.println("————接收到的参数name—————" + name);
//调用monsterService
try {
List<Monster> monsters = monsterService.findMonsterByName(name);
StringBuilder content = new StringBuilder("<h1>妖怪列表</h1>");
content.append("<table width='500px' style='border-collapse:collapse' border='1px'>");
for (Monster monster : monsters) {
content.append("<tr><td>" + monster.getId() + "</td><td>"
+ monster.getName() + "</td><td>" + monster.getSkill()
+ "</td></tr>");
}
content.append("</table>");
PrintWriter printWriter = response.getWriter();
printWriter.println(content.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- 修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
// 将需要传递给目标方法的实参
LxcHandler lxcHandler = getLxcHandler(request);
try {
if (null == lxcHandler) {//说明用户请求的路径[资源不存在]
response.getWriter().println("<h1>404 NOT FOUND!!</h1>");
} else {//匹配成功
//目标将: HttpServletRequest和 HttpServletResponse装到参数数组
//通过反射得到的参数数组->在反射调用方法时会使用到
//getParameterTypes或得到当前这个方法的所有参数信息
//1.得到目标方法的参数信息[对应的数组]
Class<?>[] parameterTypes = lxcHandler.getMethod().getParameterTypes();
//2.创建一个参数数组[对应实参数组],后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for (int i = 0; i < parameterTypes.length; i++) {
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest,将request填充到params
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
params[i] = response;
}
}
//将http请求参数封装到params数组中,老韩提示,要注意填充实参的时候,顺序问题
//1.获取http 请求的参数集合
//返回的Map<String ,String[]> String:表示http请求的参数名,string[]:表示http 请求的参数值
//这里统一处理中文乱码问题
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
//2.遍历parameterMap将请求参数,按照顺序填充到实参数组params
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lxcHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) { //找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
// 1.得到目标方法的所有形参名称
// 2.对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames = getParameterNames(lxcHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
//如果请求参数名和目标方法的形参名一样,说明匹配成功
if (name.equals(parameterNames.get(i))) {
params[i] = value;//填充到实参数组
break;
}
}
}
}
//改进: lxcHandler.getMethod().invoke(lxcHandler.getController(),request,response);
//反射调用目标方法
Object result =lxcHandler.getMethod().invoke(lxcHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc可以通过视图解析器来完成
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//编写方法,得到目标方法的所有形参的名称,并放入到集合中返回
public List<String> getParameterNames(Method method) {
List<String> parametersList = new ArrayList<>();
//获取到所以的参数名->这里有一个小细节
//在默认情况下 parameter.getName()得到的名字不是形参真正名字
//而是[arg0, arg1, arg2.. .],这里我们要引入一个插件,使用java8特性,这样才能解读
Parameter[] parameters = method.getParameters();
//遍历parameters取出名称,放入parametersList
for (Parameter parameter : parameters) {
parametersList.add(parameter.getName());
}
System.out.println("目标方法的形参参数列表==" + parametersList);
return parametersList;
}
- 修改D:\lxc\javaProject\lxc-springMVC\pom.xml
* request, response, name
*
注意:
在默认情况下,返回的并不是 request, response ,name
而是 arg0, arg1,arg2
需要使用到 jdk8的新特性,并需要在 pom.xml配置 maven
编译插件(可以百度搜索到),才能得到 request, response, name
* <plugin>
* <groupId>org.apache.maven.plugins</groupId>
* <artifactId>maven-compiler-plugin</artifactId>
* <version>3.7.0</version>
* <configuration>
* <source>1.8</source>
* <target>1.8</target>
* <compilerArgs>
* <arg>-parameters</arg>
* </compilerArgs>
* <encoding>utf-8</encoding>
* </configuration>
* </plugin>
- 点击 maven 管理,clean 项目,在重启一下 tomcat ,完成测试
8.2.7 实现任务阶段 7- 完成简单视图解析
8.2.7.1 功能说明:通过方法返回的 String, 转发或者重定向到指定页面
- 修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\service\MonsterService.java
package com.codeSE.service;
import com.codeSE.entity.Monster;
import java.util.List;
public interface MonsterService {
public List<Monster> listMonsters();
public List<Monster> findMonsterByName(String name);
public boolean login(String name);
}
- 修 改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\service\impl\MonsterServiceImpl.java
//处理登录的方法
@Override
public boolean login(String name) {
if ("齐天大圣".equals(name)){
return true;
}
return false;
}
- 增加
//处理妖怪登录的方法,返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request,HttpServletResponse response, String mName) {
System.out.println( "--接收到mName---" + mName);
request.setAttribute("mName",mName);//将mNam且没置到request域
boolean b = monsterService.login(mName);
if (b) {
//return "forward:/login_ok.jsp";
//return "redirect:/login_ok.jsp";
return "/login_ok.jsp";
} else {
return "forward:/login_error.jsp";
}
}
- 增加D:\lxc\javaProject\lxc-springMVC\src\main\webapp\login_ok.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
<h1>登录成功</h1>
欢迎您:${requestScope.mName}
</body>
</html>
- 增加D:\lxc\javaProject\lxc-springMVC\src\main\webapp\login_error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登录失败</title>
</head>
<body>
<h1>登录失败</h1>
sorry:${requestScope.mName}
</body>
</html>
- 增加D:\lxc\javaProject\lxc-springMVC\src\main\webapp\login_ok.jsp
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
// 将需要传递给目标方法的实参
LxcHandler lxcHandler = getLxcHandler(request);
try {
if (null == lxcHandler) {//说明用户请求的路径[资源不存在]
response.getWriter().println("<h1>404 NOT FOUND!!</h1>");
} else {//匹配成功
//目标将: HttpServletRequest和 HttpServletResponse装到参数数组
//通过反射得到的参数数组->在反射调用方法时会使用到
//getParameterTypes或得到当前这个方法的所有参数信息
//1.得到目标方法的参数信息[对应的数组]
Class<?>[] parameterTypes = lxcHandler.getMethod().getParameterTypes();
//2.创建一个参数数组[对应实参数组],后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for (int i = 0; i < parameterTypes.length; i++) {
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest,将request填充到params
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
params[i] = response;
}
}
//将http请求参数封装到params数组中,老韩提示,要注意填充实参的时候,顺序问题
//1.获取http 请求的参数集合
//返回的Map<String ,String[]> String:表示http请求的参数名,string[]:表示http 请求的参数值
//这里统一处理中文乱码问题
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
//2.遍历parameterMap将请求参数,按照顺序填充到实参数组params
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lxcHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) { //找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
// 1.得到目标方法的所有形参名称
// 2.对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames = getParameterNames(lxcHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
//如果请求参数名和目标方法的形参名一样,说明匹配成功
if (name.equals(parameterNames.get(i))) {
params[i] = value;//填充到实参数组
break;
}
}
}
}
//改进: lxcHandler.getMethod().invoke(lxcHandler.getController(),request,response);
//反射调用目标方法
Object result =lxcHandler.getMethod().invoke(lxcHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc可以通过视图解析器来完成
//这里直接解析,只要把视图解析的核心机制讲清楚就0K
if (result instanceof String){
System.out.println(result);
String viewName = (String) result;
if (viewName.contains(":")){//说明你返回的String结果forward:/login_ok.jsp或者redirect:/login_ok.jsp
String viewType = viewName.split(":")[0]; // forward | redirect
String viewPage = viewName.split(":")[1]; // 是你要跳转的页面名
//判断是forward还是redirect
if("forward".equals(viewType)){
request.getRequestDispatcher(viewPage).forward(request,response);
} else if ("redirect".equals(viewType)) {
response.sendRedirect(viewPage);
}
}else { //默认是请求转发
request.getRequestDispatcher(viewName).forward(request,response);
}
} else if (result instanceof ArrayList) {
//判断目标方法是否有@ResponseBody
Method method = lxcHandler.getMethod();
if (method.isAnnotationPresent(ResponseBody.class)){
//把result [ArrayList]转成json格式数据-->返回
//这里我们需要使用jackson包下的工具类可以轻松的搞定.
ObjectMapper objectMapper = new ObjectMapper();
String resultJson = objectMapper.writeValueAsString(result);
response.setContentType("text/html; charset=utf-8");
//简单处理就直接返回
PrintWriter writer = response.getWriter();
writer.println(resultJson);
writer.flush();
writer.close();
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
8.2.7.3 完成测试
1 启动 tomcat
2. 浏 览 器 输 入http://localhost:8080/monster/login?mName=XXX
8.2.8 实现任务阶段 8- 完成返回 JSON 格式数据-@ResponseBody
8.2.8.1 功能说明:通自定义@ResponseBody 返回 JSON 格式数据
- 创建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\annotation\ResponseBody.java
package com.codeSE.lxcspringmvc.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
String value() default "";
}
- 创 建D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\controller\MonsterController.java,增加方法
//编写方法,返回json格式的数据
/**
* 目标方法返回的结果是给springmvc底层通过反射调用的位置
* 我们在springmvc底层反射调用的位置,接收到结果并解析即可
* @param request
* @param response
* @return
*/
@RequestMapping("/monster/list/json")
@ResponseBody
public List<Monster> listMonsterByJson(HttpServletRequest request,HttpServletResponse response){
List<Monster> monsters = monsterService.listMonsters();
return monsters;
}
<!--引入jackson使用他的工具类操作json-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
- 修改D:\lxc\javaProject\lxc-springMVC\src\main\java\com\codeSE\lxcspringmvc\servlet\LxcDispatcherServlet.java
/编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
// 将需要传递给目标方法的实参
LxcHandler lxcHandler = getLxcHandler(request);
try {
if (null == lxcHandler) {//说明用户请求的路径[资源不存在]
response.getWriter().println("<h1>404 NOT FOUND!!</h1>");
} else {//匹配成功
//目标将: HttpServletRequest和 HttpServletResponse装到参数数组
//通过反射得到的参数数组->在反射调用方法时会使用到
//getParameterTypes或得到当前这个方法的所有参数信息
//1.得到目标方法的参数信息[对应的数组]
Class<?>[] parameterTypes = lxcHandler.getMethod().getParameterTypes();
//2.创建一个参数数组[对应实参数组],后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for (int i = 0; i < parameterTypes.length; i++) {
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest,将request填充到params
if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
params[i] = request;
} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
params[i] = response;
}
}
//将http请求参数封装到params数组中,老韩提示,要注意填充实参的时候,顺序问题
//1.获取http 请求的参数集合
//返回的Map<String ,String[]> String:表示http请求的参数名,string[]:表示http 请求的参数值
//这里统一处理中文乱码问题
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
//2.遍历parameterMap将请求参数,按照顺序填充到实参数组params
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lxcHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) { //找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
// 1.得到目标方法的所有形参名称
// 2.对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames = getParameterNames(lxcHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
//如果请求参数名和目标方法的形参名一样,说明匹配成功
if (name.equals(parameterNames.get(i))) {
params[i] = value;//填充到实参数组
break;
}
}
}
}
//改进: lxcHandler.getMethod().invoke(lxcHandler.getController(),request,response);
//反射调用目标方法
Object result =lxcHandler.getMethod().invoke(lxcHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc可以通过视图解析器来完成
//这里直接解析,只要把视图解析的核心机制讲清楚就0K
if (result instanceof String){
System.out.println(result);
String viewName = (String) result;
if (viewName.contains(":")){//说明你返回的String结果forward:/login_ok.jsp或者redirect:/login_ok.jsp
String viewType = viewName.split(":")[0]; // forward | redirect
String viewPage = viewName.split(":")[1]; // 是你要跳转的页面名
//判断是forward还是redirect
if("forward".equals(viewType)){
request.getRequestDispatcher(viewPage).forward(request,response);
} else if ("redirect".equals(viewType)) {
response.sendRedirect(viewPage);
}
}else { //默认是请求转发
request.getRequestDispatcher(viewName).forward(request,response);
}
} else if (result instanceof ArrayList) {
//判断目标方法是否有@ResponseBody
Method method = lxcHandler.getMethod();
if (method.isAnnotationPresent(ResponseBody.class)){
//把result [ArrayList]转成json格式数据-->返回
//这里我们需要使用jackson包下的工具类可以轻松的搞定.
ObjectMapper objectMapper = new ObjectMapper();
String resultJson = objectMapper.writeValueAsString(result);
response.setContentType("text/html; charset=utf-8");
//简单处理就直接返回
PrintWriter writer = response.getWriter();
writer.println(resultJson);
writer.flush();
writer.close();
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
8.2.8.3 完成测试
- 启动 Tomcat
- 浏览器输入 http://localhost:8080/monster/list/json
9、数据格式化
9.1 基本介绍
说明: 在我们提交数据(比如表单时)SpringMVC 怎样对提交的数据进行转换和处理的
- 基本数据类型可以和字符串之间自动完成转换,比如:
Spring MVC 上下文中内建了很多转换器,可完成大多数 Java 类型的转换工作。[相互转换,这里只列出部分]
2. ConversionService converters =java.lang.Boolean -> java.lang.String :
org.springframework.core.convert.support.ObjectToStringConverter@f874ca
java.lang.Character -> java.lang.Number : CharacterToNumberFactory@f004c9
java.lang.Character -> java.lang.String : ObjectToStringConverter@68a961
java.lang.Enum -> java.lang.String : EnumToStringConverter@12f060a
java.lang.Number -> java.lang.Character : NumberToCharacterConverter@1482ac5
java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory@126c6f
java.lang.Number -> java.lang.String : ObjectToStringConverter@14888e8
java.lang.String -> java.lang.Boolean : StringToBooleanConverter@1ca6626
java.lang.String -> java.lang.Character : StringToCharacterConverter@1143800
java.lang.String -> java.lang.Enum : StringToEnumConverterFactory@1bba86e
java.lang.String -> java.lang.Number : StringToNumberConverterFactory@18d2c12
java.lang.String -> java.util.Locale : StringToLocaleConverter@3598e1
java.lang.String -> java.util.Properties : StringToPropertiesConverter@c90828
java.lang.String -> java.util.UUID : StringToUUIDConverter@a42f23
java.util.Locale -> java.lang.String : ObjectToStringConverter@c7e20a
java.util.Properties -> java.lang.String : PropertiesToStringConverter@367a7f
java.util.UUID -> java.lang.String : ObjectToStringConverter@112b07f
9.2 基本数据类型和字符串自动转换
9.2.1 应用实例 -页面演示方式
● 说明: 基本数据类型可以和字符串之间自动完成转换
- 创建D:\lxc\javaProject\springMVC\src\com\codeSE\web\datavalid\entity\Monster.java
package com.codeSE.web.datavalid.entity;
public class Monster {
private Integer id;
private String email;
private Integer age;
private String name;
public Monster() {
}
public Monster(Integer id, String email, Integer age, String name) {
this.id = id;
this.email = email;
this.age = age;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Monster{" +
"id=" + id +
", email='" + email + '\'' +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
- 创建D:\lxc\javaProject\springMVC\web\WEB-INF\pages\atavalid\data_valid.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>添加妖怪~~</h3>
<!--
这里的表单,我们使用 springMVC
的标签来完成
特别说明几点:
1. SpringMVC
表单标签在显示之前必须在 request中有一个 bean,该 bean的属性和表单标签的字段要对应!
request中的 key为: form标签的 modelAttrite属性值, 比如这里的 monsters
2. SpringMVC的 form:form标签的 action属性值中的 /不代表 WEB应用的根目录.
-->
<form:form action="?" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <br><br>
妖怪年龄~: <form:input path="age"/> <br><br>
电子邮件: <form:input path="email"/> <br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
- 创建D:\lxc\javaProject\springMVC\src\com\codeSE\web\datavalid\MonsterHandler.java
package com.codeSE.web.datavalid;
import com.codeSE.web.datavalid.entity.Monster;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
/**
* MonsterHandler处理器响应用户提交数据
*
* @Scope(value="prototype")表示每次请求NonsterHandler会生成一个新的对簇
*/
@Controller
@Scope(value = "prototype")
public class MonsterHandler {
//显示添力monster的界面
/**
* 1.这里Map <String,Object> map
* 当我们向map添加的数据时,会默认存放至request域中
* @param map
* @return
*/
@RequestMapping(value = "addMonsterUI")
public String addMonsterUI(Map<String, Object> map) {
/*
1.这里的表单,我们使用 springMVC的标签来完成
2. SpringMVC表单标签在显示之前必须在 request中有一个 bean,该 bean的属性和表单标签的字段要对应!
request中 的 key,为 : form标 签 的 modelAttribute属 性 值 , 比 如 这 里 的monsters
3. SpringMVC的 form:form标签的 action属性值中的 /不代表 WEB应用的根目录.
4. <form:form action="monster" method="POST" modelAttribute="monster">
//这里需要 给 request增加一个 monster, 因为jsp页面的modelAttribute="monster"需要
//这时是 springMVC的内部的检测机制 即使是一个空的也需要,否则报错.
* */
map.put("monster",new Monster());
return "datavalid/monster_addUI";
}
}
- 创建D:\lxc\javaProject\springMVC\web\WEB-INF\pages\atavalid\monster_addUI.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>添加妖怪~~</h3>
<!--
这里的表单,我们使用 springMVC
的标签来完成
特别说明几点:
1. SpringMVC
表单标签在显示之前必须在 request中有一个 bean,该 bean的属性和表单标签的字段要对应!
request中的 key为: form标签的 modelAttrite属性值, 比如这里的 monsters
2. SpringMVC的 form:form标签的 action属性值中的 /不代表 WEB应用的根目录.
-->
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <br><br>
妖怪年龄~: <form:input path="age"/> <br><br>
电子邮件: <form:input path="email"/> <br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
- 创建D:\lxc\javaProject\springMVC\web\WEB-INF\pages\atavalid\success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>操作成功</title>
</head>
<body>
<h1>恭喜, 操作成功~</h1>
</body>
</html>
-
测试,看看是否可以正确显示添加要求界面(页面测试)
浏览器: http://localhost:8080/springmvc/data_valid.jsp -
修改 MonsterHandler.java , 增加处理添加请求
//保存方法
/*
1. springmvc可以将提交的数据,按照参数名和对象的属性名匹配
2. 直接封装到对象中
string => Integer
*/
@RequestMapping(value = "save")
public String save(Monster monster) {
System.out.println("---monster---" + monster);
return "datavalid/success";
}
- 测试 , 浏览器: http://localhost:8080/springmvc/data_valid.jsp
- 如果 age 输入的是 数字,则通过, 说明 SpringMVC 可以将提交的字符串 数字,比如"28",转成 Integer/int
- 如果不是数字,则给出 400 的页面
9.3 特殊数据类型和字符串间的转换
9.3.1 应用实例 -页面演示方式
-
特殊数据类型和字符串之间的转换使用注解(比如日期,规定格式的小数比如货币形式等)
-
对于日期和货币可以使用 @DateTimeFormat 和 @NumberFormat 注解. 把这两个注解标记在字段上即可.
-
修改 Monster.java , 增加 birthday 和 salary 字段
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern="###,###.##")
private float salary;
- 修改 monster_addUI, 增加 birthday 和 salary 字段
妖怪年龄~: <form:input path="age"/> <br><br>
妖怪生日: <form:input path="birthday"/> 要求以"9999-11-11"的形式<br><br>
妖怪工资: <form:input path="salary"/> 要求以"123,890.12"的形式<br><br>
- 测试
- 如果 birthday 和 salary 是按照指定格式输入,则通过, 说明 SpringMVC 可以按注解指定格式转换
- 如果没有按照注解指定格式,则给出 400 的页面
9.3.2 应用实例 -Postman 完成测试
- 测试
- 如果 birthday 和 email 是按照指定格式输入,则通过, 说明 SpringMVC 可以按注解指定格式转换
- 如果没有按照注解指定格式,则给出 400 的页面
- 如何给出对应的提示,后面马上讲解
10. 验证以及国际化
10.1 概述
● 概述
- 对输入的数据(比如表单数据),进行必要的验证,并给出相应的提示信息。
- 对于验证表单数据,springMVC 提供了很多实用的注解, 这些注解由 JSR 303 验证框架提供.
● JSR 303 验证框架
1.JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 中
2.JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,
并通过标准的验证接口对 Bean 进行验证
3.JSR 303 提供的基本验证注解有:
● Hibernate Validator 扩展注解
1.Hibernate Validator 和 Hibernate 没有关系,只是 JSR 303 实现的一个扩展.
2.Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
10.2 应用实例-代码实现
1.引入验证和国际化相关的 jar 包
2.修改 Monster.java
3.修改 MonsterHandler.java
/*
1. springmvc可以将提交的数据,按照参数名和对象的属性名匹配
2. 直接封装到对象中
string => Integer
3. @Valid Monster monster:表示对monster接收的数据进行校验
4. Errors errors表示如果校验出现错误,将校验的错误信息保存errors
5. Map<String,Object> map_表示如果校验出现错误,将校验的错误信息保存 map
6.校验发生的时机:在springmvc底层,反射调用目标方法时,会接收到http请求的数据,然后根据注解来进行验证
,在验证过程中,如果出现了错误,就把错误信息填充errors 和 map
*/
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(@Valid Monster monster, Errors errors, Map<String, Object> map) {
System.out.println(" ----monster---" + monster);
//我们为了看到验证的情况,我们输出map和 errors
System.out.println("===== map ======");
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println(" key= " + entry.getKey() + " value=" + entry.getValue());
}
System.out.println("===== errors ======");
if (errors.hasErrors()) {
List<ObjectError> allErrors = errors.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println("error=" + error);
}
return "datavalid/monster_addUI";
}
return "datavalid/success";
}
---这里可以测试一下,看看效果----
=====map=====
key=monster value=Monster{id=null, email='jack@sohu.com', age=900, name='',
birthday=Thu Nov 11 00:00:00 CST 1999, salary=11.11}
key=org.springframework.validation.BindingResult.monster
value=org.springframework.validation.BeanPropertyBindingResult: 2 errors
Field error in object 'monster' on field 'age': rejected value [900]; codes
[Range.monster.age,Range.age,Range.java.lang.Integer,Range]; arguments
[org.springframework.context.support.DefaultMessageSourceResolvable: codes
[monster.age,age]; arguments []; default message [age],100,1]; default message
[需要在 1 和 100 之间]
Field error in object 'monster' on field 'name': rejected value []; codes
[NotEmpty.monster.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable: codes
[monster.name,name]; arguments []; default message [name]]; default message
[不能为空]
=====errors=====
验证出现错误
验证错误=Field error in object 'monster' on field 'age': rejected value [900]; codes
[Range.monster.age,Range.age,Range.java.lang.Integer,Range]; arguments
[org.springframework.context.support.DefaultMessageSourceResolvable: codes
[monster.age,age]; arguments []; default message [age],100,1]; default message
[需要在 1 和 100 之间]
验证 错误=Field error in object 'monster' on field 'name': rejected value []; codes
[NotEmpty.monster.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable: codes
[monster.name,name]; arguments []; default message [name]]; default message
[不能为空]
4. 配置国际化文件 springDispatcherServlet-servlet.xml
<!--配置国际化错误信息的资源处理 bean -->
<bean id="messageSource" class=
"org.springframework.context.support.ResourceBundleMessageSource">
<!--
配置国际化文件名字如果你这样配的话表示 messageSource回到src/i18nXXX.properties去读取错误信息
-->
<property name="basename" value="i18n"></property>
</bean>
- 创建国际化文件D:\lxc\javaProject\springMVC\src\i18n.properties
NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
typeMismatch.monster.age=\u5e74\u9f84\u8981\u6c42\u5728\u0031\u002d\u0031\u0035\u0030\u4e4b\u95f4
typeMismatch.monster.birthday=\u751f\u65e5\u683c\u5f0f\u4e0d\u6b63\u786e
typeMismatch.monster.salary=\u85aa\u6c34\u683c\u5f0f\u4e0d\u6b63\u786e
- 修改 monster_addUI.jsp , 回显错误信息
<form:form action="save" method="POST" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <form:errors path="name"/><br><br>
妖怪年龄~: <form:input path="age"/> <form:errors path="age"/><br><br>
妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/> 要求
以"9999-11-11"的形式<br><br>
妖 怪 工 资 : <form:input path="salary"/> <form:errors path="salary"/> 要 求 以
"123,890.12"的形式<br><br>
电子邮件: <form:input path="email"/><br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
- 完成测试
10.2.2 细节说明和注意事项 - 在需要验证的 Javabean/POJO 的字段上加上相应的验证注解.
- 目标方法上,在 JavaBean/POJO 类型的参数前, 添加 @Valid 注解. 告知 SpringMVC该 bean 是需要验证的
- 在 @Valid 注解之后, 添加一个 Errors 或 BindingResult 类型的参数, 可以获取到验证的错误信息
- 需要使用 <form:errors path=“email”></form:errors> 标签来显示错误消息, 这个标签,需要写在form:form 标签内生效.
- 错误消息的国际化文件 i18n.properties , 中文需要是 Unicode 编码,使用工具转码.
√格式: 验证规则.表单 modelAttribute 值.属性名=消息信息
√NotEmpty.monster.name=\u540D\u5B57\u4E0D\u80FD\u4E3A\u7A7A
√typeMismatch.monster.age=\u7C7B\u578B\u4E0D\u5339\u914D - 注解@NotNull 和 @NotEmpty 的区别说明
- 查看源码可以知道 : @NotEmpty Asserts that the annotated string, collection, mapor array is not {@code null} or empty.
- 查看源码可以知道 : @NotNull The annotated element must not be {@code null}
Accepts any type. - 如果是字符串验证空, 建议使用 @NotEmpty
- SpingMVC 验证时,会根据不同的验证错误, 返回对应的信息
10.3 注解的结合使用
● 问题提出, age 没有, 是空的,提交确是成功了
- 使用@NotNull + @Range 组合使用解决
- 具体代码
public class Monster {
private Integer id;
@NotEmpty(message = "电子邮件不为空~~~~")
private String email;
@NotNull(message = "年龄必须填写 1-100")
@Range(min = 1, max = 100)
private Integer age;
@NotNull(message = "生日不能为空")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NotNull(message = "薪水不能为空")
@NumberFormat(pattern = "###,###.##")
private Float salary;
}
- 测试(页面方式),这时 age 不能为空,同时必须是 1-100, (也不能输入 haha, hello 等不能转成数字的内容)
10.4 数据类型转换校验核心类-DataBinder
● DataBinder 工作机制-了解
图例 Spring MVC 通过反射机制对目标方法进行解析,将请求消息绑定到处理方法的入参中。
数据绑定的核心部件是 DataBinder,运行机制如下
- Debug 一下 validate 得到验证 errors 信息
10.5 取消某个属性的绑定
10.5.1 使用实例
● 说明
在默认情况下,表单提交的数据都会和 pojo 类型的 javabean 属性绑定,如果程序员在开发中,希望取消某个属性的绑定,也就是说,不希望接收到某个表单对应的属性的值,则可以通过 @InitBinder 注解取消绑定.
- 编写一个方法, 使用@InitBinder 标识的该方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
- @InitBinder 方法不能有返回值,它必须声明为 void。
- @InitBinder 方法的参数通常是是 WebDataBinder
● 案例-不希望接收怪物的名字属性 - 修改 MonsterHandler.java , 增加方法
//取消绑定 monster的 name表单提交的值给 monster.name属性
//方法上需要标注@InitBinder springmvc底层会初始化WebDataBinder
//1. setDisallowedFields("name")表示取消指定属性的绑定.是可变形参,可以指定多个字段
// 即:当表单提交字段为 namel时,就不在把接收到的name值,填充到model数据monster li的name属性
//2.当将一个字段/属性,设置机制: springmvc在底层通过反射调用目标方法时,
//接收到http请求的参数和值,使用反射+注解技术取消对指定属性的填充为 disallowed,就不在接收表单提交的值
//3.那么这个字段/属性的值,就是该对象默认的值 (具体看程序员定义时指定)
//4.一般来说,如果不接收表单字段提交数据,则该对象字段的验证也就没有意义了
// ,可以注销掉,比如 注销 //@NotEmpty
// //@NotEmpty
// private String name;
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
//测试完,记得注销了.
dataBinder.setDisallowedFields("name");
}
- 修改 Monster.java
注销相应的验证绑定 - 完成测试(页面测试) 或 postman
10.5.2 注意事项和细节说明
- setDisallowedFields() 是可变形参,可以指定多个字段
- 当将一个字段/属性,设置为 disallowed,就不在接收表单提交的值,那么这个字段/属性的值,就是该对象默认的值 (具体看程序员定义时指定)
- 一般来说,如果不接收表单字段提交数据,则该对象字段的验证也就没有意义了可以注销掉,比如 注销 //@NotEmpty
11 中文乱码处理
11.1 自定义中文乱码过滤器
● 说明
当表单提交数据为中文时,会出现乱码,我们来解决一下(提示:先恢复 name 属性的绑定)
- 创 建 过 滤 器D:\lxc\javaProject\springMVC\src\com\codeSE\web\filter\MyCharacterFilter.java
package com.codeSE.web.filter;
import javax.servlet.*;
import java.io.IOException;
/*
* 编写过滤器,处理中文乱码
* */
public class MyCharacterFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@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() {
Filter.super.destroy();
}
}
- 配置 web.xml , 将该过滤器配置在最前面
<!--配置处理中文乱码的过滤器,全部拦截-->
<filter>
<filter-name>myCharacterFilter</filter-name>
<filter-class>com.codeSE.web.filter.MyCharacterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myCharacterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 完成测试
11.2 Spring 提供的过滤器处理中文
- 修改 web.xml , 换成 Spring 提供的过滤器,处理中文乱码
<!--配置处理中文乱码的过滤器,全部拦截-->
<!--<filter>-->
<!-- <filter-name>myCharacterFilter</filter-name>-->
<!-- <filter-class>com.codeSE.web.filter.MyCharacterFilter</filter-class>-->
<!--</filter>-->
<!--<filter-mapping>-->
<!-- <filter-name>myCharacterFilter</filter-name>-->
<!-- <url-pattern>/*</url-pattern>-->
<!--</filter-mapping>-->
<!--web.xml , 换成 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>
- 完成测试
12 处理 json 和 HttpMessageConverter
2.1 处理 JSON-@ResponseBody
● 说明:
项目开发中,我们往往需要服务器返回的数据格式是按照 json 来返回的
● 应用案例
- 引入处理 json 需要的 jar 包,注意 spring5.x 需要使用 jackson-2.9.x.jar 的包.
- 新建D:\lxc\javaProject\springMVC\src\com\codeSE\web\json\entity\Dog.java和新建D:\lxc\javaProject\springMVC\src\com\codeSE\web\json\DogHandler.java
package com.codeSE.web.json.entity;
public class Dog {
private String name;
private String address;
public Dog() {
}
public Dog(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
package com.codeSE.web.json;
import com.codeSE.web.json.entity.Dog;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class DogHandler {
/*
1.目标方法@ResponseBody,表返回的数据是json格式
2.springmvc底层根据目标方法@ResponseBody,返回指定格式,根据的http 请求来进行处理
4.底层原理我们在前面自定义GResponseBody讲过,这里原生的springmvc使用转换器
5.
* */
@RequestMapping(value = "/json/dog")
@ResponseBody
public Dog getDog() {
//返回对象
// springmvc会根据你的设置,转成json格式数据返回
Dog dog = new Dog();
dog.setName("一号狗");
dog.setAddress("小明家");
return dog;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>json 提交</title>
</head>
<body>
<h1>请求一个 json 数据</h1>
<button id="getJson" onclick="send()">点击获取 json 数据</button>
<script>
function send() {
var element = document.querySelector("#getJson");
console.log(element)
window.fetch(`/springMVC/json/dog?time=`+new Date()).then(res=>{
console.log(res)
})
return false
}
</script>
</body>
</html>
- 完成测试(页面方式), 浏览器 http://localhost:8080/springmvc/json.jsp
12.2 处理 JSON-@RequestBody
● 应用案例
- 前面是通过表单, 或者 url 请求携带 参数名=参数值 把数据提交给目标方法
- 给大家举例客户端发送 json 字符串数据,
- 使用 SpringMVC 的 @RequestBody 将客户端提交的 json 数据,封装成 JavaBean 对象
- 再把这个 javabean 以 json 对象形式返回
- 修改 json.jsp, 增加发送 json 数据代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>json 提交</title>
</head>
<body>
<h1>请求一个 json 数据</h1>
<button id="getJson" onclick="send()">点击获取 json 数据</button>
<hr/>
u:<input id="userName" type="text"><br/>
a:<input id="age" type="text"><br/>
<button id="postJson" onclick="postMsg()">点击发送 json 数据</button>
<script>
function send() {
window.fetch(`/springMVC/json/dog?time=` + new Date()).then(res => {
console.log(res)
})
return false
}
function postMsg() {
var userName = document.querySelector("#userName").value
var age = document.querySelector("#age").value
window.fetch(
`/springMVC/json/save?time=` + new Date(), {
headers: {
"Content-Type": "application/json;charset=utf-8"
},
method: 'post',
body: JSON.stringify({
userName,
age
})
}).then(async res => {
console.log(await res.json())
})
}
</script>
</body>
</html>
- 增 加 处 理 json 代 码 , 注 意 : 用 的 是 @PostMapping , 等
价:@RequestMapping(method = RequestMethod.POST)
/*
*
* 1.@RequestBody User user在形参指定了RequestBody
* 2.springmvc就会将提交的json字符串数据填充给指定给javaBean
* */
@PostMapping(value = "/json/save")
@ResponseBody
public User save(@RequestBody User user){
System.out.println("---user---"+user);
return user;
}
- 增加 JavaBean : User.java
- 并完成测试(页面方式), 浏览器 http://localhost:8080/springmvc/json.jsp
- 并完成测试(Postman 方式)
12.3 处理 JSON-注意事项和细节
- 目标方法正常返回 JSON 需要的数据, 可以是一个对象, 也可以是一个集合
@GetMapping(value = "/json/dogs")
@ResponseBody
public List<Dog> dogs() {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("大黄狗", "小新的家"));
dogs.add(new Dog("大黄狗2", "小新2的家"));
dogs.add(new Dog("大黄狗3", "小新3的家"));
return dogs;
}
- 前面讲解的是返回一个 Dog 对象->转成 Json 数据格式返回
- @ResponseBody 可以直接写在 controller 上,这样对所有方法生效
- @ResponseBody + @Controller 可以直接写成 @RestController , 我们看一下源码!
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
}
12.4 HttpMessageConverter<T>
● 基本说明
SpringMVC 处理 JSON-底层实现是依靠 HttpMessageConverter来进行转换的
● 工作机制简图
● 处理 JSON-底层实现(HttpMessageConverter<T>)
- 使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入参中, 或将响应
结果转为对应类型的响应信息,Spring 提供了两种途径:
√ 使用 @RequestBody / @ResponseBody 对目标方法进行标注
√ 使用 HttpEntity / ResponseEntity 作为目标方法的入参或返回值 - 当 控 制 器 处 理 方 法 使 用 到 @RequestBody/@ResponseBody 或
HttpEntity/ResponseEntity 时, Spring 首先根据请求头或响应头的 Accept 属性选择匹 配 的 HttpMessageConverter, 进 而 根 据 参 数 类 型 或 泛 型 类 型 的 过 滤 得 到 匹 配 的HttpMessageConverter, 若找不到可用的 HttpMessageConverter 将报错
12.5 文件下载-ResponseEntity</T>
● 说明:
在 SpringMVC 中,通过返回 ResponseEntity<T>的类型,可以实现文件下载的功能
- 修改 json.jsp
<hr/>
<h1>下载文件测试</h1>
<a href="<%=request.getContextPath()%>/downFile">点击下载文件</a>
- 增加方法
//响应用户下载文件的请求
@RequestMapping("/downFile")
public ResponseEntity<byte[]> downFile(HttpSession session) throws IOException {
//1.钩建一个ResponseEntity对象1.的http响应头headers2. http响应状态3.下载的文件
//1.先获取到下载文件的inputStream
InputStream resourceAsStream = session.getServletContext().getResourceAsStream("/images/aa.webp");
//2.开辟一个存放文件的byte数组,这里使用byte[]是可以支持二进制数据
byte[] bytes = new byte[resourceAsStream.available()];
//3.将下载文件的数据,读入到byte[]
resourceAsStream.read(bytes);
//4.创建返回的HttpStatus
HttpStatus httpStatus = HttpStatus.OK;
//5.创建返回的httpHeaders
HttpHeaders httpHeaders = new HttpHeaders();
//指定返回的数据,客户端应当以附件形式处理
httpHeaders.add("content-Disposition" , "attachment;filename=2.jpg");
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, httpHeaders, httpStatus);
return responseEntity;
}
文件下载响应头的设置
content-type 指示响应内容的格式
content-disposition 指示如何处理响应内容。
一般有两种方式:
inline:直接在页面显示
attchment:以附件形式下载
- 完成测试
13 SpringMVC 文件上传
13.1 基本介绍
● 基本介绍
- Spring MVC 为 文 件 上 传 提 供 了 直 接 的 支 持 , 这 种 支 持 是 通 过 即 插 即 用 的MultipartResolver 实 现 的 。 Spring 用 Jakarta Commons FileUpload 技 术 实 现 了 一 个MultipartResolver 实现类:CommonsMultipartResovler
- Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver
应用实例-代码实现
1.引入 springmvc 文件上传需要的 jar 包
2.新增
<h1>文件上传的演示</h1>
<form action="fileUpload" method="post"
enctype="multipart/form-data">
文件介绍:<input type="text" name="introduce"><br>
选择文件:<input type="file" name="file"><br>
<input type="submit" value="上传文件">
</form>
//处理文件上传请求
@PostMapping("/fileUpload")
public String fileUpload(@RequestParam(value = "file") MultipartFile file, HttpServletRequest
request,String introduce) throws IOException {
//接收到提交的文件名
String originalFilename = file.getOriginalFilename();
System.out.println("上传的文件名=="+originalFilename);
System.out.println("上传的文描述=="+introduce);
//得到要把上传文件保存到哪个路径[全路径:包括文件名]
String fileFullPath = request.getServletContext().getRealPath("/images/" + originalFilename);
//创建文件
File saveToFile = new File(fileFullPath);
//上传的文件,转存到saveToFile
file.transferTo((saveToFile));
return "success";
}
3.配 置 文 件 上 传 解 析 器 D:\lxc\javaProject\springMVC\src\applicationContext-mvc.xml
<!--配置文件上传需要的bean-->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
4.完 成 测 试 ( 页 面 方 式 ), 看 文 件 是 否 成 功 上 传
14 自定义拦截器
14.1 什么是拦截器
● 说明
- Spring MVC 也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定
的功能. - 自定义的拦截器必须实现 HandlerInterceptor 接口
● 自定义拦截器的三个方法 - preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request 进行处理。
- postHandle():这个方法在目标方法处理完请求后执行
- afterCompletion():这个方法在完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
14.2 自定义拦截器执行流程分析图
14.3 自定义拦截器应用实例
14.3.1 快速入门
● 应用实例需求
完成一个自定义拦截器,学习一下如何配置拦截器和拦截器的运行流程
● 应用实例-代码实现
- 创 建D:\lxc\javaProject\springMVC\src\com\codeSE\web\interceptor\MyInterceptor01.java
package com.codeSE.web.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//配置一个 @Component交给 springmvc管理
@Component
public class MyInterceptor01 implements HandlerInterceptor {
/**
* 1. preHandle()在目标方法执行前被执行
* 2.如果preHandle()返回于false ,不再执行目标方法
* 3.该方法可以获取到request, response , handler
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("-------第一个自定义拦截器MyInterceptor01的preHandle()执行------");
return true;
}
/**
* 1.在目标方法执行后,会执行postHandle
* 2. 方法可以获取到目标方法,返回的ModelAndView
* @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("-------第一个自定义拦截器MyInterceptor01的postHandle()执行------");
}
/**
* 1.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("-------第一个自定义拦截器MyInterceptor01的afterCompletion()执行------");
}
}
- D:创 建\lxc\javaProject\springMVC\src\com\codeSE\web\interceptor\FurnHandler.java
package com.codeSE.web.interceptor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class FurnHandler {
@RequestMapping(value = "/hi")
public String hi() {
System.out.println("---FurnHandler--hi()---");
return "success";
}
@RequestMapping(value = "/hello")
public String hello() {
System.out.println("---FurnHandler--hello()---");
return "success";
}
}
- 在 D:\lxc\javaProject\springMVC\src\applicationContext-mvc.xml 配置拦截器
<!--配置自定义拦截器-->
<mvc:interceptors>
<!--第一种配置方式:直接引用对应的myInterceptor01,会拦截所有-->
<!--<ref bean="myInterceptor01"/>-->
<!--第二种配置方式:指定要拦截的路径-->
<!--<mvc:interceptor>-->
<!-- <mvc:mapping path="/hi"/>-->
<!-- <ref bean="myInterceptor01"/>-->
<!--</mvc:interceptor>-->
<!--第三种配置方式:使用通配符指定要拦截的路径-->
<mvc:interceptor>
<mvc:mapping path="/h*"/>
<mvc:exclude-mapping path="/hello"/><!--不拦截 "/hello"-->
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
注意:拦截器配置在spring的xml文件里面,过滤器配置在web.xml文件里
面
- 创interceptor01.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试自定义拦截器</title>
</head>
<body>
<h1>测试自定义拦截器</h1>
<a href="hi">测试自定义拦截器</a><br><br>
<a href="hello">登录</a>
</body>
</html>
- 完成测试(页面方式) , 浏览器http://localhost:8080/springmvc/interceptor.jsp
14.3.2 注意事项和细节
1、默认配置是都所有的目标方法都进行拦截, 也可以指定拦截目标方法, 比如只是拦截 hi
2、mvc:mapping 支持通配符, 同时指定不对哪些目标方法进行拦截
3、拦截器需要配置才生效,不配置是不生效的.
4、如果 preHandler() 方法返回了 false, 就不会执行目标方法(前提是你的目标方法被拦截了), 程序员可以在这里根据业务需要指定跳转页面.
14.3.3 Debug 执行流程
14.4 多个拦截器
14.4.1 多个拦截器执行流程示意图
14.4.2 应用实例 1
● 应用实例-代码实现
- 创 建D:\lxc\javaProject\springMVC\src\com\codeSE\web\interceptor\MyInterceptor02.java
package com.codeSE.web.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyInterceptor02 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("-------第一个自定义拦截器MyInterceptor02的preHandle()执行------");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("-------第一个自定义拦截器MyInterceptor02的postHandle()执行------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("-------第一个自定义拦截器MyInterceptor02的afterCompletion()执行------");
}
}
- 在 D:\lxc\javaProject\springMVC\src\applicationContext-mvc.xml 配置拦截器
<mvc:interceptor>
<mvc:mapping path="/hi"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/hi"/>
<ref bean="myInterceptor02"/>
</mvc:interceptor>
- 完成测试(页面方式), 浏览器 http://localhost:8080/springmvc/interceptor.jsp
14.4.2.2 注意事项和细节
- 如果第 1 个拦截器的 preHandle() 返回 false , 后面都不在执行
- 如 果 第 2 个 拦 截 器 的 preHandle() 返 回 false , 就 直 接 执 行 第 1 个 拦 截 器 的
afterCompletion()方法, 如果拦截器更多,规则类似 - 说明: 前面说的规则,都是目标方法被拦截的前提
14.4.3 应用实例 2
- 需求: 如果用户提交的数据有禁用词(比如 病毒),则,在第 1 个拦截器就返回,不执行目标方法。
- 代码实现: 创建D:\lxc\javaProject\springMVC\web\WEB-INF\pages\warning.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>警告</title>
</head>
<body>
<h1>不要乱讲话~</h1>
</body>
</html>
- 代 码 实 现 : 修 改MyInterceptor01.java
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("-------第一个自定义拦截器MyInterceptor01的preHandle()执行------");
String keyword = request.getParameter("keyword");
if("病毒".equals(keyword)){
request.getRequestDispatcher("/WEB-INF/pages/warning.jsp").forward(request,response);
return false;//拒绝
}
System.out.println("得到到keyword=="+keyword);
return true;//放行
}
- 测试
15 异常处理
15.1 异常处理-基本介绍
● 基本介绍
- Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
- 主要处理 Handler 中用
@ExceptionHandler
注解定义的方法。 - ExceptionHandlerMethodResolver 内 部 若 找 不 到 @ExceptionHandler 注 解 的 话 , 会 找
@ControllerAdvice
类的@ExceptionHandler 注解方法, 这样就相当于一个全局异常处理器
15.2 局部异常
15.2.1 应用实例
● 应用实例需求
演示局部异常处理机制
如果不处理异常, 非常的不友好
- 创建D:\lxc\javaProject\springMVC\src\com\codeSE\web\exception\MyExceptionHandler.java
package com.codeSE.web.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;
@Controller
public class MyExceptionHandler {
/*
* 1.编写方法,模拟异常,算术异常
2.如果我们不做异常处理,是由tomcat默认页面显示
* */
@RequestMapping("/testException01")
public String test01(Integer num) {
int i = 9 / num;
return "success";
}
//局部异常就是直接在这个 Handler中编写即可
//Exception ex:生成的异常对象,会传递给ex,通过ex可以得到相关的信息,可以写自己的业务逻辑
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String localException(Exception ex, HttpServletRequest request) {
System.out.println("异常信息是~" + ex.getMessage());
//如何将异常的信息带到下一个页面.
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
}
- 创建D:\lxc\javaProject\springMVC\web\exception.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息</title>
</head>
<body>
<h1>测试异常</h1>
<a href="<%=request.getContextPath()%>/testException01?num=0">点击测试异常</a><br><br>
</body>
</html>
- 创建显示异常信息页面 /WEB-INF/pages/exception_mes.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常提示信息</title>
</head>
<body>
<h1>朋友,发生异常,信息如下 :</h1>
${reason }
</body>
</html>
- 测试(页面方式) , 浏览器 http://localhost:8080/springmvc/exception.jsp
15.2.2 Debug 处理流程
15.3 全局异常
15.3.1 应用实例
● 应用实例需求
演 示 全 局 异 常 处 理 机 制 , ExceptionHandlerMethodResolver 内 部 若 找 不 到@ExceptionHandler 注解的话,会找 @ControllerAdvice 类的@ExceptionHandler 注解方法,这样就相当于一个全局异常处理器
● 应用实例-代码实现
- 创 建D:\lxc\javaProject\springMVC\src\com\codeSE\web\exception\MyGlobalException.java
package com.codeSE.web.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
/*
*@ControllerAdvice全局异常就不管是哪个Handler抛出的异常,都可以捕获,@ExceptionHandler({异常类型})
* */
@ControllerAdvice
public class MyGlobalException {
@ExceptionHandler({NumberFormatException.class, NullPointerException.class})
public String globalException(Exception ex, HttpServletRequest request) {
System.out.println("全局异常处理-" + ex.getMessage());
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
}
- 修改 MyExceptionHandler.java , 增加方法
@RequestMapping("/testGlobalException")
public String test02() {
int i = Integer.parseInt("hello");
return "success";
}
-
修改 exception.jsp
-
完成测试(页面方式) 浏览器 http://localhost:8080/springmvc/exception.jsp
15.3.3 异常处理时:局部异常 优先级高于 全局异常
15.4 自定义异常
15.4.1 应用实例
● 应用实例需求
通过@ResponseStatus 注解, 可以自定义异常的说明
● 应用实例-代码实现
- 创 建D:\lxc\javaProject\springMVC\src\com\codeSE\web\exception\AgeException.java
package com.codeSE.web.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
//这个是给tomcat默认页面看的
@ResponseStatus(reason = "年龄在0-120之间",value = HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException{
public AgeException() {
}
public AgeException(String message) {
super(message);
}
}
- 修改 MyExceptionHandler.java, 增加方法
@RequestMapping("/testException02")
public String test03() {
int i = Integer.parseInt("hello");
throw new AgeException("年龄必须再1-120之间~~~");
}
-
修改 exception.jsp, 增加超链接
-
并完成测试, 浏览器 http://localhost:8080/springmvc/exception.jsp
15.5 SimpleMappingExceptionResolver
15.5.1 基本说明
● 基本说明
- 如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver
- 它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
- 需要在 ioc 容器中配置
15.5.2 应用实例
● 应用实例-需求
对数组越界异常进行统一处理,使用 SimpleMappingExceptionResolver
应用实例-代码实现 - 修改 MyExceptionHandler.java , 增加方法test04
//统一的异常
@RequestMapping(value="/testException03")
public String test04(){
int[] arr = new int[]{3,9,10,190};
//抛出一个数组越界的异常ArrayIndexOut0fBoundsException
System.out.println(arr[90]);
return "success";
}
- 配置 D:\lxc\javaProject\springMVC\src\applicationContext-mvc.xml
<!--配置统一处理异常Bean-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="excludedExceptions">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
</props>
</property>
</bean>
- 创建/WEB-INF/pages/arrEx.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>数组越界异常</title>
</head>
<body>
<h1>
统一异常: 数组越界异常
</h1>
</body>
</html>
-
修改
exception.jsp -
并完成测试(页面测试), 浏览器 http://localhost:8080/springmvc/exception.jsp
15.5.3 对未知异常进行统一处理
● 应用实例-需求
对未知异常进行统一处理,使用 SimpleMappingExceptionResolver
● 应用实例-代码实现
- 修改 MyExceptionHandler.java , 增加方法 test05
//如果发生了没有归类的异常, 可以给出统一提示页面
@RequestMapping(value="/testException04")
public String test05(){
String str = "hello";
//这里会抛出StringIndexOut0fBoundsException
char c = str.charAt(10);
return "success";
}
- 配置 D:\lxc\javaProject\springMVC\src\applicationContext-mvc.xml
<!--配置统一处理异常Bean-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="excludedExceptions">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
<prop key="java.lang.Exception">allEx></prop>
</props>
</property>
</bean>
- 创建/WEB-INF/pages/otherEx.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>未知异常信息</title>
</head>
<body>
<h1>朋友,系统发生了未知异常~</h1>
</body>
</html>
- 修改 exception.jsp , 增加超链接
- 并完成测试(页面测试), 浏览器 http://localhost:8080/springmvc/exception.jsp
15.5.4 异常处理的优先级梳理
● 异常处理的优先级
局部异常 > 全局异常 > SimpleMappingExceptionResolver > tomcat 默认机制
16 SpingMVC 执行流程-源码剖析
- Dubug SpringMVC 执行流程
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 [这儿][2],
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:
这将产生一个流程图。:
- 关于 Mermaid 语法,参考 [这儿][3],
FLowchart流程图
我们依旧会支持flowchart的流程图: