一、视图层解析
调用Web资源给域对象传值
- page
- request
- session
- application
业务数据的绑定是指把业务数据绑定给JSP域对象,业务数据的绑定是由ViewResolver来完成的,开发时,我们先添加业务数据,再交给ViewResolver来绑定,我们重点是学习如何添加业务数据,Spring MVC提供了下面几种方式来添加业务数据:
- Map
- Model
- ModelAndView
- @SessionAttribue
- @ModelAttribute
- Servlet的API
1.1业务数据绑定到request域对象
1) Map
Spring MVC在调用业务方法之前会先创建一个隐含对象作为业务数据的存储容器,设置业务方法的入参为Map类型,Spring MVC会把隐含对象的引用传递给入参
2) Model
Model和Map类似,业务方法通过入参来完成业务数据的绑定
3)ModelAndView
和Map,Model不同的是,ModelAndView不仅包含业务数据,同时也封装了视图信息,如果使用ModelAndView来处理业务数据,业务方法的返回值必须是ModelAndView对象
业务方法中对ModelAndView进行两个操作:
- 填充业务数据
- 绑定视图信息
第一种方式:
第二种方式:
第三种方式:
第四种方式:
第五种方式:
4) Servlet的API
Spring MVC可以在业务方法种直接获取Servlet原生Web资源,只需要在方法定义时添加HttpServletRequest输入参数就可以,在方法体种直接使用request对象
先在pom.xml导入相关依赖
<!--导入servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
5)@ModelAttribute
Spring MVC还可以通过@ModelAttribute注解的方式添加业务数据,具体使用步骤如下:
- 定义一个方法,这个方法用来填充到业务数据中的对象
- 给该方法添加@ModelAttribute注解,不是响应请求的业务方法
- @ModelAttribute注解的作用,将请求参数绑定到Model对象。被@ModelAttribute注释的方法会在Controller每个方法执行前被执行(如果在一个Controller映射到多个URL时,要谨慎使用)。
@ModelAttribute的作用是当Handler接收到一个客户端请求以后,不管调用哪一个业务方法,都会优先调用被@ModelAttribute注解修饰的方法,并且把其返回值作为业务数据,再到业务方法,此时业务方法只需要返回视图信息就可以了,不需要返回业务数据,即使返回业务数据,也会被@ModelAttribute注解修饰的方法返回的数据所覆盖
域中的对象以key-value的形式存储,此时key默认值是业务数据所对应的类的类名首字母小写以后的结果
如果getUser没有返回值,则必须手动在该方法中填充业务数据,使用Map或者Model均可。
@ModelAttribute
public void getUser(Model model){
User user=new User();
user.setId(1);
user.setName("张三");
model.addAttribute("user",user);
}
1.2业务数据绑定到Session域对象
1)使用原生的Servlet API
2)@SessionAttribute
@SessionAttribute这个注解不是给方法添加的,而是给类添加的
@SessionAttributes除了可以通过key值绑定,也可以通过业务数据的数据类型进行绑定
@Controller
@SessionAttributes(type=User.class)
public class ViewHandler{
...
}
@SessionAttributes可以同时绑定多个业务数据
@Controller
@SessionAttributes(type={User.class,Address.class})
public class ViewHandler{
...
}
或者
@Controller
@SessionAttributes(value={"user","address"})
public class ViewHandler{
...
}
二、自定义数据类型转换器
Spring MVC默认情况下可以对基本类型进行类型转换,例如可以将String转换为Integer,Double,Float等。但是Spring MVC并不能转换日期类型(java.util.Date),如果希望把字符串参数转换为日期类型,必须自定义类型转换器
1)创建DateConverter类,并且实现org.springframework.core.convert.converter.Converter接口,这样它就成为了一个自定义数据类型转换器,需要指定泛型<String,Date>,表示把String类型转为Date类型
public class DateConverter implements Converter<String, Date> {
private String pattern;
public DateConverter(String pattern) {
this.pattern = pattern;
}
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern);
try {
return simpleDateFormat.parse(s);
} catch (ParseException e | java.text.ParseException e) {
e.printStackTrace();
}
return null;
}
}
2)在springmvc.xml中配置conversionService bean,这个bean是org.springframework.context.support.ConversionServiceFactoryBean的实例化对象,同时bean中必须包含一个converters属性,在其中注册所有需要使用的自定义转换器
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.zyh.converter.DateConverter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
</bean>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
三、RESTful风格
3.1初识RESTful
RESTful是当前比较流行的一种互联网软件架构模型,通过统一的规范来完成不同终端的数据访问和交换,REST全称是Representaional State Transfer(资源表现层状态转换)
RESTful的优点:结构清晰,有统一的标准、扩展性好
Resources:
资源指的是网络中的某一个具体文件,类型不限,可以是文本、图片、音频、视频、数据流等,是网络中真实存在的一个实体
如何获取?可以通过统一资源标识符找到这个实体,URI,每一个资源都有特定的URI,通过URI可以找到一个具体的资源
————————————————
Pepresentation:
资源表现层,资源的具体表现形式,比如一段文字,可以使用TXT,JSON,HTML,XML等不同的形式来描述
————————————————
State Transfer:
状态转化是指客户端和服务端之间的数据交换,因为HTTP请求不能传输数据的状态,所有的状态都保存在服务端,如果客户端希望访问服务端的数据,就需要使其发生状态改变,同时这种状态转化是建立在表现层,完成转换就表示资源的表现形式发生了改变
————————————————
3.2RESTful的特点
1)URL传参更加简洁
传统形式URL: http://localhost:8080/findById?id=1
RESTful风格URL: http://localhost:8080/findById/1
2)完成不同终端之间的资源共享,RESTful提供了一套规范,不同终端之间只要遵守这个规范,就可以实现数据交互。
RESTful具体来说是四种表现形式,HTTP请求中四种请求类型(GET、POST、PUT、DELETE)分别表示四种常规操作,CRUD(create read update delete)
- GET用来获取资源
- POST用来创建资源
- PUT用来修改资源
- DELETE用来删除资源
两个终端要完成数据交互,基于RESTful的方式,增删改查操作分别需要使用不同的HTTP请求类型来访问。
传统的web开发中form表单只支持GET和POST请求,如何解决呢?我们可以通过添加HiddenHttpMethodFilter过滤器,可以把POST请求转为PUT或者DELETE
@Component
@RequestMapping("/rest")
public class RESTHandler {
// @RequestMapping(value = "/find",method = RequestMethod.GET)
@GetMapping("/find")
@ResponseBody
public String find(){
return "Hello";
}
@PostMapping("/save")
public void save(){
}
@PutMapping("/update")
public void update(){
}
@DeleteMapping("/delete")
public void delete(){
}
}
3.3 HiddenHttpMethodFilter的实现原理
HiddenHttpMethodFilter检测请求参数是否包含_method参数,如果包含则取出它的值,并且判断请求类型之后完成请求类型的转换,然后继续传递
实现步骤:
1.在form表单中添加隐藏域标签,name为_method,value为PUT或DELETE
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/rest/update" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="提交">
</form>
</body>
</html>
2.在web.xml中配置HiddenHttpMethodFilter
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
四、文件的上传下载
4.1文件上传
1)单文件上传
底层使用的是Apache fileupload 组件完成上传功能,Spring MVC只是对其进行了封装,简化开发。
pom.xml:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
springmvc.xml : 为了把二进制数据解析成对象
<!-- 文件的上传-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
2)JSP页面
- input的type属性设置为file
- form表单的method设置为post
- form表单的enctype设置为multipart/form-data
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/file/upload" method="post" enctype="multipart/form-data">
<input type="file" name="img"/>
<input type="submit" value="提交">
<!-- 加上/代表从根目录也就是8080开始找 -->
</form>
<img src="${src}"/>
</body>
</html>
@Component
@RequestMapping("/file")
public class FileHandler {
/**
* 文件是以二进制流传输的
* @param img
* @return
*/
@PostMapping("/upload")
public String upload(@RequestParam("img") MultipartFile img, HttpServletRequest request){
if (img.getSize()>0){
String path = request.getSession().getServletContext().getRealPath("file");
String filename = img.getOriginalFilename();
File descFile=new File(path, filename);
try {
img.transferTo(descFile);
request.setAttribute("src", "/file/"+filename);
} catch (IOException e) {
e.printStackTrace();
}
}
return "upload";
}
}
然后选择文件
提交
3)多文件上传
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>多文件上传</title>
</head>
<body>
<form action="/file/uploads" method="post" enctype="multipart/form-data">
file1:<input type="file" name="imgs"/><br>
file2:<input type="file" name="imgs"/><br>
file3:<input type="file" name="imgs"/><br>
<input type="submit" value="提交"/>
</form>
<c:forEach items="${pathList}" var="path">
<img src="${path}" width="300px">
</c:forEach>
@PostMapping("/uploads")
public String uploads(@RequestParam("imgs") MultipartFile[] imgs,HttpServletRequest request){
List<String> pathList=new ArrayList<>();
for (MultipartFile img:imgs){
if (img.getSize()>0){
String path = request.getSession().getServletContext().getRealPath("file");
String filename = img.getOriginalFilename();
File descFile=new File(path, filename);
try {
img.transferTo(descFile);
pathList.add("/file/"+filename);
} catch (IOException e) {
e.printStackTrace();
}
}
}
request.setAttribute("pathList", pathList);
return "uploads";
}
4.2文件下载
JSP页面通过超链接点击进行下载
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件下载</title>
</head>
<body>
<a href="/file/download?fileName=皮卡丘.jpg">皮卡丘.jpg</a>
<a href="/file/download?fileName=柯南.png">柯南.png</a>
<a href="/file/download?fileName=springmvc.png">springmvc.png</a>
</body>
</html>
Handler
/**
* 根据文件的名字进行下载
*/
@GetMapping("/download")
public void download(String fileName,
HttpServletRequest request,
HttpServletResponse response) {
if (fileName!=null){
String path = request.getSession().getServletContext().getRealPath("file");
File file=new File(path,fileName);
OutputStream out=null;
if (file.exists()){
//设置下载文件
response.setContentType("applicationContext/force-download");
//设置文件名
response.setHeader("Context-Disposition", "attachment;filename="+fileName);
try {
out=response.getOutputStream();
out.write(FileUtils.readFileToByteArray(file));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (out!=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
五、拦截器
5.1过滤器、监听器、拦截器的对比
Servlet:处理Reequest请求和Response响应
- 过滤器(Filter):对Request请求起到过滤作用,作用在Servlet之前,如果配置为/*可以为所有的资源(servlet、js/css静态资源等)进行过滤处理
- 监听器(Listener):实现了javax.servlet.ServletContextListener接口的服务器端组件,它随Web应用的启动而启动,只初始化一次,然后一直监视,随Web应用的停止而销毁
作用一:做初始化工作,web应用中spring容器启动ContextLoaderListener
作用二:监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等可以在某些动作前后增加处理,实现监控,比如说统计在线人数,利用HttpSessionListener等 - 拦截器(Interceptor):是Spring MVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image等的访问,只会拦截访问的控制器方法(Handler)
1:servlet、filter、listener是配置在web.xml中,interceptor是配置在表现层框架自己的配置文件中
2:在Handler业务逻辑执行之前拦截一次
3:在Handler逻辑执行完但是还没有跳转页面之前拦截一次
4:在跳转页面后拦截一次
5.2拦截器基本概念
Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义。
- 通过实现HandlerInterceptor接口
- 继承HandlerInterceptor接口的实现类(如:HandlerInterceptorAdapter)来定义。
5.3拦截器的实现
通过实现HandlerInterceptor接口
public class MyInterceptor implements HandlerInterceptor {
/**
* 在目标Handler(方法)执行前执行
* 返回true:执行Handler方法
* 返回false:阻止目标Handler方法执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("目标Handler执行前执行MyInterceptor---->preHandle方法...");
return true;
}
/**
* 在目标Handler(方法)执行后,视图生成前执行
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("目标Handler执行后,视图执行前执行MyInterceptor---->postHandle方法...");
}
/**
* 在目标方法执行后,视图生成后执行
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("目标Handler执行后,视图执行后执行MyInterceptor---->afterCompletion方法...");
}
}
拦截器配置1
<mvc:interceptors>
<!-- 拦截器配置 -->
<!--
使用bean定义一个Interceptor
直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求
-->
<bean class="com.zyh.interceptor.MyInterceptor"></bean>
</mvc:interceptors>
拦截器配置2
<!-- 拦截器配置2 -->
<mvc:interceptors>
<!--定义在mvc:interceptor下面,可以自定义需要拦截的请求
如果有多个拦截器满足拦截处理的要求,则依据配置的先后顺序来执行
-->
<mvc:interceptor>
<!--通过mvc:mapping配置需要拦截的资源。支持通配符,可以配置多个 -->
<mvc:mapping path="/**"/> <!-- /**表示拦截所有的请求-->
<!--通过mvc:exclude-mapping配置不需要拦截的资源。支持通配符,可以配置多个 -->
<mvc:exclude-mapping path="/hello/*"/> <!-- /hello/*表示放行hello路径下的请求 -->
<bean class="com.zyh.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
5.4多个拦截器的实现
Spring MVC框架支持多个拦截器的配置,从而构成拦截器链,对客户端进行多次拦截操作
过滤器配置
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zyh.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zyh.interceptor.MyInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
自定义第二个过滤器
public class MyInterceptor2 implements HandlerInterceptor {
/**
* 在目标Handler(方法)执行前执行
* 返回true:执行Handler方法
* 返回false:阻止目标Handler方法执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("2.目标Handler执行前执行MyInterceptor2---->preHandle方法...");
return true;
}
/**
* 在目标Handler(方法)执行后,视图生成前执行
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("3.目标Handler执行后,视图执行前执行MyInterceptor2---->postHandle方法...");
}
/**
* 在目标方法执行后,视图生成后执行
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("5.目标Handler执行后,视图执行后执行MyInterceptor2---->afterCompletion方法...");
}
}
Handler
@RequestMapping("/hello")
@Controller
public class HelloContro
@RequestMapping("/packageType")
@ResponseBody
public String packageType(@RequestParam(value = "id", required = true) Integer id) {
System.out.println("拦截的方法...");
return "id=" + id;
}
}