一 SpringMVC概述
1.1 什么是设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
设计模式使代码编写真正工程化;
设计模式是软件工程的基石脉络,如同大厦的结构一样。
设计模式就是一种模子,经过多年实践锤炼形成一套行之有效的完成某个特定任务的步骤和方式。
例如:飞天茅台的酿造过程,酿造工序,前后不能变,温差不能变,这样做就是好喝,稍微改动就变味道了。
1.2 MVC设计模式
MVC设计模式是一种通用的软件编程思想,MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图、饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。
在MVC设计模式中认为, 任何软件都可以分为三部分:
-
模型(Model)
模型是应用程序的核心部分,负责管理数据和业务逻辑。它直接与数据库交互,检索数据并处理前端的命令。
-
视图(View)
视图是用户界面的部分,负责将数据以图形界面的形式展示给用户。它仅仅展示数据,不包含业务逻辑处理。
-
控制器(Controller)
控制器作为模型和视图之间的中介,处理用户的输入,将命令传递给模型,并选择视图来显示模型的数据。
MVC 属于[架构](模式的一种,所谓架构就是如何设计一个程序的结构。MVC 将程序结构划分为三层,每一层都对外提供了可供上层调用的接口,既能维系三层之间的联系,也能保持相对的独立性。
这种将业务逻辑、数据和界面分离的代码组织形式,降低了模块间的耦合度,有利于日后的维护与扩展。
1.3 SpringMVC
springmvc是基于spring Framwork衍生出来的一个mvc框架,主要解决原有mvc架构中,控制器(Controller)的问题,常见的控制器有servlet,struts2等,控制器的核心功能是根据用户的请求调用对应业务功能,然后依据业务处理的结果,控制程序的运行流程。
而servlet实现控制器存在的问题如下:
-
接收客户端请求参数时,存在代码的冗余
String value = request.getParameter("name");
-
只能接收字符串类型的数据,其它数据类型需要手动的转换
Integer num = Integer.parseInt(request.getParameter("number"));`
-
无法接收对象类型的参数
-
调用业务对象存在耦合 (new)
-
流程跳转存在耦合(路径耦合,视图耦合)
1.4 核心组件
Spring MVC 涉及到的组件有 DispatcherServlet(前端控制器)、HandlerMapping(映射处理器)、HandlerAdapter(处理适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。下面对各个组件的功能说明如下:
-
DispatcherServlet
DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。
-
HandlerMapping
HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。
-
HandlerAdapter
HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。
-
Handler
Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。
-
View Resolver
View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)
-
View
View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。
以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面
1.5 SpringMVC执行流程
Spring MVC 框架是高度可配置的,包含多种视图技术,例如 JSP、FreeMarke和 POI。Spring MVC 框架并不关心使用的视图技术,也不会强迫开发者只使用 JSP。
SpringMVC 的执行流程如下:
-
用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
-
由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
-
DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
-
HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
-
Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
-
HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
-
DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
-
ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
-
DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
-
视图负责将结果显示到浏览器(客户端)
二 SpringMVC入门案例
需求:
(1)通过浏览器访问 http://localhost:8088/项目名称/hello
地址,在控制台输出 "hello Springmvc"
(2)将请求转向(跳转到) /WEB-INF/pages/hello.jsp
页面
2.1 搭建步骤简述
-
创建maven的Module
-
在pom.xml中引入springmvc所需jar包
-
在web.xml中配置前端控制器
-
创建并配置springmvc-config.xml
-
创建并实现HelloController类
-
创建并实现hello.jsp
-
访问测试
2.2 具体实现
1)创建maven的Module, 命名为spring-mvc01, 同时添加web框架支持。
2)在pom.xml中引入springmvc所需jar包
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies>
3)在web.xml中配置前端控制器
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 配置Springmvc前端控制器, 将所有请求交给Springmvc来处理 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置Springmvc核心配置文件的位置,默认Springmvc的配置文件是在WEB-INF目录下, 默认的名字为Springmvc-servlet.xml,如果要放在其他目录,则需要指定如下配置: --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-config.xml</param-value> </init-param> </servlet> <!-- 其中的斜杠(/)表示拦截所有请求(除JSP以外), 所有请求都要经过Springmvc前端控制器 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
4)创建并配置springmvc-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 1.配置前端控制器放行静态资源(html/css/js等,否则静态资源将无法访问) --> <mvc:default-servlet-handler/> <!-- 2.配置注解驱动,用于识别注解(比如@Controller)和json、xml自动转换等功能 --> <mvc:annotation-driven/> <!-- 3.配置需要扫描的包:Spring自动去扫描 base-package 下的类, 如果扫描到的类上有 @Controller、@Service、@Component等注解, 将会自动将类注册为bean --> <context:component-scan base-package="com.sldl.controller"/> <!-- 4.配置内部资源视图解析器 prefix:配置路径前缀 suffix:配置文件后缀 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
5)创建并实现HelloController类
package com.sldl.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("/hello") public String sayhello(){ System.out.println("hello spring mvc"); /* 跳转路径,默认使用转发,会经过视图解析器。 返回的字符串,就是jsp页面的名称 。 */ return "hello"; } }
6)创建并实现hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>springmvc...hello.jsp....</h1> </body> </html>
7)配置tomcat, 修改虚拟项目名称,项目构建=>put into output root
8)启动tomcat,浏览器访问
http://localhost:8088/spring_mvc01/hello
三 SpringMVC参数绑定
当项目中引入Springmvc框架后,所有的请求流转将由Springmvc进行控制,当客户端发送的请求中包含数据(也就是请求参数)时,那么该如何在controller层获取这些参数呢?
Springmvc会自动的将请求中包含的参数和方法的参数进行匹配,也就是说只要保证请求中的参数名称和方法中的参数名称相对应(另,参数的格式也要正确),在方法中就可以使用这些参数—即请求中的参数。
3.1 基本数据类型绑定
形参的名字和传递参数的名字保持一致,参数需要全部传递否则报500错误,为了解决不传参报错,可以给基本类型的参数设置默认值
1)添加方法
@RequestMapping("/login1") public String login1(int id,String name,double price,boolean flag){ System.out.println(id); System.out.println(name); System.out.println(price); System.out.println(flag); return "hello"; }
2)测试
http://localhost:8088/spring_mvc01/login1?id=10&name=张三&price=10.4&flag=true
3)测试结果
4)如果参数不全,会报500错误,需要使用@RequestParam(defaultValue = "xxx")设置默认值。 如果传值了,则以传的值为最终值。
@RequestMapping("/login1")
public String login1(int id, String name, @RequestParam(defaultValue = "20") double price, boolean flag){
System.out.println(id);
System.out.println(name);
System.out.println(price);
System.out.println(flag);
return "hello";
}
3.2 包装数据类型的传递
使用包装类型可以解决基本类型不传递值,出现500错误的问题但是还是要保持参数名字和形参保持一致,
1)添加方法
@RequestMapping("/login2") public String login2(Integer id,String name,Double price,boolean flag){ System.out.println(id); System.out.println(name); System.out.println(price); System.out.println(flag); return "hello"; }
2)测试
http://localhost:8088/spring_mvc01/login2?id=20&name=李四
3)测试结果
3.3 字符串类型绑定
1)添加方法
@RequestMapping("req3") public String testRequest3(String name,String address){ System.out.println(name); System.out.println(address); return "hello"; }
2)测试
http://localhost:8088/spring_mvc01/req3?name=zhangsan&address=长春
3)测试结果
3.4 数组类型绑定
1)添加方法
@RequestMapping("/login4") public String login4(String[] names){ for (String name : names) { System.out.println(name); } return "hello"; }
2)测试
http://localhost:8088/spring_mvc01/login4?names=王一&names=张二&names=李三&names=赵四
3)测试结果
3.5 javaBean类型
参数名的字段和Javabean中的属性保持一致即可。 底层调用的是javaBean的setter方法。
准备pojo类型
package com.sldl.pojo; public class User { private int id; private String name; private String gender; private double balance; public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setGender(String gender) { this.gender = gender; } public void setBalance(double balance) { this.balance = balance; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", balance=" + balance + '}'; } }
1)添加方法
@RequestMapping("/login5") public String login5(User user){ System.out.println(user); return "hello"; }
2)测试
http://localhost:8088/spring_mvc01/login5?id=1001&name=张三&gender=女&balance=200.3
3)测试结果
3.6 日期类型
Springmvc默认是以斜杠(yyyy/MM/dd)接收日期类型的参数, 因此如果日期参数不是yyyy/MM/dd 格式,就会出现400错误!
如果提交的日期就是以斜杠分隔的, Springmvc就可以接收这个日期类型的参数, 否则将无法接收。
如果提交的日期参数的格式是自定义的, 也可以修改Springmvc默认的接收格式,改为自定义的方式!!
1)添加方法
@RequestMapping("/login6") public String login6(int id,Date date,String remark){ System.out.println(id); System.out.println(date); System.out.println(remark); return "hello"; }
2)测试
http://localhost:8088/spring_mvc01/login6?id=1001&date=2024/06/12 12:20:30&remark=哈哈哈
备注: %20 其实就是空格,序列化之后就变成%20了。在url传递参数的时候,一般都会序列化一下,以保证参数的安全。
4)自定义日期格式
在Springmvc中,提供了@InitBinder注解,用于指定自定义的日期转换格式,因此,我们只需要在Controller类中添加下面的代码即可,在接受日期类型的参数时,会自动按照自定义的日期格式进行转换。
只需要在类中添加如下方法
@InitBinder public void InitBinder (ServletRequestDataBinder binder){ binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true) ); }
5)测试
http://localhost:8088/spring_mvc01/login6?id=1001&date=2024-05-31%2012:20:30&remark=哈哈哈
6)测试结果
四 SpringMVC响应数据
4.1 ModelAndView方式
1)添加方法
@Controller public class ResponseController { @RequestMapping("/resp1") public ModelAndView test1(){ ModelAndView mav = new ModelAndView(); mav.setViewName("hello"); mav.addObject("scheme","ModelAndView"); mav.addObject("content","日照香炉生紫烟"); return mav; } }
2)hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>springmvc响应数据</title> </head> <body> <br> 模式:${scheme} <br><br> 内容: ${content} </body> </html>
3)测试
4.2 Model方式
Model对象实际上是一个Map集合,例如:往model中添加一个属性
model.addAttribute(String name, Object value);
其中,addAttribute方法会将属性保存到request域中,再通过转发将属性数据带到相应的JSP中,通过${}取出并显示。
1)添加方法
@RequestMapping("/resp2") public String test2(Model model){ model.addAttribute("scheme","Model"); model.addAttribute("content","遥看瀑布挂前川"); return "hello"; }
2)测试
4.3 ModelMap方式
1)添加方法
@RequestMapping("/resp3") public String test3(ModelMap map){ map.addAttribute("scheme","ModelMap"); map.addAttribute("content","飞流直下三千尺 疑是银河落九天"); return "hello"; }
2)测试结果
五 SpringMVC的跳转
5.1 转发
在前面request对象的学习中,通过request对象可以实现请求转发(即资源的跳转)。同样的,Springmvc也提供了请求转发的方式,具体实现如下
1)hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>springmvc跳转测试</title> </head> <body> <br> 主题:${theme} <br><br> 内容:${content} </body> </html>
2)代码
@Controller public class SkipController { @RequestMapping("f1") public String test1(Model model){ model.addAttribute("theme","forward"); model.addAttribute("content","时维九月,序嘱三秋"); /* 字符串格式: forward: xxxx * forward: 转发关键字 * xxxx: 转发目的地的名称(页面) * */ return "forward:hello"; } }
3)测试结果
4)简写方式
即SpringMVC的默认方式,就是转发
@RequestMapping("f2") public String test2(Model model){ model.addAttribute("theme","forward"); model.addAttribute("content","潦水尽而寒潭清,烟光凝而暮山紫"); /* 简写方式: 直接书写页面的名字。 */ return "hello"; }
5)测试结果
6)总结
forward方式相当于:
request.getRequestDispatcher("url").forward(request,response);
转发是一次请求,一次响应;
转发后地址栏地址没有发生变化(还是访问testForward的地址);
转发前后的request和response对象也是同一个。
5.2 重定向
在前面response对象的学习中,通过response对象可以实现请求重定向(即资源的跳转)。同样的,SpringMVC也提供了请求重定向的方式,具体实现如下:
1)添加方法
@RequestMapping("re1") public String test3(){ /** * 重定向的格式: "redirect:xxxx" * redirect: 重定向的关键字 * xxxx: 重定向的新地址。 也就是浏览器收到这个新地址,然后向该地址再次发送请求 * 因此:浏览器的地址会发生变化 * * 该案例: 浏览器的最初访问地址:http://localhost:8088/项目名/re1 * 改变后的地址: http://localhost:8088/项目名/f1 * * 逻辑如下: * 1. 浏览器最初发送了re1请求, * 2. 然后走到该方法test3 * 3. 遇到返回值里的关键字redirect和新地址f1 * 4. 服务器会将新地址f1以及302状态码发送回浏览器 * 5. 浏览器收到信息后,会向新地址f1再次发送请求 * */ return "redirect:f1"; }
2)测试
http://localhost:8088/spring_mvc01/re1
3)测试结果
4)总结
redirect方式相当于:
response.sendRedirect("url");
重定向是两次或多次请求,两次或多次响应;
重定向后地址栏地址发生了变化(变为转发后的地址);
并且在重定向前后,request和response对象不是同一个。
六 SpringMVC处理JSON
6.1 数据准备
1)引入js文件
在web的根目录下创建目录statics,在目录statics下创建子目录js。将jquery-1.8.3.min.js文件放进去
2)创建regist.jsp文件
3)pojo类型
package com.sldl.pojo; public class Student { private String username; private String password; private int age; private String gender; public Student() { } public Student(String username, String password, int age, String gender) { this.username = username; this.password = password; this.age = age; this.gender = gender; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + ", gender='" + gender + '\'' + '}'; } }
6.2 JSON响应数据
6.2.1 @ResponseBody注解的应用
-
@ResponseBody的作用其实是将java对象转为json格式的数据。
-
@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。一般用于AJAX
-
注意:在使用此注解之后==不会再走==视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。
-
注意:在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。
6.2.2 方案1-JSON字符串
1)编写html代码
<h2> <button οnclick="getJson1()">服务端返回json--方案1</button> <button οnclick="getJson2()">服务端返回json--方案2</button> </h2>
2)编写后端代码
@Controller public class LoginController { @RequestMapping("toRegist") public String toRegistPage(){ return "regist"; } @RequestMapping("getJson1") @ResponseBody public Object testJson1(){ return "{\"username\":\"张三\",\"age\":21}"; } }
3)编写Ajax代码
function getJson1(){ $.ajax({ url:"getJson1", type:"post", dataType:"json", success:function (data){ console.log(data); console.log(data.username); console.log(data.age) }, error:function (){ alert("系统错误") }, async:true }) }
4)解决乱码情况
//解决乱码情况 @RequestMapping(value = "getJson1",produces ="application/json;charset=utf-8" )
6.2.3 方案2-JavaBean写法
1)后端代码编写
@RequestMapping("getJson2") @ResponseBody public User testJson2(){ User user = new User("xiaoming","123123",23,"女"); /** 如果想要将student对象转成json对象返回给浏览器,需要在方法上 * 添加@ResponseBody注解 * * 如果想要序列化到浏览器,需要添加另外一个依赖jackson-databind,否则报如下错误 * No converter found for return value of type * */ return user; }
2)编写Ajax代码
function getJson2(){ $.ajax({ url:"getJson2", type:"post", dataType:"json", success:function (data){ console.log(data); console.log(data.username); console.log(data.password); console.log(data.age); console.log(data.gender); }, error:function (){ alert("系统错误") }, async:true }) }
6.3 JSON请求数据
1)编写regist.jsp代码
<br> <h2> <button οnclick="getJson1()">服务端返回json--方案1</button> <button οnclick="getJson2()">服务端返回json--方案2</button> </h2> <h2>注册页面</h2> <form> 用户: <input type="text" id="username" name="username" placeholder="请输入用户名"> <br> 密码: <input type="password" id="pwd" name="pwd" placeholder="请输入密码"> <br> 年龄: <input type="text" id="age" name="age" placeholder="请输入年龄"> <br> 性别: <input type="text" id="gender" name="gender" placeholder="请输入性别"> <br> <input type="button" value="注册" οnclick="regist()"> </form>
2)编写ajax代码
function regist(){ var u1 = $("#username").val(); var p1 = $("#pwd").val(); var a1 = parseInt($("#age").val()); var g1 = $("#gender").val(); //组织json对象 var jsonData = {"username":u1,"pwd":p1,"age":a1,"gender":g1} console.log(jsonData) $.ajax({ url:"regist", data:jsonData, type:"post", dataType:"text", success:function (data){ alert(data); }, error:function (){ alert("系统错误") }, async:true }) }
3)编写后端代码
@RequestMapping("regist") @ResponseBody public String regist(String username,String pwd,int age, String gender){ System.out.println(username); System.out.println(pwd); System.out.println(age); System.out.println(gender); return "hello"; }
4)后端代码的另外一种写法
@RequestMapping("regist") @ResponseBody public String regist(User user){ System.out.println(user); return "hello"; }