SpringMVC
一、SpringMVC概述
- Spring为展现层面提供基于 MVC 设计理念的 Web 框架
- SpringMVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无需实现任何接口。
注:POJO( Plain Old Java Object : 普通的Java对象 ) - 支持 REST(restful) 风格的 URL 请求。
- 采用松散耦合可插拔的组件结构,比其他 MVC 框架更具扩展性和灵活性。
注:MVC,M是模型层,用于装载传输数据,V 是视图层,用于展示数据,C是控制层,是指控制器接受用户的请求并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
二、SpringMVC 是什么
- 一种轻量级的,基于 MVC 的 Web 层应用框架,偏前端,而不是基于业务逻辑层,Spring 框架的一个后续产品。
三、SpringMVC 能干什么
- 天生与Spring框架集成,如(IOC,AOP)
- 支持Restful风格
- 使得 Web 开发更简洁
- 支持灵活的 URL 到页面控制器的映射
- 容易与其他视图技术集成,如(Velocity,FreeMarker 等)
- 因模型数据不存放在特定的 API 而是放在一个 Module 里面( Map数据结构实现 )因此容易被其他框架使用
- 非常灵活的数据验证,格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的 API
- 强大简单的异常处理
- 对静态资源的支持
- 支持灵活的本地化,主题等解析
四、SpringMVC怎么用
SpringMVC 的搭建过程
1.创建 web 项目,导入 jar 包
2.配置 web.xml
在 web.xml 中来配置 SpringMVC 的核心控制器( 前端控制器:DispatcherServlet )
配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>spring_mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring_mvc</servlet-name>
<!--若是只有 / ,那么只有访问为一个请求时,才会被该 Servlet 程序处理-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
作用:加载 SpringMVC 的配置文件。在上面的配置方式下,核心控制器会自动的加载配置文件(配置文件需要手动创建,Spring只负责自动加载),此时的配置文件有默认的位置和名称,默认位置为 WEB-INF下,默认名称为就为上面配置文件中 servlet-name 的值 - servlet.xml,如 spring_mvc-servlet.xml。
如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描组件,将加上 @Controller 注解的类作为 SpringMVC 的控制层-->
<context:component-scan base-package=""></context:component-scan>
<!--配置视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀-->
<property name="prefix" value="/WEB_INF/view/"></property>
<!--配置后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
当加载完配置文件,spring_mvc就会根据扫描组件,找到控制层。
创建一个POJO( JavaBean ),在此类上加上 @Controller 注解,SpringMVC就会将此类作为控制层,处理请求和响应。
在控制层中,需要在对应的方法上设置注解 @RequestMapping ( value = "xxx" ),SpringMVC就是通过此注解将请求路径与控制层中的对应方法进行匹配,此时请求路径为 :localhost:8080/项目名称/xxx 。
处理请求的方法会返回一个字符串(这是其中一种方式) 即视图名称,最后会通过配置文件中配置的视图解析器;来实现视图跳转。例如:
<!--配置视图解析器
作用:将 prefix(/WEB-INF/view/) + 视图名称(success) + suffix(.jsp) 来确定最终要跳转的页面,如下
/WEB-INF/view/success.jsp
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀-->
<property name="prefix" value="/WEB-INF/view/"></property>
<!--配置后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
若请求地址值中附带有参数时,例如:hello?username=lisi&password=1222 ,那么在对应的控制器方法中,加上与请求地址的参数名称相同的方法参数即可,例如:public String HelloWorld(String username, String password) { } ,若对应的属性在一个实体 Bean 中存在,则可以直接在方法参数中添加对应的 Bean 对象参数,Spring 能够自动的获取请求的参数并赋值到该对象中。
常见注解以及使用过程(1)
-
@RequestMapping 注解
该注解除了修饰方法还能够修饰类,同时修饰类时和方法时,方法中的@RequestMapping访问路径其实是相对于类上面的@RequestMapping中的路径的,而类上@RequestMapping中的路径则相对于Web项目的根路径 /。例如:
@Controller //控制器 @RequestMapping("/SpringMVC") //类定义处 public class SpringMVCTest2 { private static final String successs = "success"; @RequestMapping("/tesrRequestMapping")//方法定义处 public String tesrRequestMapping() { System.out.println("tesrRequestMapping"); return successs; } }
若类定义处没有定义 @RequestMapping ,则方法定义处中的路径就直接相对于 Web项目的 根目录 / 。
使用 method 属性指定请求方式
//使用 method 属性来指定请求方式 @RequestMapping(value = "/testMethod" ,method = RequestMethod.POST) public String testMethod(){ System.out.println("testMethod"); return successs; }
-
使用@RequestMapping 来映射请求( params 和 handers 映射)
代码如下:
//可以使用 params 和 handers 来更加精确的映射请求 , params 和 handers 支持简单的表达式。 @RequestMapping(value = "/testParamsAndHeaners", params = {"username", "age!=10"} ,headers = {"Accept-Language: eh-US,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"}) public String testParamsAndHeaners() { System.out.println("testParamsAndHeaners"); return successs; }
-
使用@RequestMappping映射请求2(Ant 路径)
-
@PathVariable 映射 URL 绑定的占位符
代码实例:
/* @PathVariable 该注解可以将请求路径中的指定部分,通过占位符${xxx}映射成目标方法中的参数, 请求地址为:SpringMVC/testPathVariable/1 */ @RequestMapping(value = "/testPathVariable/{id}") public String testPathVariable(@PathVariable("id") Integer id) { System.out.println("testPathVariable "+id); return successs; }
REST
REST简介
示例:
REST风格请求代码如下:<%--表现层状态转化 单纯的 http 协议是一个无状态协议,资源在服务器上没有变化,而当加上不同类型的请求时 例如 get 请求,就代表获取服务器上的数据,这时,服务器上的数据收到影响,而表现层代表 把资源呈现出来的形式,如文本形式,图片形式,HTML形式,通过 get 请求获取数据,在表现层上进行呈现 使得表现层的状态发生变化,就叫表现层状态转化,这种转发,在SpringMVC中可以通过 HiddenHttpMethodFilter 来实现将post请求转化为不同的请求,影响表现层,实现表现层状态转化。 --%> <%--get请求--%> <a href="SpringMVC/testRestGet/1">RestGet</a> <br> <br> <%--post请求--%> <form action="SpringMVC/testRestPost" method="post"> <input type="submit" value="testPost"> </form> <br> <%--put请求--%> <form action="SpringMVC/testRestPut/2" method="post"> <%--通过name属性标识为一个请求,通过 value 属性值指定请求的类型--%> <input type="hidden" name="_method" value="PUT"> <input type="submit" value="testPUT"> </form> <%--delete请求--%> <form action="SpringMVC/testRestDelete/3" method="post"> <%--通过name属性标识为一个请求,通过 value 属性值指定请求的类型--%> <input type="hidden" name="_method" value="DELETE"> <input type="submit" value="testDelete"> </form>
接收的代码
/* 表现层状态转化 HiddenHttpMethodFilter */ //指定请求方式为 get @RequestMapping(value = "/testRestGet/{id}", method = RequestMethod.GET) public String testRestGet(@PathVariable("id") Integer id) { System.out.println("testRestGet:" + id); return successs; } //指定请求方式为 post @RequestMapping(value = "/testRestPost", method = RequestMethod.POST) public String testRestPost() { System.out.println("testRestPost"); return successs; } //指定请求方式为 put @ResponseBody //对于Tomcat8版本,并不支持 PUT,DELETE 请求,需要在控制器方法上加上该注解 @RequestMapping(value = "/testRestPut/{id}", method = RequestMethod.PUT) public String testRestPut(@PathVariable("id") Integer id) { System.out.println("testRestPut" + id); return successs; } //指定请求方式为 delete @ResponseBody @RequestMapping(value = "/testRestDelete/{id}", method = RequestMethod.DELETE) public String testRestDelete(@PathVariable("id") Integer id) { System.out.println("testRestDelete:" + id); return successs; }
常见注解及使用过程(2)
-
@RequestParam注解
代码演示:
/*
通过 @RequestParam 获取请求路径中的指定名称的参数
其中 value 属性代表获取的参数名
required 代表参数是否必需的,默认为 true ,当值为 true 时,若请求中没有该参水,则会产生异常
defaultValue 代表请求中没有参数时的默认值
*/
@RequestMapping(value = "/testRequestParam")
public String testRequestParam(@RequestParam(value = "username", required = false, defaultValue = "0")
String username, @RequestParam("age") Integer age) {
System.out.println("user:" + username + "\n" + "age:" + age);
return successs;
}
-
@RequestHeader 注解
-
@CookieValue 注解
使用POJO对象绑定请求参数

