三大框架学习之 - SpringMVC基础进阶

429 阅读13分钟

- JSON

  • 在实际开发中,通常需要和别的系统交换数据,数据交换的格式通常有XML和JSON等;

  • XML:是一门扩展标记语言,他虽然也能让我们与其他系统交换数据,但是他的缺点也很明显

    • XML文件格式文件庞大, 格式复杂, 传输占用带宽
    • 服务器端和客户端都需要花费大量代码来解析XML, 不论服务器端和客户端代码变的异常复杂和不容易维护
    • 客户端不同浏览器之间解析XML的方式不一致, 需要重复编写很多代码
    • 服务器端和客户端解析XML花费资源和时间
  • XML表示一个对象和多个对象

            <!-- XML表示一个对象 -->
            <user>

                <username>张三</username>

                <age>16</age>

                <sex>true</sex>

                <dept>

                    <id>001</id>

                    <name>开发部</name>

                </dept>

            </user>
            
            
            <!-- XML表示多个对象 -->

            <users>

                <username>张三</username>

                <age>16</age>

                <sex>true</sex>

                <dept>

                    <id>001</id>

                    <name>开发部</name>

                </dept>
                

                <username>李四</username>

                <age>26</age>

                <sex>false</sex>

                <dept>

                    <id>003</id>

                    <name>测试部</name>

                </dept>
                

                <username>王五</username>

                <age>36</age>

                <sex>true</sex>

                <dept>

                    <id>002</id>

                    <name>运维部</name>

                </dept>

            </users>
复制代码
  • JSON:是一种基于JavaScript 语法开放的轻量级数据交换格式,使用js语法来描述数据对象

    • JSON作为一个轻量级的数据格式,相对于XML,文档更小,结构清晰简洁,读写效率更高
  • JSON的数据格式:

    • 一个对象:{"key1":"value1","key2":"value2"...}
    • 多个对象:[{"key1":"value1","key2":"value2"...},{"key1":"value1","key2":"value2"...},{"key1":"value1","key2":"value2"...}]

注意:JSON数据格式就是以 "key":value 形式的数据格式,KEY必须 以 "" 包裹起来,并且如果value是字符串也必须以 "" 包裹起来,如果不是字符串那么正常写就可以

  • JSON表示一个对象和多个对象
JSON表示一个对象:

{"username":"张三","age":16,"sex":true,"dept":{"id":001,"name":"开发部"}}

格式化后的样子:

{

         "username":"张三",

         "age":16,

         "sex":true,

         "dept":{

              "id":001,

              "name":"开发部"

        }

}


JSON表示多个对象:

[


{"username":"张三","age":16,"sex":true,"dept":{"id":001,"name":"开发部"}},

{"username":"李四","age":26,"sex":false,"dept":{"id":003,"name":"测试部"}},

{"username":"王五","age":36,"sex":true,"dept":{"id":002,"name":"运维部"}}


]
复制代码

- SpringMVC返回JSON

- SpringMVC返回JSON方式一:手动拼接

/**

* springMVC响应JSON数据格式方式一:
    手动拼接的方式通过HttpServletResponse返回,这种原始方式不需要引入第三方JSONjar包

* @param resop

* @throws Exception

*/

@RequestMapping("/01")

public void getJson(HttpServletResponse resop) throws Exception{

    // 告诉浏览器,以什么方式解析我的数据,并且指定编码字符集

    resop.setContentType("application/json;charset=utf-8");

    // 获取打印输出流

    PrintWriter writer = resop.getWriter();

    // 设置打印的字符串,手动拼接JSON格式字符串

    writer.write("{"name":"张三","age":18}");

}
复制代码

- SpringMVC返回JSON方式二:使用注解返回对象

  • 通过@ResponseBody注解,将java对象转为JSON格式的数据

    • 加入jackson工具包

image.png

/**

* springMVC响应JSON数据格式方式二:通过@ResponseBody注解的方式响应对象

* @return

*/

@RequestMapping("/02")

// 将返回的数据,响应成JSON格式给浏览器,但是要注意需要导入JSONjar包

@ResponseBody

public User getUser() {

    return new User("王二麻子",17L,true,new Date());

}

