1. 访问静态资源:解决方案一
步骤
-
创建静态页面,目前可以正常访问
-
将web.xml中DispatcherServlet的url-pattern改成/,重启服务器tomcat
-
再次访问后台控制器的方法,去掉后缀名.do,可以正确访问
-
但所有的静态资源都访问不了
原因:也被DispatcherServlet拦截,而这个Servlet不能处理静态资源。在tomcat中专门有一个默认的Servlet用来处理静态资源。而我们配置的DispatcherServlet覆盖了默认的Servlet,导致所有的静态资源不能访问。
访问地址的区别
区别项 | *.do | / |
---|---|---|
说明 | 拦截所有后缀名是.do的资源 | 拦截所有的资源,包括静态资源,但不包含.jsp文件 |
访问方式 | http://localhost:8080/save.do | http://localhost:8080/save |
是否支持RESTful | 不支持 | 支持 |
分析
查看tomcat/conf/web.xml 查看113行和429行
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DefaultServlet
-
Tomcat中提供了一个servlet叫做DefaultServlet,他的全名(servlet-class)为org.apache.catalina.servlets.DefaultServlet。
-
load-on-startup为1是说明当应用启动时就在加载该servlet,默认的情况下是用户第一次访问该servlet方法时才会实例化并加载servlet。
-
DefaultServlet的servlet-mapping配置的为/,可以处理所有的请求,一般只有defaultServlet会配置为/,如果自定义的Servlet也配置为/,那么将会覆盖defaultServlet的配置。
web.xml中的解决方案
-
思路:在自己的web.xml中,让指定扩展名的静态资源找default名称对应的servlet即可。
-
方案:所有html、js、css、jpg等扩展名都由default的Servlet进行处理,配置servlet-mapping即可
<!-- 解决方案1:将所有的静态资源交给默认的default去处理 -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>*.js</url-pattern>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
2. 访问静态资源:解决方案二
技术点
<mvc:resources/>
由Spring MVC框架自己处理静态资源,传统Web容器的静态资源只能放在Web容器的根路径下, 而<mvc:resources/>
允许静态资源放在任何地方,如:WEB-INF目录下、类路径下等。
属性
location:指定静态资源的位置,可以使用诸如"classpath:"等的资源前缀指定资源位置。 可以同时指定多个路径使用逗号分隔,路径要以/结束
mapping:映射地址(即访问地址),以/**结尾,它表示映射目录下所有的URL,包括子孙路径的资源
步骤
操作一
-
映射webapp的根目录为根目录,映射地址为/**
-
访问根目录和它子目录下的静态资源
注:/处出现红色不用理会
代码
<mvc:resources mapping="/**" location="/"/>
操作二
-
映射webapp的根目录和类路径下的static为根目录,映射地址为/**
-
访问resources/static/下的静态资源
代码
<!--
访问静态资源解决方法2:
配置静态资源的访问地址映射,原理:由SpringMVC来处理静态资源
mapping:指定访问地址映射,设置成/**,表示子孙目录都可以映射
location:真实的地址,/ 表示webapp目录。(报红忽略)
可以指定模块下任意的目录进行映射,如:classpath:static/
可以指定多个目录,使用逗号分隔
-->
<mvc:resources mapping="/**" location="/,classpath:static/"/>
3. 访问静态资源:解决方案三【推荐】
解决方法
将静态资源交给默认Servlet处理
mvc:default-servlet-handler
在springMVC.xml中配置<mvc:default-servlet-handler />后,会在Spring MVC容器中定义一个
org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
springMVC.xml
<mvc:default-servlet-handler/>
4. 方法的返回值
控制器方法三种常见的返回值
-
void:没有返回值,进行转发,重定向和打印数据
-
String:进行转发,重定向和显示数据
-
ModelAndView:同步操作在页面上显示数据
1.方法的返回值:void
代码
/**
* 方法的返回值是void的情况:
* 1. 如果没有返回值,跳转到:/pages/account/update.jsp (前缀+访问地址+后缀) 就是它跳转的页面
* 2. 要转发:使用request对象的转发器
* 3. 重定向:使用response对象
* 4. 直接在方法中使用打印流输出
*/
@RequestMapping("/update")
public void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("更新");
//request.getRequestDispatcher("/pages/success.jsp").forward(request, response);
//response.sendRedirect(request.getContextPath() + "/pages/success.jsp");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("你好,我是新的页面");
}
2.方法的返回值:String类型
代码
/**
* 返回值是String类型:
* 转发 forward:url (真实地址)
* 重定向 redirect:url
*
*/
@RequestMapping("/delete")
public String delete() {
System.out.println("删除记录");
//return "forward:/pages/success.jsp";
return "redirect:/pages/success.jsp";
}
3. 方法的返回值:ModelAndView类型
代码
/**
* 返回值是:ModelAndView对象
*/
@RequestMapping("/list")
public ModelAndView list(ModelAndView mv) {
//设置视图
mv.setViewName("success");
//设置模型,支持链式写法,同时设置多个 (放在请求域中)
mv.addObject("name", "孙悟空").addObject("sex", "男");
//返回ModelAndViw对象
return mv;
}
@RequestMapping("/find")
public ModelAndView find() {
//设置视图,键,值
return new ModelAndView("success", "name", "猪八戒").addObject("sex","女");
}
5.方法返回对象:JSON数据的交互
1.pom.xml导入jackson包
<!--json支持包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!--lombok可选-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
2.Account实现类,生成get和set方法,生成toString方法
package com.it.entity;
import lombok.Data;
@Data
public class Account {
private int id;
private String name;
private double money;
}
3.编写html, 发送ajax异步请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<!--导入jq-->
<script src="js/jquery-3.3.1.js"></script>
</head>
<body>
<h2>提交JSON数据格式</h2>
<button id="btn" type="button">提交按钮</button>
<script type="text/javascript">
$("#btn").click(function () {
$.ajax({
url: "account/change",
method: "post",
data: '{"id": 1,"name": "张三", "money": 50}',
contentType: "application/json;charset=utf-8", //必须指定,否则数据格式错误
success: function (account) { //返回account对象
alert(account.id + "," + account.name + "," + account.money);
}
});
});
</script>
</body>
</html>
4.编写后台控制器类
/**
* 异步操作:
* 1. 获取提交的JSON格式数据,转成实体类 @RequestBody 要求提交的必须是JSON格式的字符串
* 2. 修改实体类的属性
* 3. 返回JSON字符串
* @ResponseBody 放在方法上面或返回值的前面,将返回的对象转成JSON字符串
*/
@RequestMapping("/change")
@ResponseBody
public Account change(@RequestBody Account account) {
System.out.println("用户提交的对象属性值:" + account);
//修改属性
account.setId(100);
account.setName("嫦娥");
account.setMoney(15000d);
return account;
}
注解 | 作用 |
---|---|
@RequestBody | 位置:放在方法的参数前面 作用:获取请求体中JSON数据,封装成实体类对象 |
@ResponseBody | 位置:放在方法上面或方法返回值的前面 作用:将方法的返回对象转成JSON格式的字符串 |
6.RESTful风格的URL介绍
概述
REST(英文:Representational State Transfer表现层状态转换,简称REST)RESTful是一种网络应用程序的设计风格和开发方式,基于HTTP协议。
REST 指的是一组架构约束条件和原则,满足这些约束条件和原则的应用程序就是符合RESTful。
两个特点
既然它描述的是个设计风格,那什么样的接口规范的才算符合它的风格呢?我们先得来看RESTful其中的两个特点:
-
每一个URI代表1种资源;
-
客户端使用
GET、POST、PUT、DELETE
四个表示操作方式的动词对服务端资源进行操作-
GET用来获取资源
-
POST用来新建资源
-
PUT用来更新资源
-
DELETE用来删除资源
-
RESTful的实现
-
访问地址是一样的
-
参数是通过/分隔的,如果有多个参数,使用多个/
请求地址(URI | 请求含义 | 请求方法 |
---|---|---|
/user | 新增用户 | POST |
/user/1 | 删除编号1的用户 | DELETE |
/user/1/Jack/18 | 修改编号1的用户 | PUT |
/user/1 | 获取编号1的用户 | GET |
RESTful风格的GET和POST请求代码
页面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RESTful操作</title>
</head>
<body>
<h2>GET请求</h2>
<form action="user/1/张三" method="get">
<input type="submit" value="查询">
</form>
<hr/>
<h2>POST请求</h2>
<form action="user/2" method="post">
<input type="submit" value="添加">
</form>
<hr/>
</body>
</html>
控制器
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* 处理增删改查的操作
*/
@Controller
@RequestMapping("/user")
public class RestfulController {
/**
* 限制使用是GET请求
* 如果要让参数有值,必须使用
* @PathVariable 作用:从路径上获取变量的值
* name /value:指定路径上变量的名字,如果同名可以省略
* required:属性是否必须
@RequestMapping(path = "/{id}/{name}", method = RequestMethod.GET)
*/
@GetMapping("/{id}/{name}") //限制使用GET方法的 @RequestMapping
public String find(@PathVariable Integer id, @PathVariable(name = "name") String username) {
System.out.println("查找");
System.out.println("id:" + id + ", name:" + username);
return "success";
}
@PostMapping("/{id}") //@RequestMapping(method = RequestMethod.POST)
public String save(@PathVariable Integer id) {
System.out.println("添加");
System.out.println("id: " + id);
return "success";
}
}
RESTful风格的PUT和DELETE请求
说明
由于浏览器 form 表单只支持 GET 与 POST 请求,而 DELETE、 PUT 等 method 并不支持, Spring3.0 添加了一个过滤器,可以将浏览器请求改为指定的请求方式,发送给我们的控制器方法,使得支持 GET、 POST、 PUT与 DELETE 请求。
使用PUT和DELETE请求步骤
-
在 web.xml 中配置HiddenHttpMethodFilter过滤器,过滤所有资源
-
表单的请求方式必须使用 post 请求。
-
要求提供_method 请求参数,该参数的取值就是我们需要的请求方式。
注意事项
-
PUT和DELETE的服务器端不能进行转发或重定向,否则会出现405错误。
-
如果访问地址的参数与方法中参数个数不匹配,会出现405错误。
-
如果方法体中没有输出任何内容,会出现404错误。
步骤
基本操作
-
配置HiddenHttpMethodFilter 过滤器,过滤所有的资源 注意:不要导错类,有两个同名的过滤器
-
编写HTML页面
-
添加2个表单,都使用post提交方式
-
put请求:表单添加隐藏域:_method="PUT"
-
delete请求:表单添加隐藏域:_method="DELETE"
-
put或delete不区分大小写
-
-
编写控制器, 处理同一个地址的不同提交方式的请求。
action后面使用参数
-
修改表单action="user/参数"
-
修改控制器中的方法,在方法的访问地址后面使用参数:/{变量名}
-
在方法的参数上使用@PathVariable注解
-
方法返回void,在方法上添加@ResponseBody注解,返回一个字符串直接打印在浏览器上
代码
web.xml
<!-- 支持隐藏的方法,让表单支持put和delete请求 -->
<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>
html
<h2>PUT请求</h2>
<form action="user" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="更新">
</form>
<hr/>
<h2>DELETE请求</h2>
<form action="user" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="删除">
</form>
控制器类
package com.it.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* 处理增删改查的操作
*/
@Controller
@RequestMapping("/user")
public class RestfulController {
/**
* 限制使用是GET请求
* 如果要让参数有值,必须使用
* @PathVariable 作用:从路径上获取变量的值
* name /value:指定路径上变量的名字,如果同名可以省略
* required:属性是否必须
@RequestMapping(path = "/{id}/{name}", method = RequestMethod.GET)
*/
@GetMapping("/{id}/{name}") //限制使用GET方法的 @RequestMapping
public String find(@PathVariable Integer id, @PathVariable(name = "name") String username) {
System.out.println("查找");
System.out.println("id:" + id + ", name:" + username);
return "success";
}
@PostMapping("/{id}") //@RequestMapping(method = RequestMethod.POST)
public String save(@PathVariable Integer id) {
System.out.println("添加");
System.out.println("id: " + id);
return "success";
}
/**
* 如果使用put或delete请求,不能使用转发或重定向,否则会出现405的错误。
* 只能在页面上直接输出结果,添加一个@ResponseBody注解,直接获取响应体的结果
*/
@PutMapping //@RequestMapping(method = RequestMethod.PUT)
@ResponseBody
public String update() {
System.out.println("更新");
return "Update Success";
}
/*
删除操作
*/
@DeleteMapping //@RequestMapping(method = RequestMethod.DELETE)
@ResponseBody
public String delete() {
System.out.println("删除");
return "Delete Success";
}
}
7.SpringMVC实现文件上传
使用SpringMVC实现文件上传,本质就是用到了Common-FileUpload组件实现。
步骤
-
在pom.xml添加commons-fileupload依赖
-
编写文件上传页面fileupload.html
注:HTML网页主文件名不能与控制器/upload的访问地址相同,否则会出现500错误。因为默认的Servlet在SpringMVC之后处理。所以要么上传页面使用jsp,要么网页的主文件名不要与控制器的访问地址相同。
-
创建新的控制器方法
-
有2个参数:HttpServletRequest,MultipartFile 也可以使用更多参数,将普通文本框的参数加进来
-
通过上下文对象,获取文件上传的真实目录
-
调用multipartFile中的getOriginalFilename()获取方法获取原始的文件名
-
调用multipartFile中的transferTo()将文件上传到指定的目录
-
-
配置springMVC.xml中的CommonsMultipartResolver对象
-
指定id为multipartResolver,这个id是固定的,不能随意修改。 因为SpringMVC封装文件上传的数据,找的是容器中指定的bean,否则会导致方法中MultipartFile对象为空。
-
可以指定上传文件大小的属性,比如:maxUploadSize文件最大不能超过10485760 (10M)
-
如果其它文本框或文件名有汉字出现乱码,需要指定属性:defaultEncoding为utf-8
-
代码
-
添加依赖
<!--添加Apache文件上传支持包-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2.上传的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传文件</title>
</head>
<body>
<h2>上传文件</h2>
<!--
上传文件三要素:
1. 必须使用post方法提交
2. 必须指定:enctype="multipart/form-data"
3. 必须使用文件域,指定name属性
-->
<form action="upload" method="post" enctype="multipart/form-data">
用户名:
<input type="text" name="username" placeholder="请输入用户名"> <br/>
照片:
<input type="file" name="photo" accept="image/*"> <br/>
<input type="submit" value="上传文件">
</form>
</body>
</html>
3.编写控制器方法:参数是:请求对象,MultipartFile photo 注:参数名photo必须与表单中文件域的name相同
package com.it.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
/**
上传文件的控制器
*/
@Controller
public class UploadController {
/**
* 上传文件方法
* @param photo 参数名必须与表单中文件域的名字要一致
* @return
*/
@RequestMapping("/upload")
public String upload(HttpServletRequest request, MultipartFile photo, String username) throws IOException {
System.out.println("用户名:" + username);
//1. 获取上传的文件名
String filename = photo.getOriginalFilename();
//获取服务器真实的地址: 请求对象 -> 上下文对象 -> 方法获取真实地址
String realPath = request.getServletContext().getRealPath("/img/");
System.out.println("服务器真实地址:" + realPath);
//2. 写入文件,参数是一个文件对象。
photo.transferTo(new File(realPath, filename));
System.out.println("上传文件成功");
return "success";
}
}
4. 配置文件上传解析器
1. id必须叫multipartResolver
2. 指定最大上传文件大小
3. 如果其它有汉字,可以指定汉字的编码
<!-- 配置上传文件处理器,id必须叫multipartResolver名字 -->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
<!--上传文件大小的属性 -->
<property name="maxUploadSize" value="10485760"/>
<!-- 表单上传中如果有汉字乱码,指定它的编码 -->
<property name="defaultEncoding" value="utf-8"/>
</bean>
8. SpringMVC中异常处理机制
处理流程
步骤
-
创建异常处理类,实现HandlerExceptionResolver接口
-
重写resolveException方法,四个参数,其中第三个参数是HandlerMethod对象
-
创建ModelAndView对象
-
向请求域中添加错误信息
-
设置显示的视图,注:错误页面放在了pages目录下
-
返回ModelAndView对象
-
输出异常的方法名和异常的处理器类
-
-
必须将这个异常处理类加入到Spring IoC容器中,无需指定id
代码
控制器类
-
在方法中模拟异常
/**
* 更新
*/
@RequestMapping("/update")
public String update(Integer age) {
System.out.println("用户年龄:" + age);
if (age <=18) {
throw new RuntimeException("未成年,请绕行");
}
return "success";
}
用户定义的异常处理类
package com.it.filter;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* 自定义异常处理实现类
*/
@Component //使用注解加到容器中去,注:要扫描这个包
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
/**
* 处理异常的方法
* @param request 请求对象
* @param response 响应对象
* @param handler HandlerMethod对象
* @param ex 出现的异常
* @return 跳转到哪个页面,并且指定模型数据
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println(handler.getClass());
//第三个参数对象
HandlerMethod handlerMethod = (HandlerMethod) handler;
//获取处理器中哪个方法出现了异常
Method method = handlerMethod.getMethod();
System.out.println("出现异常的方法:" + method.getName()); //update()
Object bean = handlerMethod.getBean();
System.out.println("出现异常的处理器对象:" + bean); //ExceptionController
//跳转到/pages/error.jsp页面
return new ModelAndView("error", "msg", ex.getMessage());
}
}
配置文件
springMVC.xml在IoC容器中声明异常处理类
可以配置,也可以使用注解
<!-- 1.扫描处理器所在包 -->
<context:component-scan base-package="com.it"/>