代码演示:
/*
使用POJO作为参数
直接设置参数为实体对象即可,Spring会自动进行属性注入和级联属性注入
实际上又是通过空参构造器创建对象,调用对象中的 set 方法注入属性。
*/
@RequestMapping(value = "/testPOJO", method = RequestMethod.POST)
public String testPOJO(User user) {
System.out.println("testPOJO:\n" + "username:" + user.getUsername() + "\n" + "password:" + user.getPassword()
+ "\nemail:" + user.getEmail() + "\nage:" + user.getAge() + "\nprovince:" + user.getAddress().getProvince() + "\n"
+ "\ncity:" + user.getAddress().getCity() + "\n");
return SUCCESS;
}
请求参数:
<%--使用POJO对象作为参数
Spring自动根据参数名 name 的值,匹配实体对象中的属性名,进行赋值和级联赋值
因此参数名 name 的值必须和 POJO 中对应的属性名一致,否则无法注入。
--%>
<h3>使用POJO对象作为参数的测试</h3>
<form action="SpringMVC/testPOJO" method="post">
username: <input type="text" name="username">
<br>
password: <input type="password" name="password">
<br>
email: <input type="text" name="email">
<br>
age: <input type="text" name="age">
<br>
<%--级联属性,通过 属性对象.属性值 来设置 --%>
province: <input type="text" name="address.province">
<br>
city: <input type="text" name="address.city">
<input type="submit" value="提交">
</form>
使用 Servlet 原生 API 作为参数