/**

* springMVC响应JSON数据格式方式三:通过@ResponseBody注解的方式返回多个对象

* @return

*/

@RequestMapping("/03")

// 将返回的数据,响应成JSON格式给浏览器,但是要注意需要导入JSONjar包

@ResponseBody

public List<User> getUser2() {

    return Arrays.asList(new User("王二麻子",17L,true,new Date()),

    new User("王三码子",17L,true,new Date()),

    new User("王四马子",17L,true,new Date()));

}

/**

* springMVC响应JSON数据格式方式四:通过@ResponseBody注解的方式返回Map

* @return

*/

@RequestMapping("/04")

// 将返回的数据,响应成JSON格式给浏览器,但是要注意需要导入JSONjar包

@ResponseBody

public HashMap<String, String> getUser3() {

    HashMap<String,String> hashMap = new HashMap<String,String>();

    hashMap.put("name", "李四");

    hashMap.put("age", "18");

    return hashMap;

}
复制代码

- Json中对日期格式的特殊处理

  • 在返回对象中,如果有时间字段,那么浏览器默认展示的是时间戳,所以我们需要进行格式化

  • 从后台向前台:

    • 在日期get属性字段上,添加一个格式化注解
    • import com.fasterxml.jackson.annotation.JsonFormat;
    • @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
  • 从前台到后台

    • 在日期set属性字段上或者是属性上,添加一个格式化注解
    • @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
  • 注意:

    • 出现406状态异常:缺少jar包,加入jackson的jar包即可;
    • 使用@ResponseBody注解之后,不会经过视图解析器,意思就是如果不想返回页面或者说想返回JSON格式数据就必须加上此注解;
    • 如果在ie中测试,会弹出下载文件的窗口,可以在spring-mvc.xml的mvc:annotation-driven中加入以下配置解决:
<!-- 开启spring对MVC的支持 -->

<mvc:annotation-driven>

<!-- 避免在IE浏览器中返回JSON时出现下载文件的情况 -->

<mvc:message-converters>

    <bean id= "mappingJackson2HttpMessageConverter"

    class= "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">

    <property name= "supportedMediaTypes" >

    <list>

    <value>text/html;charset=UTF-8</value>

    </list>

    </property>

    </bean>

</mvc:message-converters>

</mvc:annotation-driven>
复制代码

- SpringMVC文件上传

  • 文件上传:指的是将本地的文件复制到服务器上;
  • SpringMvc中的文件上传是对原生文件上传的封装,目的是,较少代码量,提高开发效率;

- 文件上传三要素:

  • 表单的提交的方式必须是POST请求(get请求对提交的数据)
  • 表单中必须有一个文件上传项:<input type="file" name="headImg"/>,文件上传项必须有name属性和值;
  • 表单的enctype属性的值必须是multipart/form-data

- 文件上传步骤

- 第一步:编写文件上传页面

<form action= "/upload2" method= "post" enctype= "multipart/form-data" >

username:<input name= "username" type= "text" ><br/>

头像: <input name= "headImg" type= "file" ><br/>

<!-- button按钮在form表单内部,默认就是submit提交 -->

<button>提交</button>

</form>
复制代码

- 第二步:添加文件上传jar包

- 由于SpringMVC自己没有实现文件上传,它使用的是apache.commons.fileupload

- com.springsource.org.apache.commons.fileupload-1.2.0.jar

- com.springsource.org.apache.commons.io-1.4.0.jar
复制代码

- 第三步:配置文件上传解析器

- SpringMVC使用MultipartFile来进行文件上传,所以我们首先要配置MultipartResolver,用于处理表单中的file,如果没有配置就会报如下错误:提示告诉开发者你没有配置文件上传解析器:
复制代码

image.png

  • 配置MultipartResolver:注意id="multipartResolver"的id值不能乱写
<!-- 配置文件上传解析器 -->

<!-- 注意:id不能乱写,必须叫这个,否则上传解析器不生效 -->

<bean id= "multipartResolver" class= "org.springframework.web.multipart.commons.CommonsMultipartResolver" >

<!-- 设置最大上传10MB -->

<property name= "maxUploadSize" value= "10485760" />

<!-- 配置编码字符集 -->

