SpringMVC
一.SpringMVC介绍
SpringMVC属于SpringFrameWork的产品,是一个实现MVC的轻量级Web框架
优点:
- 轻量级、高效
- 与Spring兼容性好,无缝结合
- 功能强大:RESTful、数据验证、格式化dengs
SpringMvc是以请求为驱动,围绕DispatcherServlet设计的,DispatcherServlet是一个中心控制器,它的作用是将不同的请求分发到不同的处理器,然后对请求作出不同的响应
二.SpringMVC执行原理

spring初始化的时候会把控制器处理器和适配器提供好,以及Controller被控制器处理器携带着,放在一个map中,key为路径,value为对象
1.用户发送请求到DispatcherServlet前端控制器
2.DispatcherServlet收到请求调用HandlerMapping处理器映射器
为什么要获取映射器?这是因为映射器不止一个,我们配置Controller有xml和注解两种方式
1.怎么查找的呢
1.进入RequestMappingHandlerMapping的afterPropertiesSet方法,从applicationContext中扫描beans,然后将bean查找判断有没有@RquestMapping注解,有的话会将类注解和方法注解合并,注册处理器方法
3.HandlerMapping根据请求url找到Handler处理器,Handler封装了控制器(我们自己写的Controller)和一个拦截器返回给DispatcherServlet
4.DisPatcherServlet获取HandlerAdapter处理器的适配器
为什么要获取适配器呢?与映射器一样,有不同的适配器,有xml和注解两种方式
5.HandlerAdapter执行Handler
7.Controller执行后返回信息给HandlerAdapter,比如ModelAndView
8.HandlerAdapter将ModelAndView返回给DispatcherServlet
9.DispatcherServlet将ModelAndView传递给ViewResolver视图解析器
10.ViewResolver解析后将具体的视图名返回给DispatcherServlet
11.DispatcherServlet对View进行视图渲染
12.将View视图响应给用户
代码演示
真实开发并不会这样操作。。。还是使用注解
1.导入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
2.配置web.xml
<!--配置DispatcherServlet前端控制器,用于接收请求、拦截请求、分发请求给处理器-->
<servlet>
<servlet-name>springmcv</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--DispatcherServlet 绑定Spring的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<!--
url-pattern / 和 /*的区别:
/ :匹配所有的请求,不匹配jsp页面
/*:匹配所有的请求,包括jsp页面
-->
<servlet-mapping>
<servlet-name>springmcv</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3.配置springmvc-servlet.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--处理映射器:根据请求url查找映射器(Handler)-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!--处理适配器:去执行控制器(Controller)-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!--视图解析器:解析视图的名字 DispatcherServlet调用并且传给视图名,根据前缀和后缀解析好视图名返回具体视图,然后DispatcherServlet调用具体的视图并且显示-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置好前缀和后缀就只用在代码写视图的名字了-->
<!--视图的前缀 jsp放在WEB-INF下用户不可以直接访问-->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!--视图的后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
<!--自己编写的Controller-->
<bean id="/hello" class="com.djn.hellomvc.controller.HelloController"></bean>
</beans>
4.编写Controller
实现Controller是一种比较老的方法,而且每个类只能处理一个功能,假如有几百个功能,需要写几百个类?
public class HelloController implements Controller {
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView modelAndView = new ModelAndView();
String result ="hello mvc!";
modelAndView.addObject("msg",result);
//会将ModelAndView传给HandlerAdpter,然后传给DispatcherServlet,然后将视图名字传给视图解析器处理
modelAndView.setViewName("hello");
return null;
}
}
问题
部署成功,访问404
- 可能是因为jar包没有导入进去,需要将jar包放进去

三.注解开发
1.导入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
2.配置web.xml
<!--配置DispatcherServlet前端控制器,用于接收请求、拦截请求、分发请求给处理器-->
<servlet>
<servlet-name>springmcv</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--DispatcherServlet 绑定Spring的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<!--
url-pattern / 和 /*的区别:
/ :匹配所有的请求,不匹配jsp页面
/*:匹配所有的请求,包括jsp页面
-->
<servlet-mapping>
<servlet-name>springmcv</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3.配置springmvc-servlet.xml
<!--配置Spring创建容器时要扫描的包 注解扫描-->
<context:component-scan base-package="com.djn.hellomvc"></context:component-scan>
<!--让SpringMVC不处理静态资源-->
<mvc:default-servlet-handler/>
<!--
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。
-->
<mvc:annotation-driven/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
4.编写Controller
@Controller必须要有,因为底层会先扫描Spring容器中的bean,然后再去找@RequestMapping注解,具体的流程可以在博客中搜SpringMVC源码分析
@Controller
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("msg","hello mvc...");
return "hello";
}
}
四.RESTFul
RESTFul是一种风格,通过不同的请求方式来实现不同的功能
优点:
- 简洁
- 高效:可以做缓存
- 安全:可以隐藏参数
@PathVariable:将参数映射到url路径上
@Controller
public class RESTFulController {
//原先 :http://locahost:8080/restful/test?a=1&b=2
//RESTFul:http://locahost:8080/restful/test/1/2
@RequestMapping(value = "/restful/test/{a}/{b}",method = RequestMethod.GET)
public String test(@PathVariable int a,@PathVariable int b, Model model){
model.addAttribute("msg","GET结果为"+(a+b));
return "hello";
}
@PostMapping(value = "/restful/test/{a}/{b}")
public String test2(@PathVariable int a,@PathVariable int b, Model model){
model.addAttribute("msg","POST结果为"+(a+b));
return "hello";
}
}
转发与重定向
使用SpringMVC转发重定向有两种方式,一种使用HttpServletRequest和HttpServletResponse,一种是return
注意:
- 默认就是使用的转发方式
- 使用重定向不能将jsp页面放在WEB-INF下,因为重定向相当于用户第二次请求,而WEB-INF下的是直接请求不到的
@Controller
public class RESTFulController {
//重定向: redirect:/jsp页面
@RequestMapping(value = "/restful/test/{a}/{b}",method = RequestMethod.GET)
public String test(@PathVariable int a,@PathVariable int b, Model model){
model.addAttribute("msg","GET结果为"+(a+b));
return "redirect:/hello.jsp";
}
}
五.接受参数
正常接受参数:
传递的参数名与接受的参数名一致,可以直接定义
//http://localhost:8080/param/test?name=djn
@GetMapping("/param/test")
public void test(String name){
System.out.println(name);
}
传递参数与接受参数名称不一致:
使用@RequstParam注解,用于匹配传递的参数
//http://localhost:8080/param/test?username=djn
@GetMapping("/param/test")
public void test(@RequestParam("username") String name){
System.out.println(name);
}
接受对象参数
传递的参数必须和对象的属性名一致,参数直接使用对象,如果不一致会为null
//http://localhost:8080/param/test2?name=djn&age=22&sex=男
@GetMapping("/param/test")
public void test(User user){
System.out.println(user);
}
@Data
public class User {
private String name;
private String age;
private String sex;
}
六.字符乱码
使用SpringMVC提供的CharacterEncodingFilter字符编码过滤器
web.xml
注意:
- /*会匹配jsp,如果是/不会解决jsp乱码的
<!--字符编码过滤器-->
<filter>
<filter-name>encoding</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>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
七.返回JSON数据
json是轻量级数据交换格式,以键值对形式来存储和表示数据,可读性高、容易解析,传输效率高
1.使用@ResponseBody注解
2.使用jackson/fastjson将对象转换为json并return
3.处理json乱码
使用jackson
1.导入jar包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
2.配置web.xml
<!--配置DispatcherServlet前端控制器,用于接收请求、拦截请求、分发请求给处理器-->
<servlet>
<servlet-name>springmcv</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--DispatcherServlet 绑定Spring的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<!--
url-pattern / 和 /*的区别:
/ :匹配所有的请求,不匹配jsp页面
/*:匹配所有的请求,包括jsp页面
-->
<servlet-mapping>
<servlet-name>springmcv</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--字符编码过滤器-->
<filter>
<filter-name>encoding</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>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.配置springmvc-servlet.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:aop="http://www.springframework.org/schema/aop" 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
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 https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置Spring创建容器时要扫描的包 注解扫描-->
<context:component-scan base-package="com.djn"></context:component-scan>
<!--让SpringMVC不处理静态资源-->
<mvc:default-servlet-handler/>
<!--
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。
-->
<mvc:annotation-driven/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
4.配置Controller
@ResponseBody表示不会被视图解析器解析
@Controller//当前类是一个控制器,可以被处理器适配器执行
public class JsonController {
@SneakyThrows
@GetMapping("/json/test")
@ResponseBody //表示不会被视图解析器解析
public String test() {
//创建jackson
ObjectMapper objectMapper = new ObjectMapper();
User user = new User("丁江楠", 22, "男");
//将对象转为json字符串
String json = objectMapper.writeValueAsString(user);
return json;
}
}
5.处理json乱码
<!--json乱码问题-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<!--解决乱码-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
使用fastjson
1.导入jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
2.省略配置。。。。
3.测试
@Test
public void testFastjson(){
User user1 = new User("丁江楠", 22, "男");
System.out.println("****对象转json*****");
String toJSONString = JSON.toJSONString(user1);
System.out.println(toJSONString);
System.out.println("******json转对象*******");
User user = JSON.parseObject(toJSONString,User.class);
System.out.println(user);
}
八.拦截器
SpringMVC中的拦截器类似于servlet中的过滤器,用于拦截请求进行预处理和后处理,和过滤器不同的是,拦截器只会拦截Controller中的方法,自动过滤掉jsp/html/js等资源
基于AOP实现的
自定义拦截器:
1.实现HandlerInterceptor接口
2.处理逻辑
3.注册拦截器
案例
一个登录案例,没有登录不能访问主页,通过拦截器实现
登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
//返回true放行
//放回false不放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("====处理前====");
//如果是登录相关的 直接放行
if (request.getRequestURI().contains("login")) {
return true;
}
//有session,说明登录过,直接放行
if (request.getSession().getAttribute("userinfo") != null) {
return true;
}
//需要转发,因为重定向访问不到WEB-INF下的资源
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
return false;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("====处理后====");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("====清理====");
}
}
配置拦截器
<!--拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--/**表示拦截所有请求-->
<mvc:mapping path="/**"/>
<!--拦截类-->
<bean class="com.djn.intercepter.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
登录Controller类
@Controller
public class TestController {
//登录处理
@RequestMapping("/user/login")
public String login(String username, String password, HttpSession session){
System.out.println("login==>"+username+","+password);
session.setAttribute("userinfo",username);
return "main";
}
//前往主页面
@RequestMapping("/gomain")
public String gomain(){
return "main";
}
//前往登录页面
@RequestMapping("/gologin")
public String gologin(){
return "login";
}
//注销处理
@RequestMapping("/goout")
public String goout(HttpSession session){
session.removeAttribute("userinfo");
return "login";
}
}
jsp页面
<!--index.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<a href="${pageContext.request.contextPath}/gologin">去登录</a>
<a href="${pageContext.request.contextPath}/gomain">去首页</a>
</body>
</html>
<!--login.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="/user/login" method="post">
<label for="name">用户名:</label><input id="name" name="username" type="text">
<label for="password">密码:</label><input id="password" name="password" type="text">
<input type="submit" value="登录">
</form>
</body>
</html>
<!--main.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主页</title>
</head>
<body>
<h1> ${sessionScope.get("userinfo")},欢迎回来</h1>
<a href="${pageContext.request.contextPath}/goout">注销</a>
</body>
</html>
九.文件上传和下载
基于SpringMVC实现文件上传和下载
前端:
1.请求方式要为post
2.将enctype设置为multipart/form-data,一旦设置了multipart/form-data,浏览器会以二进制流的形式来处理表单数据
后端
1.SpringMVC需要配置MultipartResolver解析器
2.使用CommonsMultipartFile接受文件
3.获取文件名、创建上传路径,读取文件并写入文件
3.使用file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));不需要读,直接写(更简单)
1.导入jar包
因为MultipartResolver是基于commons-fileupload实现的
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
2.配置MultipartResolver解析器
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
3.上传jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
4.上传 方式一:
@Controller
public class FileController {
//@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
//批量上传CommonsMultipartFile则为数组即可
@RequestMapping("/upload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : "+uploadFileName);
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
InputStream is = file.getInputStream(); //文件输入流
OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流
//读取写出
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
}
5.上传 方式二 更简单:
@Controller
public class FileController {
/*
* 采用file.Transto 来保存上传的文件
*/
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "redirect:/index.jsp";
}
}
6.下载
@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
//要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "map.jpg";
//1、设置response 响应头
response.reset(); //设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据
//设置响应头
response.setHeader("Content-Disposition",
"attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
File file = new File(path,fileName);
//2、 读取文件--输入流
InputStream input=new FileInputStream(file);
//3、 写出文件--输出流
OutputStream out = response.getOutputStream();
byte[] buff =new byte[1024];
int index=0;
//4、执行 写出操作
while((index= input.read(buff))!= -1){
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return null;
}

浙公网安备 33010602011771号