代码如下:
/*
使用原生 Servlet 作为参数
*/
@RequestMapping(value = "/testServletAPI")
public String testServletAPI(HttpServletRequest request, HttpServletResponse response) {
System.out.println(request + "\n" + response);
return SUCCESS;
}
ModelAndView处理模型数据

代码演示:
/*
使用ModelAndView处理模型数据
目标方法的返回值可以是模型数据和和视图信息
*/
@RequestMapping(value = "/testModuelAndView")
public ModelAndView testModelAndView() {
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加模型数据到ModuelAndView中,实际上是将数据模型 module 放入 request 域中,通过键值调用
modelAndView.addObject("time",new Date());
return modelAndView;
}
请求成功后,通过键在 request 调用:
${ requestScope.time }
Map处理模型数据

代码演示
/*
Map处理数据模型
在目标方法中添加Map类型的参数,实际上也可以是 Moduel 或 ModuelMap 类型
实质是将数据放入ModuelAndView中的ModuelMap中
获取数据时,也是可以通过 request 域对象获取
*/
@RequestMapping(value = "/testMap")
public String testMap(Map<String, Object> map) {
map.put("names", Arrays.asList("tom", "jery", "jack", "Mike"));
return SUCCESS;
}
处理模型数据之SessionAttributes

/*
@SessionAttributes 该注解可以通过 value 和 types 指定某种类型的模型数据放入Session域中,
同时也放入Request域中,value 和 types 的参数可以有多个
注:该注解只能使用在类上,不能使用于方法上
*/
@SessionAttributes(value = {"user", "user1"}, types = {User.class, Address.class})
代码演示
/*
处理数据模型之 @SessionAttributes 注解
*/
@RequestMapping(value = "testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map) {
User user = new User("Mike", "111111", "Mike@163.com", 16);
map.put("user", user);
return SUCCESS;
}
实际使用时,可以同时在 request 和 session 域中获取到
req:${requestScope.user} <br>
Session:${sessionScope.user} <br>
@ModelAttribute 注解
被该注解标记的方法会在每个目标方法执行前被 SpringMVC 调用
代码演示:
/*
有 @ModelAttribute 注解标记的方法,会在每个目标方法执行前被 SpringMVC 调用
*/
@ModelAttribute
public void getUser(@RequestParam(value = "id", required = false) String id,
Map<String, Object> map) {
//模拟从数据库获取数据
User user = new User("1", "Tom", "12121212", "192812@13.com", 10, new Address("dwdw", "dwdwd"));
System.out.println("模拟数据库获取user:" + user);
map.put("user", user);
}
/*
@ModuelAttribute应用场景
*/
@RequestMapping(value = "/testModuelAttribute", method = RequestMethod.POST)
public String testModuelAttribute(User user) {
System.out.println("修改user:" + user);
return SUCCESS;
}
页面代码:
<h3>使用@ModuelAttribute注解</h3>
<%--
1.要求密码不能被修改
2.直接在表单回显修改后的数据
--%>
<form action="SpringMVC/testModuelAttribute" method="post">
<input type="hidden" name="id" value="1">
username: <input type="text" name="username" value="Tom">
<br>
email: <input type="text" name="email" value="11223@1212.com">
<br>
age: <input type="text" name="age" value="12">
<br>
province: <input type="text" name="address.province" value="sichuan">
<br>
city: <input type="text" name="address.city" value="deyang">
<input type="submit" value="提交">
</form>
@ModelAttribute运行原理
1.运行流程
(1) 执行 @ModeAttribute 修饰的方法,获取模型数据,把模型数据放入 Map 中,键为 :user
(2) SpringMVC从 Map 中取出 user 对象,并把表单的请求参数赋给该 user 对象的对应属性
(3) Spring把对象传入目标方法的参数 user 中
注:在 @MudelAttribute 修饰的方法中,放入到 Map 中的数据的键,需要和目标方法入参的类型首字母小写的字符串相同。
2.@ModelAttribute源码分析
(1) 调用 @ModelAttribute 注解修饰的方法,实际上把 @ModelAttribute 方法中的 Map 数据放在了 implicitModel 中
(2) 解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder 对象的 target 属性
创建 WebDataBinder 对象
① 确定 objectName属性:
若传入的 attrName 属性值为空,则 objectName 为类名第一个字母小写
注:若目标方法的 Bean 属性值使用了 @ModelAttribute 来修饰,则 attrName 值即为 @ModelAttribute 的 value 值。
② 确定 terget 属性:
在 impliciiModel 中查找 attrName 对应的属性值,若不存在,则验证当前 handler 是否使用了 @SessionAttributes 进行 修饰,若使用了,则尝试从 Session 中获取 attrName 对应的属性值,若 Session 没有对应的属性,则抛出异常。
若 handler 没有使用 @SessionAttributes 进行修饰,或 @SesssionAttribuutes 中没有使用 value 值指定的键和attrName 相互匹配,则通过反射创建 Bean 对象。
(3) SpringMVC把请求表单的请求参数赋给了 WeDataBinder 的 target 对应的属性
(4) SpringMVC 把 WebDataBinder 的 attrName 和 target 给到 implicitModel,进而传导 Request 域对象中
(5) 把 WebDataBinder 的 target 作为参数传递给目标方法的对应入参
3.SpringMVC 确实目标方法 Bean 类型入参的过程
(1) 确定一个 key:若目标方法的Bean类型的参数没有使用 @ModelAttribute 进行修饰,则使用 Bean 名称的第一个字母小写来作为 key,若使用了 @ModelAttribute 来修饰,则使用 @ModelAttribute 注解的 value 属性值来作为 key
(2) 在 implicitModel 中查找 key 对应的对象,若存在,则作为入参传入,(若在 @ModelAttribute 标记方法中的Map中保存过 key 且和 之前确定的 key 一致,则会获取到这个对象), 若不存在 key 对应的对象,则检查当前的 handler 是否使用 @SessionAttibute 注解修饰,若使用了该注解,且这个注解的 value 属性值中,包含了 key ,则会从 HttpSession 中来获取 key 所对应的 value 值,若存在,则直接传入到目标方法的入参中,若不存在,则抛出异常。
若handler 没有标识 @SsessionAttribute ,或 @SessionAttribute的value值中不包含 key,则通过反射来创建 Bean 类型的参数,传入为目标方法的入参
(3) SpringMVC会把 key 和对应的 value(Bean类型的对象) 保存到 implicitModel中,进而会保存到 request 中。
注: handler 是对 Controller 的 Bean 本身和请求 Method( 目标方法 ) 的包装,也是一个类,有实例化的对象。
@ModelAttribute 修饰 Bean 类型目标方法的入参
1.被 @MudelAttribute 注解修饰的方法,会在每个目标方法执行之前被 SpringMVC 调用
2.@ModelAttribute 注解也可以用来修饰目标方法 Bean 类型的入参,其 value 属性值有如下作用
> SpringMVC会利用 value 属性值在 implicitModel 中查找对应的键值,若存在,会传入到目标方法的入参中
> SpringMVC会以 @ModelAttribute 的 value 为 key ,Bean 类型的对象为 value 存入 request 中
@SessionAttributes注解引发的异常
1.在没有 @ModelAttribute 修饰的方法时,目标方法使用 Bean 类型作为入参,SpringMVC会在 implicitModel 中查找对应的键值为该目标方法的入参赋值,若没有找到,又会去检查 handler 是否使用了 @SeeionAttributes 注解修饰,若使用了且 value 属性的值中包含 对应的 key ,那么就会从 HttpSessoin 中根据 key 获取对应的值,若值不存在或为空,就会抛出由 @SessionAttribute 注解引发的异常,意为 Session没有对应的属性值,此时只要为目标方法的对应入参加上 @ModelAttribute 注解,通过 value 指定 key 的值与 @SessionAttribute 注解中的 value 值不一样即可。
2.在有 @ModelAttribute 注解修饰的方法的情况下,此异常不会出现。
注:实际上,直接使用 POJO 作为入参时,使用的也是如上的流程来为 Bean 类型的入参赋值,当@SessionAttributes 和 @ModelAttribute 注解存在的前提下,首先会根据这两个注解先使用如上流程后,再结合请求地址中的参数来为目标方法的入参赋值,否则若没有这两个注解,则直接通过反射与结合请求地址中的参数来为目标方法的入参赋值。
视图解析器流程分析
SpringMVC_JstlView

