spring mvc
用来开发基于 web 的应用程序
1. 流行的 mvc 框架
- struts 1.x
- webwork
- struts 2.x (webwork+struts 1.x) ssh 经常暴露安全问题
- springmvc 更容易上手,相对更安全
2. mvc 思想
model 模型 - 数据 domain
view 视图 - 数据的展现方式 jsp, jstl, el
controller 控制器 - 结合模型和视图,控制请求流程 servlet, controller
servlet 如果作为控制器
缺点1:请求参数处理麻烦 String 值 = request.getParameter(“参数名”)
缺点2:当业务比较多时,servlet 类的数量膨胀不好管理
controller 优点
优点:利用spring 各种思想,ioc, di, aop, 可扩展性高
3. spring mvc 使用步骤
- 添加jar依赖
<!-- spring mvc 依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
<!-- servlet, jsp, 标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
- 添加 spring 配置文件
<?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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 启用了 与 springmvc 相关的注解
@RestController
@RequestMapping
@ResponseBody
@ExceptionHandler
...
-->
<mvc:annotation-driven />
- 添加控制器
- spring 提供的前控制器(实际是一个servlet),作为一个统一入口,由它来分发请求,进入真正的控制器
需要使用 web.xml 文件配置它的路径
<!-- 配置前控制器 (servlet) -->
<servlet>
<servlet-name>aa</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指明 spring 配置文件的位置, 根据它创建 spring容器 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- tomcat 启动时, 就创建和初始化 servlet, 同时创建 spring 容器 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>aa</servlet-name>
<!-- / 用来匹配所有的路径 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
- 自己提供的控制器
@Controller
public class 控制器类 {
@RequestMapping("请求路径") // 请求路径要唯一
public String 方法1() {
}
...
@RequestMapping("请求路径")
public String 方法n() {
}
}
- 配置视图
控制器方法的返回值是一个视图名,此视图名经过视图解析器解析为一个完整路径,利用请求转发跳转至该jsp
<mvc:view-resolvers>
<!-- 前缀 + 视图名 + 后缀 == 完整路径, 再利用请求转发跳转至 jsp -->
<mvc:jsp prefix="/" suffix=".jsp" />
</mvc:view-resolvers>
- 处理请求参数
方法1: 只要把方法的参数名称和请求的参数名称对象,springmvc 会将请求参数的值赋值给方法参数,并完成类型转换
日期类型需要特殊处理,请求参数类型转换出错,会出现400错误, 日期可以使用 @DateTimeFormat(pattern=“日期格式”)
@RequestMapping("/c1")
public String a1(String name, Integer age,@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
System.out.println("执行了 a1 方法: name:" + name + " age:" + age + " birthday:" + birthday);
// 返回值代表视图的名字,但要注意它不是完整路径
// /hello.jsp
return "hello";
// 没有经过视图解析器,直接转发进行了跳转
return "forward:/hello.jsp";
}
方法2:当请求参数较多时,可以把请求参数映射到java对象的属性上
class User{
private String name;
private Integer age;
private Date birthday;
}
@RequestMapping("/c3")
public String c3(User user) {
System.out.println(user);
return "hello";
}
方法3:把参数信息包含在路径当中
删除用户
/deleteUser?id=1
/deleteUser?id=2
…
/deleteUser/1 删除1号用户
/deleteUser/2 删除2号用户
…
// 例如 /deleteUser/1 {id} 的值就是1 /deleteUser/2 {id} 的值就是2
@RequestMapping("/deleteUser/{id}")
// @PathVariable 获取一个路径变量的值{id}, 然后复制给方法参数
public String c4(@PathVariable("id") int aa) {
System.out.println("值为:" + aa);
return "hello";
}
- 处理模型数据
Model接口, 需要调用Model 的相关方法把数据存入模型,页面上再从模型中获取数据并显示
@RequestMapping("/c5")
public String c5(Model model) {
model.addAttribute("name", "张三");
model.addAttribute("list", Arrays.asList("苹果","草莓","橘子"));
return "model";
}
${名字} <c:forEach>
原理:会把模型中数据放入request作用域
=========================================================
1. 汉字乱码
在 web.xml 中添加一个 spring 提供的 CharacterEncodingFilter
<!-- 配置字符编码过滤器 -->
<filter>
<filter-name>bb</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>
</filter>
<filter-mapping>
<filter-name>bb</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2. 使用传统 servlet 中的类
HttpServletRequest, HttpServletResponse, HttpSession 他们都可以作为控制器方法的参数,直接使用即可,spring会把他们准备好
@RequestMapping("/s1")
public String s1(HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
session.setAttribute("name", "老王");
return "servlet";
}
3. 异常处理
局部的异常处理器,只针对某个控制器
// 专门定义一个处理异常的方法
// ArrayIndexOutOfBoundsException
@ExceptionHandler(Exception.class) // 异常处理器, 可以指定具体的异常类型
public String error(Exception e) {
System.out.println("进入了 ExceptionController error " + e.getMessage());
return "error";
}
全局的异常处理器,针对所有的控制器
@ControllerAdvice
class 全局异常处理器类 {
@ExceptionHandler(异常类型)
public String error(异常对象) {
// 异常处理
return "视图名";
}
}
局部的异常处理优先级高于全局的异常处理
4. 文件上传
- pom.xml添加依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
- springmvc.xml 中对上传文件表单做特殊配置
简单表单
name=zhangsan&age=18
上传文件表单
multipartResovler 上传文件表单的解析器
<!-- 配置上传文件表单解析器, id 是固定的 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 限制上传文件的总大小,单位是字节-->
<property name="maxUploadSize" value="2000"></property>
</bean>
- 编辑表单页面
<!-- multipart/form-data 表示表单数据由多部分,属于复杂的表单格式 -->
<form action="请求地址" method="post" enctype="multipart/form-data">
<!-- 文件选择框 -->
<input type="file" name="请求参数名">
</form>
- 编写控制器
@Controller
public class UploadController {
@RequestMapping("/upload")
public String upload(String username,
Integer age,
MultipartFile image) throws IOException {
System.out.println("上传文件的原始名称:" + image.getOriginalFilename());
System.out.println("上传文件的类型:" + image.getContentType());
System.out.println("获取上传文件大小:" + image.getSize());
// 生成了临时文件, 当请求结束, 此临时文件会被删除
// 把临时文件另存为: file
image.transferTo(new File("d:\\" + image.getOriginalFilename()));
return "hello";
}
}
- 图片预览
通过js完成
<input type="file" name="image" id="image" onchange="preview()">
<img src="" id="preview"/>
function preview() {
// 获取图片选择框中图片的二进制信息
var image = document.getElementById("image");
var file = image.files[0]; // 拿到数组中第一个图片文件
// 读取文件内容
var reader = new FileReader();
// 定义事件
reader.onload = function() {
// 获取结果
document.getElementById("preview").src = this.result;
};
// 开始读取
reader.readAsDataURL(file);
}
- 对上传文件的控制
从客户端的角度限制
服务器端要对上传文件做检查
图片,修改大小 800x600 -> 640x480 成功
假图片,修改大小 800x600 -> 640x480 报异常
文件大小限制
<!-- 配置上传文件表单解析器, id 是固定的 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 限制上传文件的总大小,单位是字节-->
<property name="maxUploadSize" value="2000"></property>
</bean>
- springmvc 对静态文件的处理(图片,html,js,css)
springmvc 默认把所有路径都当做了 控制器 的路径
在springmvc.xml中设置
<!-- 对静态资源的处理, 不要把静态资源的路径当做 controller 的路径-->
<mvc:default-servlet-handler/>
5. spring 中的拦截器
请求到达控制器之前,先经过拦截器,才到达控制器
Filter 过滤器接口
HandlerInterceptor 拦截器接口
- 编写拦截器
@Component
public class Interceptor1 implements HandlerInterceptor {
// 在控制器方法执行前被调用, 返回 true 放行请求, 如果返回 false 拦截请求(不会前进了)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("==========> 进入了 Interceptor1 preHandle");
return true;
}
// 在控制器方法执行后被调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("==========> 进入了 Interceptor1 postHandle");
}
// 在控制器和视图都完成后被调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("==========> 进入了 Interceptor1 afterCompletion");
}
}
- 配置拦截器
<!-- 某个拦截器 -->
<mvc:interceptor>
<!-- 拦截器要匹配的路径 -->
<mvc:mapping path="/hello" />
<!-- 要调用的拦截器 -->
<ref bean="interceptor1"/>
</mvc:interceptor>
6. 对 json 数据的支持
var xhr = new XMLHttpRequest(); 可以在页面不刷新的情况下与服务器进行交互
xhr.onload = function() {
var json = xhr.responseText;
JSON.parse(json); // 把json字符串转为 js 对象
};
xhr.open("get|post", url, true|false);
xhr.send();
6.1 @ResponseBody 注解
加在控制器方法上,将控制器方法的返回结果转换为json字符串,并写入响应
6.2 @RestController 注解
加在控制器类上,表示所有控制器方法都加了 @ResponseBody 注解
6.3 其他注解
@RequestMapping // 不区分 get,post
@GetMapping // 专门匹配 get 请求 等价于 @RequestMapping(method=RequestMethod.GET)
@PostMappping // 专门匹配 post 请求
@DeleteMapping // 专门匹配 delete 请求
@PutMapping // 专门匹配 put 请求
get 一般对应查询操作 select
post 一般对应新增操作 insert
delete 对应删除操作 delete
put 对应修改操作 update
6.4 @RequestBody
把请求中的json 字符串,转换为java 对象
服务器端代码:
@PostMapping("/json4")
// @RequestBody 作用是把请求体中的 json 字符串,转为 java 中的对象
public void json4(@RequestBody Student student) {
System.out.println(student);
System.out.println("ok");
}
客户端代码:
function sendStudent() {
var student = {"id": 2, "name":"李四"}; // {id: 2, name:"李四"}
var xhr = new XMLHttpRequest();
xhr.open("post", "/json4", true);
// 设置请求体的格式为 json 格式
xhr.setRequestHeader("content-type", "application/json");
// 将 js 对象 转为为 json 字符串, 并作为请求体, 发送给服务器
xhr.send( JSON.stringify(student) );
}
6.5 ajax 请求跨域(源)
两个应用程序,ip 地址不一样或是端口号不一样,就称之为跨域
localhost:8080 提供控制器,返回json数据, 通过 cors 的技术运行其他域的机器访问我的数据
@CrossOrigin(“允许访问我的数据的ip地址”)
@CrossOrigin("http://192.168.9.3") // 只允许9.3 访问
@CrossOrigin("http://192.168.9.3,http://192.168.9.4,http://192.168.9.5") //允许多个ip访问
@CrossOrigin("*") // 允许所有域访问
可以加在方法上,表示只有此方法允许跨域
还可以加在控制器类上,表示此控制器中的所有方法都允许跨域
localhost:9090 ajax 获取 localhost:8080 的json数据, 默认情况没有权限访问
7. 重定向请求
@RequestMapping("/test1")
public String test1() {
// 重定向到 /test2 控制器
return "redirect:/test2";
}
@RequestMapping("/test2")
public void test2() {
//...
}