<property name= "defaultEncoding" value= "UTF-8" />

</bean>
复制代码

- 第四步:后台控制器处理上传

package cn.itsource._02_upload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
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 {
	
        // 简单版本的文件上传
	@RequestMapping("/upload")
	@ResponseBody
	public void upload(String username, MultipartFile headImg) throws Exception{
//		System.out.println(username);
//		System.out.println(headImg);
//		// 获取的就是文件的类型
//		String contentType = headImg.getContentType();
//		// 获取的就是文件的名称
//		String originalFilename = headImg.getOriginalFilename();
//		// 获取的就是在form表单里面的文件名称(不常用)
//		String name = headImg.getName();
//		// 获取的就是文件的大小
//		long size = headImg.getSize();
//		System.out.println("contentType: " + contentType);
//		System.out.println("originalFilename: " + originalFilename);
//		System.out.println("name: " + name);
//		System.out.println("size: " + size);
		// 拿到文件输入流
		InputStream inputStream = headImg.getInputStream();
		// 创建文件输出流
		FileOutputStream outputStream = new FileOutputStream("/Users/colin/Desktop/aaa.png");
		// 将文件复制到指定的地方
		IOUtils.copy(inputStream, outputStream);
		outputStream.close();
		inputStream.close();
	}
	
	/**
         * 完整版本的文件上传
	 * 需求:
	 * 1.上传的附件保存在webapp下面
	 * 2.文件的名称随机
	 * 3.文件的后缀根据上传的附件后缀决定
	 * @param username
	 * @param headImg
	 * @throws Exception
	 */
	@RequestMapping("/upload2")
	@ResponseBody
	public void upload2(String username, MultipartFile headImg,HttpServletRequest request) throws Exception{
		
		// 拿到附件名称
		String originalFilename = headImg.getOriginalFilename();
		// 通过工具类,拿到附件类型后缀
		String extension = FilenameUtils.getExtension(originalFilename);
		
		// 拿到上下文对象
		ServletContext servletContext = request.getServletContext();
		// 拿到上下文路径  /Users/colin/eclipse-workspace/springMVC_JSON/webapp
		String contextPath = servletContext.getRealPath("/");
		String fileName = System.currentTimeMillis() + "." + extension;
		
		// 根据路径创建文件流
		File file = new File(contextPath + "upload/" + fileName);
		
		// 判断是否有父文件路径,如果没有创建出来
		if (!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		
		// 根据指定路径创建输出流
		OutputStream OutputStream = new FileOutputStream(file);
		// 将附件转为输入流
		InputStream inputStream = headImg.getInputStream();
		// 复制文件到本地
		IOUtils.copy(inputStream,OutputStream);
		
		inputStream.close();
		OutputStream.close();
		
	}
	

}

- SpringMVC文件下载

  • 文件下载:就是将服务器(表现在浏览器中)中的资源下载(复制)到本地磁盘;

- 第一种方式:

  • 在webapp下面创建文件夹download,并在文件夹下方放入三个不同类型的文件
  • 创建一个JSP页面,写入三个超链接

C5EF2B92-4646-4E64-9538-D9391D4FE8E7.png

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<a href="/download/1.zip">这是一个附件</a><br/>
<a href="/download/2.png">这是一张图片</a><br/>
<a href="/download/3.txt">这是一个文本文档</a>

</body>
</html>
复制代码
  • 通过测试,我们可以发现,如果只使用超链接的方式,浏览器能够解析的图片、文本文档,是可以直接打开的,而压缩文件浏览器是给我们下载的。这是因为浏览器会自动识别是否能够打开文件,不能打开才进行下载,这很明显不符合我们的文件下载要求,这时候我们就要通过后台代码来处理

- 第二种方式:

  • 改变jsp页面,因为需要我们后台处理下载,所以需要将要下载的文件名称传递到后台
<%@page import="java.net.URLEncoder"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<a href="/download?fileName=1.zip">这是一个附件</a><br/>
<a href="/download?fileName=2.png">这是一张图片</a><br/>
<a href="/download?fileName=3.txt">这是一个文本文档</a><br/>

</body>
</html>
复制代码
  • 创建DownloadController类
package cn.itsource.download;

import java.io.File;
import java.io.FileInputStream;

import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class DownloadController {
	
	
	@RequestMapping("/download")
	@ResponseBody
	public void download(HttpServletResponse response,HttpServletRequest request,String fileName) throws Exception {
		
		// 设置请求头,告诉浏览器文件下载的名字,附件表示做下载或上传操作,浏览器就不会将文件的内容直接显示出来了
		// attachment:告诉浏览器,以弹框的方式打开我的附件
		response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
		
		// 通过上下文对象获取附件
		ServletContext servletContext = request.getServletContext();
		// 通过上下文对象获取download绝对路径
		String realPath = servletContext.getRealPath("/download");
		// 根据父路径和要下载的文件名称得到文件流
		File file = new File(realPath,fileName);
		// 将文件流给FileInputStream 创建输入流
		FileInputStream fileInputStream = new FileInputStream(file);
		
		// 通过response 拿到输出流
		ServletOutputStream outputStream = response.getOutputStream();
		// 下载的核心代码
		IOUtils.copy(fileInputStream, outputStream);
		
		// 关闭流
		outputStream.close();
		fileInputStream.close();
	}

}

复制代码

注意:想要浏览器以下载的形式打开我们的文件,那么设置请求头是关键

- 解决中文问题

  • 做完上面的步骤,貌似我们的下载功能已经完成了,但是各位注意,如果你的文件名称是中文,那么就会发生下面的情况
  • 改变文件名称以及文本文档下载链接的名称,3.txt为中文.txt

34FD843A-796E-4991-98BF-F584918B7CE5.png

  • 测试点击中文下载

BD5BD464-AAA9-45B8-B84F-30FEEBD0680A.png

  • 我们的文件名称因为是中文,浏览器无法解析,所以变成了上面的样子,这是主流浏览器,这时候我们再来看一下IE浏览器

AC50BC66-4851-439E-8FB9-1BD9E334556F.png

  • 可以发现,当文件名称是中文时,普通浏览器文件名称会为空或者乱码。IE浏览器会400,因为IE浏览器认为你的文件名称就是不能是中文的,这时候我们就要做一个兼容IE的操作,分别将主流浏览器和IE浏览器区别开来处理
  • JSP页面处理
<%@page import="java.net.URLEncoder"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<a href="/download?fileName=1.zip">这是一个附件</a><br/>
<a href="/download?fileName=2.png">这是一张图片</a><br/>
<!-- 给中文编码,这样传递的时候就不是中文了,在后台解码即可 -->
<a href="/download?fileName=<%=URLEncoder.encode("中文.txt","UTF-8")%>">这是一个文本文档</a><br/>

</body>
</html>
复制代码
  • 后台处理
package cn.itsource.download;

import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;

import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class DownloadController {
	
	
	@RequestMapping("/download")
	@ResponseBody
	public void download(HttpServletResponse response,HttpServletRequest request,String fileName) throws Exception {
		
		//获取请求头信息,这个属性值可以知道是那种浏览器,每个浏览器有独特的标识
		String header = request.getHeader("User-Agent");
		//编码后的名字
		String fileNameEncoder = "";
		//证明是IE浏览器
		if(header.toUpperCase().contains("MSIE") || header.toUpperCase().contains("TRIDENT")){
			//对中文进行编码
			fileNameEncoder = URLEncoder.encode(fileName,"UTF-8");
		}else{
			//主流浏览器
			fileNameEncoder = new String(fileName.getBytes("UTF-8"),"ISO-8859-1");
		}
			
		// 设置文件下载的名字  -- 附件表示做下载或上传操作,浏览器就不会将文件的内容直接显示出来了
		// attachment:告诉浏览器,以弹框的方式打开我的附件
		response.setHeader("Content-Disposition", "attachment; filename=" + fileNameEncoder);
		
		// 通过上下文对象获取附件
		ServletContext servletContext = request.getServletContext();
		// 通过上下文对象获取download绝对路径
		String realPath = servletContext.getRealPath("/download");
		// 根据父路径和要下载的文件名称得到文件流
		File file = new File(realPath,fileName);
		// 将文件流给FileInputStream 创建输入流
		FileInputStream fileInputStream = new FileInputStream(file);
		
		// 通过response 拿到输出流
		ServletOutputStream outputStream = response.getOutputStream();
		// 下载的核心代码
		IOUtils.copy(fileInputStream, outputStream);
		
		// 关闭流
		outputStream.close();
		fileInputStream.close();
	}

}

复制代码

注:Microsoft Edge和IE的最大区别就是Edge 是windows 10 之后有微软推出的浏览器,而在windows 10 之前微软系统自家浏览器都是IE;

- SpringMVC拦截器

  • 在springMVC第一天的时候,我们学习了过滤器,SpringMVC也给我们提供了一个相似功能的拦截器

- 拦截器与过滤器的区别、相同点

  • 功能都是一样的
  • 过滤器是tomcat的
  • 拦截器是SpringMVC的

- 使用步骤

  • 创建一个拦截器类,实现HandlerInterceptor接口并实现三个方法

    • preHandle:拦截器核心方法,会在所有请求进入目标方法之前执行,返回参数true/false,true放行,false拦截
    • postHandle:在执行完目标方法之后,返回页面之前执行
    • afterCompletion:在页面渲染完毕之后,展示页面之前执行
package cn.itsource.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

// HandlerInterceptor 拦截器的接口
public class MyInterceptor implements HandlerInterceptor{
	
	/**
	 * 拦截器核心方法,会在所有请求进入目标方法之前执行
	 * 返回值类型:true 放行  false 不放行
	 */
	@Override
	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
		System.out.println("请求进入拦截器了。。。。。。");
		return true;
	}
	
	/**
	 * 在执行完目标方法之后,返回页面之前执行
	 */
	@Override
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
			throws Exception {
		System.out.println("进入方法了,并且在返回页面之前执行。。。。。。");
	}

	/**
	 * 在页面渲染完毕之后,展示页面之前执行
	 */
	@Override
	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		System.out.println("页面已经渲染完毕了,并且在返回页面之前执行。。。。。。");
	}

}