mvc:view-controller 标签
<!--配置直接转发的页面
可以直接响应转发的页面,而无需经过任何控制器和控制器中的目标方法
path:访问的路径(相当于RequestMapping(“/hello”))
view-name:是你所要的视图(如hello.jsp,相当于return “hello”) 配置了这个后对于/hello请求,
就会直接交给dispatcherServlet,然后使用ViewResolver进行解析。
注意:
使用了这个标签后必须配置 <mvc:annotation-driven />
否则会造成所有的@Controller注解无法解析,导致404错误。
如果请求存在处理器,则这个标签对应的请求处理将不起作用。因为请求是先去找处理器处理,如果找不到才会去找这个标签配置。
-->
<mvc:view-controller path="/success" view-name="success"></mvc:view-controller>
<mvc:annotation-driven></mvc:annotation-driven>
自定义视图
1.自定义一个视图类,实现 View 接口,实现其中方法,代码如下:
import org.springframework.web.servlet.View;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/*
自定义视图类
注:视图类以及视图类所属的包,必须和控制器以及所属的目标方法在同一个包下,否则无法访问到该视图
*/
public class HelloView implements View {
//返回视图的类型
@Override
public String getContentType() {
return "text/html";
}
//渲染视图
@Override
public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
httpServletResponse.setContentType("text/html; charset=utf-8");
httpServletResponse.getWriter().write("<h4>自定义视图</h4>");
}
}
2.配置视图解析器
<!--配置自定义视图的视图解析器:BeanNameViewResolver
自动根据视图类的的类名来解析视图,观察源码发现,BeanNameViewResolver 通过
视图类的实例来解析视图,因此,需要对视图类加上实例化的注解,如:@Component,
注:因为是通过视图名来实例化视图类,因此在没有指定 @Component 注解的 value 值的前提下,
默认视图名为视图类名首字母小写
-->
<bean id="beanNameViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver" ></bean>
3.为上面的视图解析器设置优先级
<!--设置视图解析器的优先级,值越小,优先级越高
没有固定的值,根据实际情况来,小于InternalResourceViewResolver的优先级即可
而对于InternalResourceViewResolver的优先级,因为该解析器笔记常用,因此该解析器的优先级为 Integer 的最大值,排在最后
否则就会阻碍其他的视图解析器
-->
<property name="order" value="100"></property>
对应优先级的问题,解析如下:
重定向

