🏓Spring-MVC(模型-视图-控制器)
- 概念:Spring-Mvc是spring家族中的web成员,基于java的轻量级web,springmvc是服务到工作者思想的实现,中央控制器是DisatcherServlet.
目录
一、MVC思想
模型-视图-控制器(MVC)是一个众所周知的以设计界面应用程序为基础的设计思想。它主要通过分离模型、视图及控制器在应用程序中的角色将业务逻辑从界面中解耦。通常,模型负责封装应用程序数据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请求,并调用后台服务(service或者dao)来处理业务逻辑。处理后,后台业务层可能会返回了一些数据在视图层展示。控制器收集这些数据及准备模型在视图层示。MVC模式的核心思想是将业务逻辑从界面中分离出来,允许它们单独改变而不会相互影响。
常见MVC框架运行性能比较:Jsp+servlet > struts1 > spring mvc > struts2+freemarker > struts2,ognl,值栈。
二、SpringMVC 请求流程
Spring MVC框架也是一个基于请求驱动的Web框架,并且使用了前端控制器模式(是用来提供一个集
中的请求处理机制,所有的请求都将由一个单一的处理程序处理来进行设计,再根据请求映射规则分发
给相应的页面控制器(动作/处理器)进行处理。首先让我们整体看一下Spring MVC处理请求的流程:
- 首先用户发送请求,请求被SpringMvc前端控制器(DispatherServlet)捕获;
- 前端控制器(DispatherServlet)对请求URL解析获取请求URI,根据URI,调用HandlerMapping;]
- 前端控制器(DispatherServlet)获得返回的HandlerExecutionChain(包括Handler对象以及
Handler对象对应的拦截器); - DispatcherServlet 根据获得的 HandlerExecutionChain,选择一个合适的HandlerAdapter。(附
注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法); - HandlerAdapter根据请求的Handler适配并执行对应的Handler;HandlerAdapter(提取Request
中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程
中,根据配置,Spring将做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定
的响应信息。
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等数据格式化:
数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中) - Handler执行完毕,返回一个ModelAndView(即模型和视图)给HandlerAdaptor
- HandlerAdaptor适配器将执行结果ModelAndView返回给前端控制器。
- 前端控制器接收到ModelAndView后,请求对应的视图解析器。
- 视图解析器解析ModelAndView后返回对应View;
- 渲染视图并返回渲染后的视图给前端控制器。
- 最终前端控制器将渲染后的页面响应给用户或客户端
三、Spring MVC 优势
- 清晰的角色划分:前端控制器(DispatcherServlet)、请求到处理器映射(HandlerMapping)、
处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)、处理器或页面控制器
(Controller)、验证器( Validator)、命令对象(Command 请求参数绑定到的对象就叫命令对
象)、表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。 - 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要;
- 和Spring 其他框架无缝集成,是其它Web框架所不具备的;
- 可适配,通过HandlerAdapter可以支持任意的类作为处理器;
- 可定制性,HandlerMapping、ViewResolver等能够非常简单的定制;
- 功能强大的数据验证、格式化、绑定机制;
- 利用Spring提供的Mock对象能够非常简单的进行Web层单元测试;
- 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
- 强大的JSP标签库,使JSP编写更容易。
还有比如RESTful(一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条
件。它主要用于客户端和服务器交互类的软件,目前了解即可)风格的支持、简单的文件上传、约定大
于配置的契约式编程支持、基于注解的零配置支持等等。
四、环境搭建
1. 开发环境
Idea + Maven + Jdk1.8 + Jetty
2.依赖配置pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springmvc01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springmvc01 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- spring web -->
<dependency> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- spring mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!-- web servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!-- json依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<!-- ajax-->
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-util-ajax -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util-ajax</artifactId>
<version>11.0.1</version>
</dependency>
</dependencies>
<build>
<plugins> <!-- 编译环境插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
</configuration> </plugin>
<!-- jetty插件 -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.27.v20200227</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<!-- 设置端口 -->
<httpConnector>
<port>8084</port>
</httpConnector>
<!-- 设置项目路径 -->
<webAppConfig>
<contextPath>/springmvc01</contextPath>
</webAppConfig>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.web.xml配置
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 编码过滤 utf-8 -->
<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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- servlet请求分发器 -->
<servlet>
<!-- 要想启动我们的 SpringMVC 环境,目前对于 mvc 框架的配置还未进行。以上在 web.xml 中引用了-->
<!-- spring.xml 文件。-->
<!-- 5.4.1. spring.xml 配置-->
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<!-- 表示启动容器时初始化该Servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<!-- 这是拦截请求, "/"代表拦截所有请求,"*.do"拦截所有.do请求 --> <!-- <url-pattern>/</url-pattern> -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
4.spring.xml文件配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 开启自动扫描-->
<context:component-scan base-package="com.yjxxt.controller"/>
<!-- 使用默认的 Servlet 来响应静态文件 -->
<mvc:default-servlet-handler/>
<!--方案二-->
<mvc:resources mapping="/js/**/" location="/js/"/>
<!-- 开启注解驱动-->
<!-- <mvc:annotation-driven/>-->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀:在WEB-INF目录下的jsp目录下 -->
<property name="prefix" value="/"/>
<!-- 后缀:以.jsp结尾的资源 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
五、URL 地址映射
1. 单地址映射
@RequestMapping(value = "test1")
通过/test1进行访问
2. 多地址映射
@RequestMapping(value={"test","tests"})
:通过/test或者/tests两个映射都可以进行访问
3.请求方式限制
@RequestMapping(value = "test6",method = RequestMethod.GET):仅限get方式请求
后面会有案例使用!
六、参数绑定
- 基本数据类型
@Controller
public class HelloController {
//多路径访问
@RequestMapping(value = {"test","tests"})
public void test(){
System.out.println("HellController。。。。。。");
}
//无参数
@RequestMapping(value = "test1")
public ModelAndView test1(){
ModelAndView mv=new ModelAndView();
//存储
mv.addObject("hello","hellocontroller");
//转发
mv.setViewName("test");
return mv;
}
//基本数据类型
@RequestMapping(value = "test2")
public void test2(String name){
System.out.println(name);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
hello
<hr>
${hello}
<hr>
${hello}
<hr>
</body>
</html>
- 包装类型
@Controller
public class HelloController {
//包装类
@RequestMapping(value = "test3")
public ModelAndView test3(String name,Integer age){
ModelAndView mv=new ModelAndView();
mv.addObject("name",name);
mv.addObject("age",age);
mv.setViewName("test");
return mv;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
hello
<hr>
${name}--->${age}
<hr>
</body>
</html>
- 字符串类型
上面已经概述,略~~
- 数组类型
@Controller
public class HelloController {
//数组1
@RequestMapping(value = "test5")
public void test5(String[] args){
for (String arg: args) {
System.out.println(arg+"---");
}
}
//数组2
@RequestMapping(value = "test4")
public void test4(String[] args, HttpServletResponse response, HttpServletRequest request) throws IOException {
for (String str: args) {
System.out.println(str+"---");
}
request.setAttribute("arr",args);
// request.getRequestDispatcher()
response.sendRedirect("test");
}
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
hello
<hr>
${args}
<hr>
</body>
</html>
- JavaBean类型
@Controller
public class HelloController {
//method请求方式get
@RequestMapping(value = "test6",method = RequestMethod.GET)
public ModelAndView test6(User user){
ModelAndView mv=new ModelAndView("test");
mv.addObject("user",user);
return mv;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
hello
<hr>
${user}
<hr>
</body>
</html>
- List类型
@Controller
public class HelloController {
//List对象
@RequestMapping(value = "test7")
public void test7(User user){
List<String> list=user.getList();
for (String str: list) {
System.out.println(str+"----------");
}
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="test7" method="get">
<input type="text" name="list[0]" value="夏莉"/>
<input type="text" name="list[1]" value="卢克"/>
<input type="submit" value="提交">
</form>
</body>
</html>
- Set类型(增加两个类作为测试User类和People类)
//set集合
@RequestMapping(value = "test8")
public void test8(User user) {
Set<People> set=user.getPeoples();
for (People people: set) {
System.out.println(people.getPhone()+"----------");
}
set.forEach(System.out::println);
//resp.sendRedirect("test");
}
//======================User.java====================
public class User {
private Integer age;
private String name;
//set集合
private Set<People> peoples=new HashSet<>();
// map集合
private Map<String,People> map=new HashMap<>();
// list集合
private List<String> list=new ArrayList<>();
}//get、set、toString记得生成
//====================people.java====================
public class People{
private String phone;
}//记得生成get/set/toString方法
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="test8" method="get">
<input type="text" name="peoples[0].phone" value="123456"/>
<input type="text" name="peoples[1].phone" value="12345"/>
<input type="submit" value="提交">
</form>
</body>
</html>
- Map类型
@Controller
public class HelloController {
// map集合
@RequestMapping(value = "test9")
public void test9(User user,HttpServletResponse resp) throws IOException {
Map<String,People> map=user.getMap();
Set<String> keys=map.keySet();
for (String key: keys) {
System.out.println(key +"........."+map.get(key));
}
resp.sendRedirect("test");
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="test9" method="get">
<input type="text" name="map['1001'].phone" value="123"/>
<input type="text" name="map['1002'].phone" value="234"/>
<input type="submit" value="提交">
</form>
</body>
</html>
此处打印的是对象地址,未进行处理,大概就是这样
七、请求转发与重定向
1、请求转发(四种方式介绍)
①方式一:ModelAndView(“disp”);
Dispatchercontroller.java
@Controller
public class DispatcherController {
//转发1
@RequestMapping(value = "/disp01")
public ModelAndView dispatcher1(@RequestParam(defaultValue = "转发1") String str){
System.out.println(str);
ModelAndView mv=new ModelAndView("disp");
mv.addObject("str",str);
return mv;
}
}
dispatcher.jsp文件都一样,后面也直接省略.....
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${requestScope.str}
</body>
</html>
结果:
②方式二:return “disp”;(字符串+视图解析器)
DispatcherController .java
@Controller
public class DispatcherController {
//转发2
@RequestMapping(value = "/disp02")
public String dispatcher2(Model model, @RequestParam(defaultValue = "转发2") String str){
System.out.println(str);
model.addAttribute("str",str);
return "disp";
}
结果:
③方式三: return “forward:disp.jsp”;
//转发3
@RequestMapping(value = "/disp03")
public String dispatcher3(Model model, @RequestParam(defaultValue = "转发3") String str){
System.out.println(str);
model.addAttribute("str",str);
return "forward:disp.jsp";
}
结果
④方式四:mv.setViewName(“redirect:reds.jsp”);
//转发4
@RequestMapping(value = "/disp04")
public void dispatcher4(HttpServletRequest req, HttpServletResponse resp, @RequestParam(defaultValue = "转发4") String str) throws ServletException, IOException {
System.out.println(str);
req.setAttribute("str",str);
req.getRequestDispatcher("disp.jsp").forward(req,resp);
}
结果:
2、重定向(三种方式)
①方式一 :return “redirect:reds.jsp”;
RedirectController .java
@Controller
public class RedirectController {
//重定向1
@RequestMapping("/reds1")
public String reds1(Model model, HttpServletResponse resp, HttpServletRequest req, @RequestParam(defaultValue = "重定向1") String str) throws IOException {
System.out.println(str);
model.addAttribute("str",str);
//resp.sendRedirect("reds.jsp");
return "redirect:reds.jsp";
}
}
reds.xml ,后面测试一样,后面直接省略
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>Title</title>
</head>
<body>
<hr>
${param.str}
</body>
</html>
③方式二:resp.sendRedirect(“reds.jsp?str=重定向2”);
//重定向2
@RequestMapping("/reds2")
public void reds2(Model model, HttpServletResponse resp, HttpServletRequest req, @RequestParam(defaultValue = "重定向1") String str) throws IOException {
System.out.println(str);
resp.sendRedirect("reds.jsp?str=重定向2");
}
③方式三:mv.setViewName(“redirect:reds.jsp”);
//重定向3
@RequestMapping("/reds3")
public ModelAndView reds3( HttpServletResponse resp, HttpServletRequest req, @RequestParam(defaultValue = "重定向3") String str) throws IOException {
System.out.println(str);
ModelAndView mv=new ModelAndView();
mv.addObject("str",str);
mv.setViewName("redirect:reds.jsp");
return mv;
}
八、JSON数据开发
1、基本概念
①、@ResponseBody
该注解用于将 Controller 的方法返回的对象,通过适当的HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区。返回的数据不是 html 标签的页面,而是其他某种格式的数据时(如 json、xml 等)使用(通常用于ajax 请求)。
②、 @RequestBody
该注解用于读取 Request 请求的 body 部分数据,使用系统默认配置的HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上 ,再把 HttpMessageConverter 返回的对象数据绑定到controller 中方法的参数上。
2、环境配置
①、导入json依赖pom.xml
<!-- json依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
②、修改配置spring.xml
<!-- mvc 请求映射 处理器与适配器配置 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
3、@ResponseBody将响应对象的数据转换为JSON
- json:是一个轻量级数据格式转换语言,与xml相似,其格式:花括号内由多个键值对组成,并以都好分割每个键值对。
JsonController .java
@Controller
public class JsonController {
// @ResponseBody将响应对象的数据转换为JSON
@RequestMapping("/json1")
@ResponseBody
public Demo test(){
Demo demo=new Demo();
demo.setName("小明");
demo.setAge(20);
return demo;
}
}
Dmeo.java
public class Demo {
private String name;
private int age;
//请自行生成get/set/toString/以及构造器
}
测试结果
4、Ajax发送Json请求数据绑定对应Bean
JsonController .java
@RequestMapping("/getDemo")
@ResponseBody
public Demo getUser(@RequestBody Demo demo){
System.out.println(demo);
return demo;
}
Ajax.xml
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<script src="${pageContext.request.contextPath}/js/jquery-1.8.2.min.js"></script>
<script>
/*选择元素绑定事件*/
$(function(){
//预加载函数
$("#btn").click(function(){
//$.post(url,data,function(){},"json");
$.ajax({
type:"post",
url:"getDemo",
data:'{"name":"lisisi","age":"18"}',
dataType:"json",
contentType: "application/json;charset=utf-8",
success:function(msg){
alert(msg);
}
});
});
});
</script>
<html>
<head>
<title>Title</title>
</head>
<body>
<button id="btn">发送</button>
</body>
</html>
Demo.java
public class Demo {
private String name;
public Demo() {
}
public Demo(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Demo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
private Integer age;
}
结果:
- 异常情况:400:代表参数匹配,看对象带参构造器有没有写,还有数据类型。
未完待续........................