复制代码
  • 在spring-mvc.xml中配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>

	<mvc:interceptor>
	<!-- /*:拦截所有请求,但是在springMVC的拦截器中如果使用此方式那么他只会拦截一级请求
	一级请求:可以拦截的:/a /b /c  不能拦截的:/a/b   /b/c  /c/v/n
	/**:拦截所有请求,但是他可以拦截多级路径 例如:/a /b /c /a/b   /b/c  /c/v/n
		 -->
		<mvc:mapping path="/**"/>
		<bean class="cn.itsource.interceptor.MyInterceptor"/>
	</mvc:interceptor>

</mvc:interceptors>
复制代码

- SpringMVC执行流程

  • SpringMVC是一个面试高频题,所以我们来大概了解一下他的执行流程,流程图如下:

image.png

  • 流程描述:控制器即处理器(Handler)

    • 1. 用户向服务器发送请求,请求会统一交给SpringMVC前端控制DispatcherServlet处理;

    • 2. DispatcherServlet通过请求HandlerMapping(处理器映射管理对象)找到该请求对应的Handler对象(包括控制器以及Handler对象对应的拦截器) 和HandlerExecutionChain对象(包含:控制器+2个拦截器);

    • 3. DispatcherServlet请求HandlerAdapter,选择一个合适的HandlerAdapter去处理Handler。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法);

    • 4. 提取request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

      • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
      • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
      • 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
      • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
    • 5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;

    • 6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet;

    • 7. ViewResolver 结合Model和View,来渲染视图(Model+View合成)

    • 8. 将渲染结果返回给客户端;

  • 简易描述

    • 1.客户端将请求统一提交到DispatcherServlet;

    • 2.DispatcherServlet会将请求交给HandlerMapping进行请求映射,匹配该请求的Handler;

    • 3.DispatcherServlet再请求HandlerAdapter调用相应的Handler处理请求,并向前端控制器返回一个ModelAndView对象;

    • 4.DispatcherServlet将ModelAndView对象交给ViewResoler视图解析器处理,返回指定的视图View;

    • 5.DispatcherServlet 对 View 进行渲染(即将模型数据填充至视图中);

    • 6.DispatcherServlet 将页面响应给用户;

    SpringMVC高级 + JSON 我们就学习完了