代码如下:
/*
重定向操作
*/
@RequestMapping("/testRedirect")
public String testRedirect(){
System.out.println("testRedirect");
return "redirect:/index.jsp";//redirect:重定向 forword:转发
}
基于 SpringMVC 快速搭建 RESTFUL风格的 CRUD
CRUD 需求一
CRUD 需求二
CRUD 需求三
CRUD 需求四
相关的类:
注:emptloyee:员工 ,department:部门
由于一些问题,该 CRUD 只观看教程,不做演示
静态资源的处理
<!--配置TomCat服务器中默认的 Servlet:DefaultServlet
注:当DefaultServlet所设置的<url-pattern>和开发人员所配置的 DispatecherServlet 的 <url-pattern>相同时,
以开发人员的Servlet优先
作用:当客户端发送请求,由于DefaultServlet 的 <url-pattern> 和开发人员设置的<url-pattern> 都为 /
会先通过 DispatcherServlet 来处理请求,来找该请求是否有相对应的处理器,有则处理,无则调用 DefaultServlet来处理
-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!--MVC驱动,其中一个功能就是和 DefaultServlet 搭配使用来处理静态资源的释放-->
<mvc:annotation-driven></mvc:annotation-driven>
数据绑定的流程
参考如下连接:https://www.cnblogs.com/softidea/p/10079869.html
自定义类型转换器
SpringMVC上下文自带的类型转换
自定义的类型转换
1.创建一个自定义的转换器类,实现转换器接口:Converter<S,T> ,使用注解进行实例化并实现数据转换的具体方法
import Bean.Address;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
public class AddressConverter implements Converter<String, Address> {
@Override
public Address convert(String s) {
if (s != null) {
//按照指定分隔符分割为数组
String[] vals = s.split("-");
if (vals.length == 2) {
return new Address(vals[1], vals[0]);
} else {
return new Address("默认", "默认");
}
}
return null;
}
}
2.将自定义数据转换类添加到配置文件:
<!--配置 Converter 数据类型转换器
1.配置 ConversionServiceFactoryBean:转换服务工厂
2.将自定义的数据类型转换注入到 ConversionServiceFactoryBean 的 converters 属性中,该属性值为 set
可以在 property 中使用 set 标签注入
-->
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="addressConverter"></ref>
</set>
</property>
</bean>
3.将 ConversionServiceFactoryBean 添加到 MVC 驱动中
<!--MVC驱动,其中一个功能就是和 DefaultServlet 搭配使用来处理静态资源的释放-->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean" ></mvc:annotation-driven>
此时,一旦表单提交,便可以通过自定义的转换器实现将指定的数据转化成指定的类型的功能。
mvc:annotation-driven 配置
@InitBinder 注解
简介
参考笔记(相对路径地址): typora-user-images/从原理层面掌握@InitBinder的使用【享学Spring MVC】 - 云+社区 - 腾讯云.html
数据类型转换
简介
日期类型:
1. 配置 MVC 驱动 mvc:annotation-driven
2. 在对应的 bean 属性上加上 DataTimeFormat 注解,注解值为日期的格式,例如 :yyy-MM-dd
数值类型:
1. 配置MVC驱动 mvc:annotation-driven
2. 在对应的属性上加上 NumberFormat 注解 ,注解值为数值的格式,例如:#,###,###.# (使用 # 代替数值)
JSR303数据校验
简介
1.如何校验
① 使用 JSR303 验证标准
② 加入 hibernate validator 验证框架
③ 在 SprinvMVC 配置文件中,加上 mvc:annotation-driven 驱动配置
④ 在对应 bean 属性上添加注解
⑤ 在目标方法 bean 类型的参数前面加上 @Valid 注解
2.验证出错跳转的页面
例如:
/*
数据校验测试
*/
@RequestMapping("/testDatavalidation")
//被 @Valid 注解修饰的校验 bean ,必须和绑定结果 BindResult 或 Errors 成对出现,二者之间不允许什么其他入参
public String Datavalidation(@Valid Book book, Errors result) {
System.out.println("Book:" + book);
if (result.getErrorCount() > 0) {
//遍历显示错误信息
for (FieldError fieldError : result.getFieldErrors()) {
System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage());
}
//如果验证出错,则转向定制页面,重定向和转化都可以
return "redirect:/index.jsp";
}
return SUCCESS;
}
SpringMVC返回JSON
1.前端 jsp 页面中,使用 JQuery 发起请求,请求的数据为 JSON 格式,代码如下
<h3>SpringMVC处理JSON</h3>
<script>
window.onload = function () {
$(function () {
$('#testJSON').click(function () {
var url = this.href;//请求的地址
var ages = {};//请求的参数
$.post(url, ages, function (data) { //发起 POSt 请求,获取 JSON 格式的数据:data
for (let i = 0; i < data.length; i++) { //data 为数组格式
var bookName = data[i].bookName;
var bookPrice = data[i].bookPrice;
alert("book" + i + ":" + bookName + "-" + bookPrice);
}
});
})
return false;
})
}
</script>
<a href="SpringMVC/testJson" id="testJson">testJSON</a>
2.导入相关的 jar 资源包:
3.编写目标方法:
/*
SpringMVC处理JSON,
*/
@RequestMapping("/testJson")
@ResponseBody //加上该注解,@ResponseBody的作用其实是将java对象转为json格式的数据。
public List<Book> testJson() { //直接返回需要的数据即可,不一定非是集合
List<Book> list = new ArrayList<>();
list.add(new Book("水浒传", new BigDecimal("562.33")));
list.add(new Book("红楼梦", new BigDecimal("134.33")));
list.add(new Book("西游记", new BigDecimal("155.33")));
list.add(new Book("三国演义", new BigDecimal("222.33")));
return list;
}
使用HttpMessageConverter(Http消息转换器,负责转换请求和响应的消息)
简介
使用示例:
解析:当使用 @RequestBody 修饰入参时,就根据入参的类型选择 HttpMessageConverter 的实现类,当使用 @ResponseBody 时,则根据目标方法的返回值类型来选择 HttpMessageConverter 实现类。
代码测试:
/*
HttpMessageConverter使用测试
*/
@RequestMapping("/testHttpMessageConverter")
@ResponseBody //将当前目标方法 return 的数据通过 HttpOutputMessage 输出流响应到客户端
//@RequestBody 将请求的参数转化为其修饰的入参类型相同的数据类型,赋值给对应的入参
public String testHttpMessageConverter(@RequestBody String body) {
System.out.println(body);
return "current:" + new Date();
}
@ResponseEntity测试(文件下载)
简介
/*
ResponseEntity使用测试,可由于处理目标方法入参和返回值
*/
@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws Exception {
byte[] bytes;
ServletContext servletContext = session.getServletContext();
InputStream ins = servletContext.getResourceAsStream("/resource/jquery-3.5.1.js");
/*
要一次读取多个字节时,经常用到InputStream.available()方法,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取。
需要注意的是,如果这个方法用在从本地文件读取数据时,一般不会遇到问题,但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,
对方明明发来了1000个字节,但是自己的程序调用available()方法却只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。
其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,
但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次available()方法才能将数据总数全部得到。
*/
bytes = new byte[ins.available()];
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=jquery-3.5.1.js");
HttpStatus status = HttpStatus.OK;
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, status);
return responseEntity;
}
国际化
关于国际化:
1.在页面上能够根据浏览器的语言设置情况对文本(不是内容),时间,数值,进行本地化处理
2.可以在 Bean 中获取国际化资源文件 Local 对应的消息
3.可以通过超链接切换 Local ,而不再依赖浏览器的语言设置情况
解决:
1.使用 JSTL 的 fmt 标签
2.在 Bean 中注入 ResourceBundleMessageSource 的示例,使用其对应的 getMessage 方法即可
3.配置 LocalResolver 和 LocaleChangeInterceptor
文件上传
简介
1.导入相关 jar 包
2.配置上传解析器
<!--配置文件上传解析器(该解析器的 id 必须为:multipartResolver)-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--默认字符集-->
<property name="defaultEncoding" value="UTF-8"></property>
<!--最大上传大小-->
<property name="maxUploadSize" value="102400000"></property>
</bean>
3.编写目标方法
/*
文件上传
使用 @RequestParam 注解获取请求参数中的上传文件数据
使用 MultipartFile 类型的入参来接收文件
*/
@RequestMapping("/testFileUplopa")
public String testFileUplopad(@RequestParam("file") MultipartFile file) throws IOException {
System.out.println(file.getOriginalFilename());
System.out.println(file.getInputStream());
InputStream inputStream = file.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("D:\\" + file.getOriginalFilename()));
//保存上传文件到服务器
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
return SUCCESS;
}
注:
<!--在文件上传的表单中,必须加上 enctype="multipart/form-data" 否则浏览器会报 500 的错误-->
<form action="SpringMVC/testFileUplopa" method="post" enctype="multipart/form-data">
UpLoad: <input type="file" name="file"> <br>
<input type="submit" value="上传">
</form>
报错如下:
拦截器
自定义拦截器
1.自定义一个拦截器类,实现 Interceptor 接口并重写其中的方法
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
自定义第一个拦截器
*/
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("preHandle:预先处理");
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle:后期处理");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("afterCompletion:完成后");
}
}
2.在配置文件中进行配置
<!--
配置自定义的拦截器
在该标签中添加自定义拦截器的全类名路径即可
-->
<mvc:interceptors>
<bean id="firstInterceptor" class="SpringMVCTest.MyInterceptor.FirstInterceptor"></bean>
</mvc:interceptors>
拦截器的配置
1.通过拦截映射配置拦截指定请求路径和排除指定请求路径的拦截器
<!--配置一个拦截映射,拦截(或不拦截)指定路径-->
<mvc:interceptor>
<!--
<mvc:exclude-mapping path=""/>
不拦截(排除)该 path 值的请求路径
-->
<!--拦截该 path 值的请求路径-->
<mvc:mapping path="/success"/>
<!--指定一个拦截器对象-->
<bean id="secondInterceptor" class="SpringMVCTest.MyInterceptor.SecondInterceptor"></bean>
</mvc:interceptor>
注:在配置 拦截映射 的情况下,当请求路径为拦截器映射中的指定路径时,由拦截器映射指定的拦截器对象便会执行,同时,其余的拦截器也会执行,当请求路径不是拦截器映射中的指定路径时,只执行其余拦截器,该 映射拦截器 不会执行,在配置 排除型拦截映射 的情况下,拦截和执行规则反之。
2.多个拦截方法的执行顺序
执行顺序图示:
通过上图以及源码分析,对于多个拦截器,针对 preHandler() 方法,按照拦截器配置顺序的顺序执行,对于 postHandler()方法 按照拦截器配置顺序的倒序执行,对于 afterCompletion()方法,按照拦截器配置顺序的倒序执行。
跟详细的执行过程,可参考:https://blog.csdn.net/shijiujiu33/article/details/88146502
异常处理
简介
代码演示
/*
处理异常
通过 @ExceptionHandler(异常处理程序) 注解来处理异常,该注解的 value 属性中
通过一个异常类型的 class 数组来规定可以处理的异常类型
被 @ExceptionHandler 注解标注的方法体内可以执行输出异常信息
跳转异常页面等操作。
注:
方法的入参为 Exception 类型时,SpringMVC会自动将 Exception 异常对象注入该入参
被 @ExceptionHandler 注解修饰的方法中,除了 Exception 不能传入其他类型的入参
可以传入多个 Exception 的入参,其被注入异常信息是相同的
若要向页面传递异常信息,可以使用 ModelAndView 来实现
@ExceptionHandler 异常方法的优先级
当有多个异常处理的方法,且处理的异常类型具有包含关系时,会优先查找执行与异常接近的异常方法,
若没有则向上查找异常方法
*/
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView handlerArithmeticException(Exception ex) {
System.out.println("ArithmeticException-出现算术异常" + ex);
ModelAndView mv = new ModelAndView("errorView/error");
mv.addObject("ex",ex);
return mv;
}
@ExceptionHandler(value = {RuntimeException.class})
public ModelAndView handlerArithmeticException2(Exception ex) {
System.out.println("RuntimeException-出现算术异常" + ex);
ModelAndView mv = new ModelAndView("errorView/error");
mv.addObject("ex",ex);
return mv;
}
全局的异常处理
1.自定义一个异常处理类
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/*
ExceptionHandler测试(全局的异常处理)
*/
@ControllerAdvice
public class ExceptionHandlerDemo {
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView handlerArithmeticException(Exception ex) {
System.out.println("ArithmeticException-出现算术异常" + ex);
ModelAndView mv = new ModelAndView("errorView/error");
mv.addObject("ex", ex);
return mv;
}
}
注:关于 @ControllerAdvice 注解:https://www.cnblogs.com/lenve/p/10748453.html 使用该注解后,一些全局操作,例如全局异常处理,全局数据绑定等,可以在添加了该注解的类中进行执行。
对于全局异常操作,如果在当前 Handler 中找不到对应的 @ExceptionHandler 异常处理方法,则会去 @ContorllerAdvice 标记的类中去查找对应的异常处理方法。
异常处理:ResponseStatusExceptionResolver
简介
带有 @ResponseStatus 注解的异常类会被 ResponseStatusExceptionResolver 解析。可以实现自定义的一些异常,同时在页面上进行显示。具体的使用方法如下:
代码演示:
/*
自定义异常类,图书名称为空
*/
/*
@ResponseStatus 注解
属性
value:用于指定发生异常时,Http 的状态码
reson:表明发生异常的原因
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "图书名为空!")
public class BookNameNullException extends RuntimeException {
//序列号
static final long serialVersionUID = 1L;
}
/*
ResponseStatusExceptionResolver(状态响应异常解析器)测试
*/
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam(value = "bookName", required = false) String bookName) {
if (bookName == null) {
//抛出自定义的异常
throw new BookNameNullException();
}
System.out.println("图书名称合法,无异常!");
return SUCCESS;
}
响应结果:
抛出异常在没有被 ExpetionHandlerResolver 解析的情况下,ResponseStatusExceptionResolver 会解析被 @ResponseStatus 标识的异常类,根据 @ResponseStatus 的中的 响应状态码 value 和异常原因 reason 两个参数来响应给客户端( 如上图所示 )。
@ResponseStatus 也可以使用在方法上,这时,任何请求该目标方法的请求,都会响应异常给客户端。
DefaultHandlerExceptionResolver
简介
例如:出现 NoSuchRequestHandlingMethodException 异常时,经过 DefaultHandlerExceptionResolver 解析响应的页面为:
SimpleMappingExceptionResolver
简介
在配置文件中配置
<!--配置SimpleMappingExceptionResolver(简单映射异常解析器)-->
<bean id="simpleMappingExceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--配置SimpleMappingExceptionResolver的异常属性exceptionAttribute的名称-->
<property name="exceptionAttribute" value="ex"></property>
<!--配置异常类型以及跳转的视图,参数类型为 Properties的Map类型,因此可以有多个异常以及视图-->
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">errorView/error</prop>
</props>
</property>
</bean>
注:当出现的异常在该配置中进行过配置,在异常发生时,则会跳转到指定的视图,且异常对象也会保存到 request 域中。
SpringMVC运行流程
文字说明
首先,客户端发送一个请求,被 JavaWeb 应用程序的 web.xml 配置文件中的 DispatcherServlet 处理,在 SpringMVC 中,所有的请求,都通过该 Servlet 统一处理。
然后,检查对于该请求,是否在 SpringMVC 中存在对应的请求映射,也就是 @RequestMapping。
若映射不存在:则检查 SpringMVC 自己的配置文件中是否配置 < mvc:default-servlet-handler/ >
若没有配置,则控制台输出:No mapping found for HTTP request whith URI [/xx/xx] in DispatcherServlet (在DispatcherServlet中找不到HTTP请求和URI [/ xx / xx]的映射),同时,在视图层面上,显示 404 页面。
若配置了,则直接通过查找目标资源,进行访问,若没有目标资源,则给出 404 页面,但控制台没有相关信息的输出。
若映射存在,则由 HandlerMapping(处理程序映射) 获取一个 HandlerExecutionChain(处理程序执行链) 对象,然后再获取 HandlerAdapter(处理程序适配器),然后调用拦截器的 preHandler() 方法,然后调用目标方法得到 ModelAndView 对象,然后调用拦截器的 postHandler() 方法,这时,检查调用目标方法执行的过程中,是否产生异常
若有异常,此时,不会执行 postHandler() 方法,且由 HandlerExceptionResolver(处理程序异常解析器) 组件处理异常,得到新的 ModelAndView 对象
若不存在异常,则 ModelAndView 不发生变化。
由 ViewResolver(视图解析器) 解析 ModelAndView 对象,最终渲染视图,得到实际的显示在客户端上的视图,最后调用拦截器的 aftedrComplection() 方法
图片演示
源码级别简单解析
如下
注:第 5 步,适配器执行目标方法后会获得 MOdelAndView 对象。
Spring整合SpringMVC
是否需要在 web.xml 配置文件中配置启动 Spring IOC 的 ContextLoaderLinstener (上下文加载监听器)
需要,通常情况下,类似于数据源,事务,整合其他框架,都是放在 Spring 的配置文件中,而不是放在 SpringMVC 的配置文件中,实际上放入 Spring 配置文件对应 IOC 容器中的,还有 Service ,Dao 等。
不需要,都放在 SpringMVC 的配置文件中,也可以分多个Spring 的配置文件,使用 import 节点导入其他的配置文件,例如
<!--impoprt 可以通过 resource 属性导入其他的外部配置文件 --> <import resource="spring_mvc-servlet.xml"></import>
综合上述情况,也更建议将 Spring 与 SpringMVC 进行整合。
步骤:
1.添加 Spring 配置文件
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="SpringIntegrationSpringMVCTest"></context:component-scan>
<!--配置数据源,整合 其他框架,事务等-->
</beans>
2.在 web.xml 中配置启动 Spring IOC 容器的 Listener
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
问题:若 SpringMVC 和 Spring IOC 容器配置文件中,注解扫描的包有重合的部分,可能会导致有的 bean 被创建多次。
解决:
1.使 Spring IOC 容器扫描的包与 SpringMVC 扫描的包没有重合的部分。
2.使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解,由此将两个配置文件扫描的注解区别开来
配置文件如下:
SpringMVC
<!--开启组件扫描 use-default-filters:配置扫描时,是否使用默认过滤器,值为布尔类型 --> <context:component-scan base-package="SpringIntegrationSpringMVCTest" use-default-filters="false"> <!--使用context:include-filter,配置扫描指定的注解--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
Spring IOC 容器
<!--开启注解扫描--> <context:component-scan base-package="SpringIntegrationSpringMVCTest" use-default-filters="false"> <!--使用 context:exclude-filter 指定不扫描的注解--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
注:实际上,现阶段,没有遇到上述问题。
SpringMVC 与 Spring IOC 容器的关系
1.SpringMVC IOC 容器中的 bean 可以引用 Spring IOC 容器中的 bean,反之,则不行。
如图:
二者存在一种父子关系
如图:
即 WEB 容器层可以引用到业务层,而业务层则不能引用 WEB 容器层。