1.SpringMVC框架简介
1.1.框架简介
Spring MVC 是 Spring 提供给 Web 应用的框架设计。
Spring MVC 角色划分清晰,分工明细,并且和 Spring 框架无缝结合。作为当今业界最主流的 Web 开发框架,Spring MVC 已经成为当前javaWeb框架事实上的标准。
1.2.SpringMVC核心架构流程
springMVC核心架构的具体流程步骤如下:
-
首先用户发送请求——>DispatcherServlet(前端控制器),前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
-
DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
-
DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
-
HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
-
ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
-
View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
-
返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
1.3.前后端分离架构下的SpringMVC
上一节中,详细描述了SpringMVC的核心架构流程。 但是要注意:这是非前后端分离模式下的SpringMVC核心架构。
所以,我们会看到:在Handler处理器执行后,返回一个ModelAndView;也就是说:在非前后端分离模式下,视图层是由服务器端控制的。
那么,在前后端分离模式下,视图层要分离出去,成为一个独立工程;或者说:视图层不在由服务器端控制。 所以,在前后端分离模式下,SpringMVC的核心架构流程修改如下:
本教程中,讲解的都是在前后端分离模式下的SpringMVC
2.SpringMVC框架实例
SpringMVC框架的开发有两种方式:
-
配置文件方式
-
注解方式
本教程中,只讲解注解方式。
2.1.创建Maven工程 
注意:SpringMVC是Web工程,所以打包方式要选择 war 包。
注意:Maven创建Web工程后,需要手动添加 WEB-INF 目录与 web.xml 配置文件。
2.2.在pom.xml文件中添加依赖
<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 https://maven.apache.org/xsd/maven-
4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.neusoft</groupId>
<artifactId>smvc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<build>
<plugins>
<!-- 设置jdk版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<spring.version>5.3.20</spring.version>
</properties>
<dependencies>
<!-- 此依赖会关联引用Spring中的所有基础jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring-webmvc会依赖spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
2.3.配置SpringMVC前端控制器
在 web.xml 文件中配置 SpringMVC 前端控制器,也就是配置 DispatcherServlet 核心组件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web
app_3_1.xsd" id="WebApp_ID" version="3.1">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 也可不配置参数,默认加载 /WEB-INF/springmvc-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.4.创建Handler处理器
在 com.neusoft.springmvc.controller 包下创建 HelloController 处理器。
package com.neusoft.smvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String hello() throws Exception {
return "hello world!";
}
}
@Controller:此注解声明在类上,表示此类是一个 Handler 处理器类,并被纳入到 Spring 容器中;
@ResponseBody:此注解可以声明在类上,或者方法上;表示处理器方法直接返回数据。
@RequestMapping:此注解可以声明在类上,或者方法上;表示将一个请求url映射给处理器方法。
2.5.创建SpringMVC配置文件
在resources文件夹下创建springmvc-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:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--
此标签能够自动加载注解的处理器映射和注解的处理器适配,
而且还默认加载了很多其他方法。 比如:参数绑定到控制器参数、json转换解析器
-->
<mvc:annotation-driven />
<!-- 开启注解扫描,将包下带有@Controller注解的类纳入Spring容器中-->
<context:component-scan base-package="com.neusoft.smvc.controller" />
</beans>
2.6.测试
将工程部署到Tomcat中,启动服务器,在浏览器地址栏中写入:http://localhost:8080/smvc/hello
2.7.相关注解详解
2.7.1.@ResponseBody注解
@responseBody注解的作用是将controller的方法返回的数据写入到response对象的body区,也就是直接将数据
写入到输出流中,效果等同于使用 response.getWriter() 输出流对象向前端返回数据。需要注意的是,在使用此注解之后,响应不会再走视图处理器。
-
@responseBody 应用在处理器类上:此处理器类中的所有方法都直接返回数据。
-
@responseBody 应用在处理器类的某个方法上:此处理器类中的某个方法直接返回数据。
2.7.2.@RequestMapping注解
用于建立请求URL和处理器方法之间的对应关系。
-
@RequestMapping 应用在处理器类上: 设置请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以 / 开头。 它出现的目的是为了使我们的URL可以按照模块化管理。
@Controller
@RequestMapping("/user")
public class HelloController {
@ResponseBody
@RequestMapping("/hello")
public String hello() throws Exception {
return "hello world";
}
}
上面处理器方法请求url应该这样写:http://localhost:8080/springmvc/user/hello
-
@RequestMapping 应用在处理器类的某个方法上:请求URL的第二级访问目录。
-
@RequestMapping注解中常用属性有:
value:用于指定请求的URL。 method:用于指定请求的方式。
@ResponseBody
@RequestMapping(value="/hello",method=RequestMethod.POST)
public String hello() throws Exception {
return "hello world";
}
如果使用 get 方式访问此处理器方法时(比如:在地址栏中输入url访问),就会出现异常。
2.7.3.@GetMapping与@PostMapping
@GetMapping是一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写。
@PostMapping是一个组合注解,是@RequestMapping(method = RequestMethod.Post)的缩写。
3.处理器方法的参数与返回值
3.1.处理器方法参数
SpringMVC也是基于Spring的,所以可以直接给处理器方法注入参数,自动绑定默认支持的各种数据类型。 这些默认支持的数据类型,或者说可以注入的对象类型有:
-
HttpServletRequest对象。
-
HttpServletResponse对象。
-
HttpSession对象。(注意:ServletContext不会注入。)
注意,注入上面的ServletAPI对象需要添加servlet-api依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency
4.简单数据类型。作用:获取客户端提交的单值参数。
public String hello(String userName,String password) throws Exception {}
注意:处理器方法的参数名必须与提交参数名一致。
测试:http://localhost:8080/springmvc/user/hello?userName=zhangsan&password=123
5.数组类型。 作用:获取客户端提交的多值参数
public String hello(Integer[] aihao) throws Exception {}
注意:处理器方法的参数名必须与提交参数名一致。
测试:http://localhost:8080/springmvc/user/hello?aihao=1&aihao=2&aihao=3
6.对象类型。 作用:获取客户端提交参数。
public String hello(User user) throws Exception {}
注意:处理器方法的参数名可以任意,但参数中的属性名必须与提交参数名一致。
测试:http://localhost:8080/springmvc/user/hello?userId=1&userName=zhangsan&password=123
3.2.使用@RequestParam匹配参数
获取客户端提交参数时,处理器的参数名必须与提交参数名一致。但如果处理器的参数名与提交参数名不一致时,可以使用@RequestParam注解来匹配参数。
public String hello(@RequestParam("username") String un,
@RequestParam("password") String pw) throws Exception {}
注意:@RequestParam的value值必须与提交参数名一致。
测试:http://localhost:8080/springmvc/user/hello?userName=zhangsan&password=123
3.3.前后端分离架构应用程序
3.3.1.跨域访问问题
在开发前后端分离架构应用程序中,有一个关键问题必须要解决,这就跨域访问问题。
当一个客户端访问服务器端时,如果客户端URL中的协议、IP、端口三者之间的任一个,与服务器端URL的协议、IP、端口不同时,即为跨域访问。
AJAX默认不允许跨域访问(为了安全),如何解决呢?通常有两种解决方案:
-
服务器端设置允许跨域访问; 比如:CORS(跨域资源共享)代理方式
-
前端通过代理进行跨域访问;比如:Vue-cli自带的跨域代理方式
在服务器端添加CORS过滤器:
package com.neusoft.smvc.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter("/*")
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
//设置允许跨域
response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
//设置开启Cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, resp);
}
@Override
public void destroy() {}
}
3.3.2.前后端分离架构实例
get方式提交
前端:
<button onclick="hello()">提交</button>
<script src="https://unpkg.com/axios/dist/axios.js"></script>
<script>
let user = {
userId:1,
userName:'zhangsan',
password:'123'
};
function hello() {
axios.get('http://localhost:8080/smvc/user/hello',{params:user})
.then(response=>{
console.log(response.data);
}).catch(error=>{
console.log(error);
});
}
</script>
服务器端:
public String hello(Integer userId,String userName,String password) throws Exception {}
post方式提交
前端
<button onclick="hello()">提交</button>
<script src="https://unpkg.com/axios/dist/axios.js"></script>
<script src="https://cdn.bootcss.com/qs/6.5.1/qs.min.js"></script>
<script>
let user = {
userId:1,
userName:'zhangsan',
password:'123'
};
function hello() {
axios.post('http://localhost:8080/smvc/user/hello',Qs.stringify(user))
.then(response=>{
console.log(response.data);
}).catch(error=>{
console.log(error);
});
}
</script>
服务器端:
public String hello(User user) throws Exception {}
3.4.处理器方法返回值
SpringMVC的处理器方法可以返回 ModelAndView ,也可以直接返回数据。 在前后端分离模式中,SpringMVC的处理器方法直接返回数据。所以,返回 ModelAndView 的情况不做讨论。
-
返回 String
上面实例中,SpringMVC的处理器方法就是直接返回String。
-
返回 json
首先,在 pom.xml 文件中添加 jackson 依赖:
<!-- jackson相关依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
服务器端代码:
//返回值设置为对象或者集合
@ResponseBody
@RequestMapping("/hello")
public User hello(User user) throws Exception {
System.out.println(user);
return user;
}
/*
@ResponseBody
@RequestMapping("/hello")
public List<User> hello(User user) throws Exception {
List<User> list = new ArrayList<>();
list.add(user);
list.add(user);
list.add(user);
return list;
}
*/
前端代码:
axios.post('http://localhost:8080/smvc/user/hello',Qs.stringify(user))
.then(response=>{
console.log(response.data);
}).catch(error=>{
console.log(error);
});
注意:
springmvc-servlet.xml 配置文件中必须要有 mvc:annotation-driven 标签配置。这里配置了json转换解析器。
只要 SpringMVC 的处理器返回值设置为对象,或者集合,那么就能返回 json 数据。
4.SpringMVC文件上传
4.1.添加文件上传jar包依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
4.2.配置文件上传解析器
在springmvc-servlet.xml 配置文件中配置文件上传解析器。
<!--必须要有此id名 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--上传文件大小限制,单位:字节 -->
<property name="maxUploadSize" value="5000000"></property>
</bean>
4.3.创建上传文件处理器
package com.neusoft.smvc.controller;
import java.io.File;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@Controller
public class UploadController {
@ResponseBody
@RequestMapping("/upload")
// 必须要有MultipartFile类型参数。而且参数名必须与表单file控件名一致
public String upload(MultipartFile myFile) {
//上传图片存储目录
String path = "d:/upload";
//获取文件名并使用UUID生成新文件名
String fileName = myFile.getOriginalFilename();
String newFileName = UUID.randomUUID() + fileName.substring(fileName.lastIndexOf("."));
//在指定上传图片存储目录中创建新文件
File targetFile = new File(path, newFileName);
//如果找不到指定目录和文件,就新创建此目录和文件
if (!targetFile.exists()) {
targetFile.mkdirs();
}
//将文件写入硬盘(myFile在内存中)
try {
myFile.transferTo(targetFile);
} catch (Exception e) {
e.printStackTrace();
}
return "ok";
}
}
4.4.客户端表单提交
<form action="http://localhost:8080/smvc/upload" enctype="multipart/form-data" method="post">
<input type="file" name="myFile"><br>
<input type="submit" value="上传">
</form>
注意:
1.enctype:设置表单提交时发送服务器数据的编码方式。
默认情况,这个编码格式是application/x-www-form-urlencoded(标准键值对格式),不能用于文件
上传;只有使用了multipart/form-data,才能上传二进制数据;
2.method:必须为post方式提交。
5.SpringMVC拦截器
早期MVC框架将一些通用操作写死在核心控制器中,致使框架灵活性不足、可扩展性降低。
Spring将预处理与后处理功能放到多个拦截器中实现,拦截器可自由选择和组合,增强了灵活性,有利于系统的解
耦。SpringMVC的拦截器类似于Servlet中的过滤器Filter,用于对处理器进行预处理和后处理。
5.1.创建SpringMVC拦截器
创建 SpringMVC 拦截器,只需要实现 HandlerInterceptor 接口即可。 接口中有三个方法需要实现:
-
preHandle:该方法在 controller 执行前执行,可以实现对数据的预处理。如果方法返回 true ,则继续调用下一个资源。否则不在继续调用。
-
postHandle:该方法在处理器执行后,生成视图前执行。这里有机会修改视图层数据。
-
afterCompletion:最后执行,通常用于记录日志,释放资源,处理异常。
package com.neusoft.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class CharSetInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object
handler) throws Exception {
System.out.println("在到达处理器之前做前置工作");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object
handler,ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object
handler, Exception ex)throws Exception {}
}
5.2.配置SpringMVC拦截器
在springmvc-servlet.xml 配置文件中配置 SpringMVC 拦截器。
<!-- 可以配置多个拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.neusoft.springmvc.interceptor.CharSetInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
6.RESTful接口规范
6.1.什么是RESTful接口规范
在前后端分离架构的理念中认为:服务器端的任务就是给前端提供数据的。所以,服务器端就需要给前端提供WebAPI接口,用于前端访问。而Web API接口的制定是需要遵守规范的。
RESTful:是一种定义Web API接口的规范。具体来说:RESTful规范要求我们:
-
对url进行规范,写出符合RESTful格式的url。优雅RESTful风格的资源URL不希望带 ?或 & 等后缀
-
指定请求资源格式。
也就是说:在RESTful架构中,每个URI代表一种资源,客户端通过四个HTTP方法(四个动词),对服务器端
资源进行操作:
GET用来获取资源;
POST用来新建资源(也可以用于更新资源);
PUT用来更新资源;
DELETE用来删除资源;
6.2.使用RESTful接口规范
1.客户端提交方式:
http://localhost:8080/springmvc/hello/zhangsan/123
2.处理器接收参数方式:
@ResponseBody
@RequestMapping("/hello/{userName}/{password}")
public String hello(@PathVariable("userName") String userName,@PathVariable("password")
String password) throws Exception {
System.out.println(userName+","+password);
return "hello world!";
}