本教程源码请访问:tutorial_demo
一、MVC模型和三层架构
1.1、MVC模型
MVC的全程是Model View Controller,是模型(Model)-视图(View)-控制器(Controller)的缩写,是一种用于设计创建Web应用程序的模式 。每个部分有专门的功能:
Model(模型):指的就是我们的数据模型。一般情况下用于封装数据。
View(视图):用于展示数据,包括我们之前学习的JSP和HTML,也就是用户“看得到”的内容。
Controller(控制器):处理用户交互的部分,可以认为是我们之前学习的Servlet,用来控制页面跳转,把特定的页面展示给用户。
这是传统的开发方式,在此基础上通过迭代,出现了JavaWeb经典的三层架构。
1.2、三层架构
我们的开发架构一般都是基于两种形式,一种是C/S(客户端/服务器)架构,另一种是B/S(浏览器/服务器)架构。在JavaWeb开发中,几乎全都是基于B/S架构的开发。那么在B/S架构中,通过不断迭代,出现了经典的三层架构,包括:表现层、业务层、持久层。三层架构在实际开发中使用的非常多,接下来我们看一下每一层都负责哪些方面:
表现层:
- 也被称为Web层;
- 负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web层, web需要接收http请求,完成http响应;
- 表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示;
- 表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端;
- 表现层的设计一般都使用MVC模型,MVC是表现层的设计模型,和其他层没有关系。
业务层:
- 也被称为Service层;
- 负责业务逻辑处理,和我们开发项目的需求息息相关 ;
- web层依赖业务层,但是业务层不依赖web层;
- 业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。
持久层:
- 也被称为Dao层;
- 包括数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口;
- 业务层需要通过数据访问层将数据持久化到数据库中;
- 通俗的讲,持久层就是和数据库交互,对数据库表进行增删改查的。
二、SpringMVC概述
2.1、SpringMVC是什么
SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring FrameWork的后续产品,已经融合在 Spring里面。Spring框架提供了构建Web应用程序的全功能MVC模块。使用Spring可插入的MVC架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架。
SpringMVC已经成为目前最主流的MVC框架之一,并且随着Spring3.0的发布,全面超越Struts2,成为最优秀的MVC框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。
2.2、SpringMVC在三层架构的位置
SpringMVC框架处于表现层,负责接收请求,向浏览器响应,以及和业务层交互;
Spring框架贯穿整个三层架构,通过IOC和DI对Bean及其属性进行管理,同时进行事务控制;
MyBatis处于持久层,与数据库进行交互。
2.3、SpringMVC和Struts2比较
共同点:
- 都是表现层框架,基于MVC模型编写;
- 底层都是基于ServletAPI;
- 处理请求都要通过一个核心的控制器。
区别:
- Spring MVC的入口是Servlet,Struts2是Filter;
- Spring MVC是基于方法设计的,而Struts2是基于类,Struts2每次执行都会创建一个动作类。所以Spring MVC会稍微比Struts2快些;
- Spring MVC 使用更加简洁,同时还支持JSR303,处理ajax的请求更方便;
- Struts2的OGNL表达式使页面的开发效率相比Spring MVC更高些,但执行效率并没有比JSTL提升,尤其是Struts2的表单标签,远没有html执行效率高。
三、SpringMVC入门
3.1、入门案例
3.1.1、创建工程
在idea中从原型创建Maven工程,选择org.apache.maven.archetypes:maven-archetype-webapp
,在pom.xml中添加如下的坐标:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- JSP -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
这里注意,由于我们这个是JavaWeb项目,一定要添加Servlet和JSP的坐标。
3.1.2、在项目中创建存放源码和配置文件的目录
在src/main目录下创建java和resources两个目录,java目录用来存放Java源码,resources用来存放配置文件。这样创建的目录是普通目录,编译时不能被正确识别,此时还需要进行如下操作:
- 在java目录上右键–>Make Directory as–>Sources root;
- 在resources目录上右键–>Make Directory as–>Resources Root;
以后的教程里面,只要涉及到Maven的Java Web工程,都要这样做。
3.1.3、编写SpringMVC的配置文件
在resources目录下新建springmvc.xml。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="org.codeaction"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置spring开启注解mvc的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
3.1.4、配置核心控制器
在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>
<!-- SpringMVC的核心控制器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置Servlet的初始化参数,读取springmvc的配置文件,创建spring容器 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 配置servlet启动时加载对象 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.1.5、编写控制器类
package org.codeaction.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello() {
System.out.println("hello...");
return "success";
}
}
3.1.6、编写相关页面
index.jsp,在webapp目录下
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<body>
<h2>Hello World!</h2>
<a href="${pageContext.request.contextPath}/hello">to hello</a>
</body>
</html>
这里有一个超链接,用来访问控制器类。
在WEB-INF目录下创建pages目录,编写success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>success</title>
</head>
<body>
Hello World!
</body>
</html>
3.1.7、启动tomcat服务器测试
在idea中配置tomcat服务器,运行。
在地址栏输入http://localhost:8080/start/index.jsp
,点击弹出页面中的超链接,页面显示Hello World!
。
3.2、SpringMVC运行流程及原理分析
3.2.1、入门案例执行流程
- 当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象,就会加载springmvc.xml配置文件;
- 开启了注解扫描,那么HelloController对象就会被创建 ;
- 从index.jsp发送请求,请求会先到达核心控制器DispatcherServlet,根据配置@RequestMapping注解找到执行的具体方法;
- 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件;
- Tomcat服务器渲染页面,做出响应。
3.2.2、SpringMVC请求响应流程(掌握)
由入门案例执行流程,这里详细的说一下SpringMVC请求响应流程:
- 用户发送请求至核心控制器DispatcherServlet;
- .核心控制器DispatcherServlet收到请求调用处理器映射器HandlerMapping;
- 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet;
- DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作;
- 执行处理器Handler(Controller,也叫页面控制器);
- Handler执行完成返回ModelAndView;
- HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet;
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
- ViewReslover解析后返回具体View;
- DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中);
- DispatcherServlet响应用户。
3.3、RequestMapping注解
作用:建立请求URL和处理方法之间的对应关系;
出现位置:
- 类上,设定第一级访问目录
- 方法上,第二级访问目录
- 路径可以不编写/表示应用的根目录开始
package org.codeaction.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/hello")
public class HelloController {
//@RequestMapping("/hello")
@RequestMapping("/hello")
public String hello() {
System.out.println("hello...");
return "success";
}
}
此时页面上访问hello方法时,超链接需要这样写href="${pageContext.request.contextPaht}/hello/hello"
。
属性:
- path,指定请求路径的url;
- value,和path作用相同;
- method,指定请求方式(get,post,put,delete);
- params,指定限制请求参数的条件;
- headers,发送请求中必须包含的请求头。
四、请求参数绑定
4.1、什么是请求参数绑定
表单提交的数据都是key=value格式,SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定。
4.2、支持的数据类型及使用要求
基本类型参数:包括基本数据类型和String类型,要求我们的参数名称必须和控制器中方法的形参名称保持一致(严格区分大小写,也可以不一致,后面RequestParam注解会讲到不一样的情况)。
POJO类型参数:包括各种实体类,以及实体类中的实体类。要求表单中参数名称和 POJO 类的属性名称保持一致,并且控制器方法的参数类型是 POJO 类型。
数组和集合类型参数:包括 List 结构和 Map 结构的集合(包括数组)。这里涉及两种方式:1.要求集合类型的请求参数必须在POJO中。在表单中请求参数名称要和POJO中集合属性名称相同。给List集合中的元素赋值,使用下标。给Map集合中的元素赋值,使用键值对。2.接收的请求参数是json格式数据。需要借助一个注解实现。
4.3、请求参数绑定示例
4.3.1、新建控制器类
package org.codeaction.controller;
import org.codeaction.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ParamController {
//测试绑定基本类型参数
@RequestMapping("/testParam")
public String testParam(String username, Float money) {
System.out.println(username);
System.out.println(money);
return "success";
}
//测试绑定POJO类型参数
@RequestMapping("/testParamPOJO")
public String testParamPOJO(User user) {
System.out.println(user.getName());
System.out.println(user.getAge());
System.out.println(user.getBirthday());
System.out.println(user.getList());
System.out.println(user.getMap());
return "success";
}
}
4.3.2、新建页面
param.jsp,在webapp目录下
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<body>
<h2>Param test</h2>
<p>测试绑定基本类型参数</p>
<form action="${pageContext.request.contextPath}/testParam" method="post">
用户名:<input type="text" name="username" /><br/>
余额:<input type="text" name="money" /><br/>
<button type="submit">提交</button>
</form>
<p>测试绑定POJO类型参数</p>
<form action="${pageContext.request.contextPath}/testParamPOJO" method="post">
姓名:<input type="text" name="name" /><br/>
年龄:<input type="text" name="age" /><br/>
生日:<input type="text" name="birthday" /><br/>
用户名:<input type="text" name="account.username" /><br/>
余额:<input type="text" name="account.money" /><br/>
用户名:<input type="text" name="list[0].username" /><br/>
余额:<input type="text" name="list[0].money" /><br/>
用户名:<input type="text" name="list[1].username" /><br/>
余额:<input type="text" name="list[1].money" /><br/>
用户名:<input type="text" name="map['one'].username" /><br/>
余额:<input type="text" name="map['one'].money" /><br/>
<button type="submit">提交</button>
</form>
</body>
</html>
4.3.3、启动tomcat服务器测试
注意:输入生日的时候一定要输入yyyy/MM/dd
格式的生日,否则提交会报错。如果我想输入yyyy-MM-dd
格式的日期怎么办呢?这里需要自定义类型转换器。
4.4、自定义类型转换器
表单提交的任何数据类型全部是字符串类型,但后台数据定义成其他类型也可以封装上,说明Spring框架内部会进行数据类型转换。我们也可以自己进行数据类型转换,这就需要自定义类型转换器。
自定义类型转换器,需要实现Converter接口。
4.4.1、自定义类型转换器
package org.codeaction.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 自定义类型转换器
*/
public class StringToDateConverter implements Converter<String, Date> {
private SimpleDateFormat sft;
public StringToDateConverter() {
sft = new SimpleDateFormat("yyyy-MM-dd");
}
@Override
public Date convert(String s) {
Date date = null;
try {
date = sft.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
Converter<S, T>接口有两个泛型,S表示接受的类型,T表示目标类型。
4.4.2、配置类型转换器
在springmvc.xml中添加如下内容
<!-- 配置类型转换器工厂 -->
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器 -->
<property name="converters">
<set>
<!-- 配置自定义类型转换器 -->
<bean class="org.codeaction.converter.StringToDateConverter" />
</set>
</property>
</bean>
4.4.3、在annotation-driven标签中引用配置的类型转换服务
<!-- 配置spring开启注解mvc的支持-->
<!-- 引用自定义类型转换器 -->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"></mvc:annotation-driven>
4.4.4、启动tomcat服务器测试
输入yyyy-MM-dd
格式的日期,控制台能够打印出来。
4.5、绑定ServletAPI对象作为方法参数
只需要把ServletAPI定义为控制器方法的参数就可以使用ServletAPI。
在控制器类ParamController中添加方法如下:
//测试使用ServletAPI
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
System.out.println(request);
System.out.println(response);
System.out.println(session);
return "success";
}
4.6、请求参数乱码问题
4.6.1、post请求乱码
在web.xml添加一个Filter
<!-- 解决GET请求乱码 -->
<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>
4.6.2、get请求乱码
修改tomcat的server.xml,如下
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
修改为:
<Connector connectionTimeout="20000"
port="8080"
protocol="HTTP/1.1"
redirectPort="8443"
useBodyEncodingForURI="true" />
如果遇到ajax请求乱码,修改为:
<Connector connectionTimeout="20000"
port="8080"
protocol="HTTP/1.1"
redirectPort="8443"
URIEncoding="UTF-8" />
4.7、配置静态资源不过滤
<!--
配置静态资源不过滤
location:表示路径
mapping:表示文件
**表示该目录下的文件以及子目录文件
-->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/scripts/" mapping="/javascript/**"/>
五、常用注解
5.1、RequestParam
作用:将请求中指定名称的参数给控制器中的形参赋值。
属性:
- value:请求参数中的名称,默认属性,通过设置这个参数可以让浏览器传过来的参数名称和控制器类方法形参名称不同;
- name:和value作用相同,默认属性;
- required:请求参数中是否必须提供此参数。默认值:true。表示必须提供。如果不提供将报错。
5.1.1、新建控制器类
package org.codeaction.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class AnnotiationController {
/**
* 测试RequestParam注解,有两个请求参数
* username从浏览器传过来的名称为name
* 可以没有age参数
* @param username
* @param age
* @return
*/
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(value = "name") String username, @RequestParam(value = "age", required = false) Integer age) {
System.out.println(username);
System.out.println(age);
return "success";
}
}
5.1.2、新建页面
annotiation.jsp,在webapp目录下
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>测试RequestParam注解,有两个请求参数</p>
<a href="${pageContext.request.contextPath}/testRequestParam?name=Tom&age=10">testRequestParam</a><br/>
<p>测试RequestParam注解,有一个请求参数</p>
<a href="${pageContext.request.contextPath}/testRequestParam?name=Bob">testRequestParam</a>
</body>
</html>
这一这里请求参数的名字是name,不是username。
从这个例子我们可以发现,在参数绑定时,可以通过RequestParam注解的value属性设置参数名称,从而让参数名称和控制器类方法形参名称不同。
5.1.3、启动tomcat服务器测试
分别点击两个超链接,控制台输出如下:
Tom
10
Bob
null
5.2、RequestBody
作用:用来解析请求体(GET请求不适用),如果发送的数据是form表单中的数据,得到的是key=value&key=value…结构的数据;如果是JSON类型的数据,利用消息转换器可将其转换为Java对象。
属性:required,是否必须有请求体。默认值是:true,当取值为 true 时,get 请求方式会报错。如果取值为false, get请求得到是null。
5.2.1、form表单数据
5.2.1.1、在控制器类中添加方法
在AnnotiationController中添加如下内容
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String accountStr) {
System.out.println(accountStr);
return "success";
}
5.2.1.2、在页面中增加form表单
在annotiation.jsp中增加如下内容
<p>测试RequestBody, post方式提交form表单</p>
<form action="${pageContext.request.contextPath}/testRequestBody" method="post">
用户名:<input type="text" name="username" /><br/>
余额:<input type="text" name="money" /><br/>
<button type="submit">提交</button>
</form>
5.2.1.3、启动tomcat服务器测试
启动tomcat服务器,在表单中输入数据,提交,控制台输出如下:
username=111&money=111
5.2.2、JSON数据
5.2.2.1、增加jquery文件
在webapp目录下新建js目录,拷贝jquery文件到该目录下。
5.2.2.2、在pom.xml中添加坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
这三个坐标为JSON处理提供支持。
5.2.2.3、在控制器类中添加方法
在AnnotiationController中添加如下内容
@RequestMapping("/testRequestBody1")
public String testRequestBody1(@RequestBody Account account) {
System.out.println(account);
return "success";
}
5.2.2.4、在页面中添加如下内容
在annotiation.jsp中增加如下内容
发送JSON数据的按钮
<button id="btn" type="button">测试RequestBody,发送json数据</button>
发送Ajax请求的js代码
<script src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
<script>
$(function () {
$("#btn").click(function(){
// 发送ajax请求
$.ajax({
// 编写json格式,设置属性和值
url:"${pageContext.request.contextPath}/testRequestBody1",
contentType:"application/json;charset=UTF-8",
data:'{"username":"Tom","money":30}',
dataType:"json",
type:"post",
success:function(data){
}
});
});
});
</script>
5.2.2.5、启动tomcat服务器测试
启动tomcat服务器,点击button,控制台输出如下:
Account{username='Tom', money=30.0}
5.3、PathVariable
学习PathVariable注解之前,我们要先学习一下REST风格的URL。
5.3.1、什么是REST风格的URL
简单说就是:用URL表示要操作的资源,用不同的HTTP请求(GET、POST、PUT、DELETE)描述对资源的操作,通过HTTP的状态码来判断此次对资源操作的结果。
REST风格的URL是对请求的一次解耦,提高了URL的重用性。
- GET:用来获取资源;
- PUT:用来更新资源;
- DELETE:用来删除资源;
- POST:用来新增资源;
REST风格的URL举例:
- GET请求,/account/1,得到id=1的account;
- PUT请求,/account/1,修改id=1的account;
- DELETE请求,/account/1,删除id=1的account;
- POST请求,/account/1,新增account。
由于我们的form表单只有GET和POST两种方式,如何发送DELETE和PUT请求,关于这点,需要在SpringMVC中进行配置,这里就不展开了。
5.3.2、关于PathVariable
作用:用于绑定url中的占位符。例如,请求url中/account/{id},这个{id}就是url占位符。
属性:
- value:用于指定URL中占位符名称;
- name:作用和value相同,两个可以互换;
- required:是否必须提供占位符。
5.3.2.1、在控制器类中添加方法
在AnnotiationController中添加如下内容
@RequestMapping(value = "/testPathVariable/account/{id}", method = RequestMethod.GET)
public String testPathVariable(@PathVariable("id") Integer id) {
System.out.println(id);
return "success";
}
5.3.2.2、在页面中添加如下内容
在annotiation.jsp中增加如下内容
<a href="${pageContext.request.contextPath}/testPathVariable/account/1">测试PathVariable</a>
5.3.2.3、启动tomcat服务器测试
运行tomcat服务器,点击超链接,控制台输出如下:
1