SpringMVC

Spring 框架是一个开源的 Java 平台,它为容易而快速的开发出耐用的 Java 应用程序提供了全面的基础设施

SpringMVC

一、简介

1.1 引言

java开源框架,Spring Framework的一个独立模块。

MVC框架,在项目中开辟MVC层次架构

对控制器中的功能包装简化扩展践行工厂模式,功能架构在工厂之上

1.2 MVC架构

1.2.1 概念

名称 职责
Model 模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的service和dao
View 视图:渲染数据,生成页面。对应项目中的Jsp
Controller 控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet

1.2.2 好处

  • MVC是现下软件开发中的最流行的代码结构形态;
  • 人们根据负责的不同逻辑,将项目中的代码分成MVC3个层次;
  • 层次内部职责单一,层次之间藕合度低;
  • 符合低精合高内聚的设计理念。也实际有利于项目的长期维护。

二、开发流程

2.1 导入依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>

2.2 配置核心(前端)控制器:DispatcherServlet

作为一个MVC框架,首先要解决的是:如何能够收到请求!

所以MVC框架大都会设计一款前端控制器,选型在Servlet或Filter两者之一,在框架最前沿率先工作,接收所有请求。

此控制器在接收到请求后,还会负责springMVC的核心的调度管理,所以既是前端又是核心。

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--SpringMVC前端(核心)控制器
1.前端,按收所有请求
2.启动SpringMVC工厂mvc.xml
3.springMVC流程调度
-->
<!-- SpringMVC前端控制器 -->
<servlet>
<servlet-name>mvc_shine</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<!--懒汉式 饿汉式加载 -->
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>mvc_shine</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

2.3 后端控制器

等价于之前定义的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller     //声明控制器
@RequestMapping("/hello") //访问路径
public class HelloController {

@RequestMapping("/test1") //访问路径
public String hello1() { //service doGet doPost
System.out.println("hello1");
return "hello"; //跳转:hello.jsp
}
@RequestMapping("/test2") //访问路径
public String hello2() { //service doGet doPost
System.out.println("hello2");
return null;
}
}

2.4 配置文件

默认名称:核心控制器名-servet.xml默认位置:WEB-INF

随意名称:mvc.xml随意位置:resources但需要配置在核心控制器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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: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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 注解扫描 告诉SpringMVC 哪些包中含有注解 -->
<context:component-scan base-package="com.qf.web"/>

<!-- 注解驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>

<!-- 视图解析器
作用:1.辅获后端控制器的返回值="hello"
2.解析:在返回值的前后拼接==>"/hello.jsp"
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>

三、接受请求参数

3.1 基本类型参数

请求参数和方法的形参 同名即可

SpringMVC默认可以识别的日期字符串格式为:YYYY/MM/dd HH:mm:ss

通过@DateTimeFormat可以修改默认日志格式

1
2
3
4
5
6
7
8
9
10
// http://xxxx/param/test1?id=1&name=shine&gender=true&birth=2020-12-12 12:13:20
@RequestMapping("/test1")
public String test1(Integer id,
String name,
Boolean gender,
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date birth) {
System.out.println("test1");
System.out.println("id:" + id + " name:" + name + " gender:" + gender + " birthday:" + birth);
return "hello";
}

3.2 实体收参(重点)

请求参数和实体的属性 同名即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//实体类
public class User {
private Integer id;
private String name;
private Boolean gender;
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date birth;
}

// http://xxxx/param/test2?id=1&name=shine&gender=true&birth=2020-12-12 12:13:20
@RequestMapping("/test2")
public String test2(User user) {
System.out.println("test2");
System.out.println(user);
return "hello";
}

3.3 数组收参

简单类型的 数组

1
2
3
4
5
6
<form action="${pageContext.request.contextPath}/param/test3">
<input type="checkbox" name="hobby" value="football">足球
<input type="checkbox" name="hobby" value="basketball">篮球
<input type="checkbox" name="hobby" value="volleyball">排球
<input type="submit" value="提交">
</form>
1
2
3
4
5
6
@RequestMapping("/test3")
public String test3(String[] hobby) {
System.out.println("test3");
System.out.println(hobby);
return "hello";
}

3.4 集合收参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//实体类
public class UserList {
private List<User> users;
}

//http://xxx/param/test4?users[0].id=1&users[0].name=shine&...
@RequestMapping("/test4")
public String test4(UserList userList) {
System.out.println("test4");
for (User u :
userList.getUsers()) {
System.out.println(u);
}
return "hello";
}

3.5 路径参数

1
2
3
4
5
6
7
8
// {id} 命名规则
// {id} 等价于 /test5/1 test5/2 test5/xxx
@RequestMapping("/test5/{id}")
public String test5(@PathVariable("id") Integer id) {
System.out.println("test5");
System.out.println("id:" + id);
return "hello";
}

3.6 中文乱码

tomcat中字符集设置,对get请求中,中文参数乱码有效

1
Tomcat配置:URIEncoding=utf-8

web.xml中加入编码过滤器,对post请求中,中文参数乱码有效

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 编码过滤器 -->
<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>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

四、跳转

4.1 转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("/test1")
public String test1() {
System.out.println("test1");
// return "hello"; //转发
return "forward:/hello.jsp";
}

@RequestMapping("/test2")
public String test2() {
System.out.println("test2");
// return "forward:/jump/test1"; //转发
return "forward:test1"; //相对路径转发

}

4.2 重定向

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping("/test3")
public String test3() {
System.out.println("test3");
return "redirect:/hello.jsp"; //重定向
}

@RequestMapping("/test4")
public String test4() {
System.out.println("test4");
// return "redirect:test3"; //重定向
return "redirect:/jump/test3"; //重定向
}

4.3 跳转细节

在增删改之后,为了防止请求重复提交,重定向跳转

在查询之后,可以做转发跳转

五、传值

c得到数据后,跳转到V,并向V传递数据。进而V中可以渲染数据,让用户看到含有数据的页面

转发跳转:Request作用域

重定向跳转:Session作用域

5.1 Request和Session

1
2
3
4
5
6
7
8
//在形参中可以获得Request和Session
@RequestMapping("/test1")
public String test1(HttpServletRequest request, HttpSession session) {
System.out.println("test1");
request.setAttribute("name", "张三");
session.setAttribute("age", 10);
return "data";
}

5.2 JSP中取值

1
2
name:${requestScope.name}<br>
age:${sessionScope.age}<br>

5.3 Model

1
2
3
4
5
6
7
8
9
10
11
//model中的数据,会在V渲染之前,将数据复制一份给request
@RequestMapping("/test2")
public String test2(Model model) {
model.addAttribute("city", "北京");
model.addAttribute("street", "长安街");
return "data2";
}

//JSP 通过EL表达式取值
city:${sessionScope.city}<br>
street:${sessionScope.street}

5.4 ModelAndView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ModelAndView 可以集中管理跳转和数据
@RequestMapping("/test4")
public ModelAndView test4() {
//新建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
//设置视图名即如何跳转
modelAndView.setViewName("forward:/hello.jsp");
//增加数据
modelAndView.addObject("claz", "001");
return modelAndView;
}

//JSP 通过EL表达式取值
${sessionScope.claz}

5.5 @SessionAttributes

  • @SessionAttributes(name={“city”,"street}) Model中的name和gender会存入session中

  • SessionStatus 移除Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Controller
@RequestMapping("/data")
@SessionAttributes(names = {"city", "street"})
public class DataController {

@RequestMapping("/test2")
public String test2(Model model) {
model.addAttribute("city", "北京");
model.addAttribute("street", "长安街");
return "data2";
}

@RequestMapping("/test3")
public String test3(SessionStatus status) {
//清空所有通过model存入的session
status.setComplete();
return "data2";
}
}

六、静态资源

6.1 静态资源问题

静态资源:html,js文件,css文件,图片文件

  • 静态文件没有url-pattern,所以默认是访问不到的

  • 之所以可以访问,是因为tomcat中有一个全局的servlet:
    org.apache.catalina.servlets.DefaultServlet,它的url-pattern是"/"

  • 它是全局默认的Servlet.所以每个项目中不用匹配的静态资源的请求,由这个Servlet来处理即可。

  • 但在SpringMVC中DispatcherServlet也采用了“/"作为url-pattern,所以项目中不会再使用全局的Serlvet,则静态资源不能完成访问,

6.2 解决方案1

DispathcerServlet采用其他的url-pattern

此时,所有访问handler的路径都要以action结尾!!

web.xml文件中:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>mvc_shine</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>mvc_shine</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>

6.3 解决方案2

DispathcerServlet的url-pattern依然采用"/",但追加配置

mvc.xml文件中:

1
2
3
4
5
6
7
8
9
<!--
额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
处理方式:将请求转会到tomcat中名为default的Servlet

RequestMapping /* /a /b /c /dxxxx /a/b
/**
-->
<mvc:default-servlet-handler/>

解决方案3

mapping是访问路径,location是静态资源存放的路径

将/html/** 中 /**匹配到的内容,拼接到/hhh/ 后 http://…/html/a.html 访问 /hhh/a.html

mvc.xml文件中

1
<mvc:resources mapping="/html/**" location="/hhh/"/>

七、Json处理

7.1 导入依赖

1
2
3
4
5
6
<!-- SpringMVC默认的Json解决方案是Jackson,所以只需要导入jackson的jar,即可使用。-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.0</version>
</dependency>

7.2 发送Json数据

7.2.1 使用@ResponseBody

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Controller
@RequestMapping("/json")
public class JsonController {

@RequestMapping("/test1")
@ResponseBody //handler的返回值,转换成json,并将json响应给客户端。
public User test1() {
System.out.println("test1");
User user = new User(1, "张三",new Date());
return user;
}

@RequestMapping("/test2")
//@ResponseBody还可以用在handler的返回值上
public @ResponseBody List<User> test2() {
System.out.println("test2");
User user = new User(1, "张三");
User user1 = new User(1, "李四");
List<User> users = Arrays.asList(user, user1);
return users;
}

//如果返回值已经是字符串,则不需要转json,直接将字符串响应给客户端
@RequestMapping(value="/test3",produces ="text/html;charset=utf-8")//produces防止中文乱码
@ResponseBody
public String test3() {
System.out.println("test3");
// return "ok";
return "你好";
}
}

7.2.2 使用@RestController

Controller类上加了@RestController注解,等价于在类中的每个方法上都加了@ResponseBody

7.3 接收Json数据

7.3.1 使用@RequestBody

  • 接收数据

    1
    2
    3
    4
    5
    6
    public class User {
    private Integer id;
    private String username;
    private String password;
    private Boolean gender;
    }
    1
    2
    3
    4
    5
    @RequestMapping("/test4")
    public String test4(@RequestBody User user){//@RequestBody将请求体中的json数据转换为java对象
    System.out.println(user);
    return "ok";
    }
  • Ajax发送数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function send_json() {
    var user = {id: 1, name: "shine"};
    var json = JSON.stringify(user);
    $.ajax({
    url: "${pageContext.request.contextPath}/json/test4",
    type: "post",
    data: json,
    contentType: "application/json",
    success: function (ret) {
    alert(ret);
    }
    })
    }

7.4 Jackson常用注解

7.4.1 日期格式化

@JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”, timezone = “GMT+8”)

1
2
3
4
5
6
public class User {
private Integer id;
private String name;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date birth;
}

7.4.2 属性名修改

1
2
3
//不再使用原属性名 而是“new_id”
@JsonProperty("new_id")
private Integer id;

7.4.3 属性忽略

@JsonIgnore

1
2
3
//生成Json时,忽略此属性
@JsonIgnore
private String name;

7.4.4 null和empty属性排除

Jackson默认会输出null值的属性,如果不需要,可以排除。

@JsonInclude(JsonInclude.Include.NON_NULL) 属性不输出null值

@JsonInclude(JsonInclude.Include.NON_EMPTY) 不输出empty属性(空串,长度为0的集合,null值)

7.4.5 自动化序列

1
2
@JsonSerialize(using = MySerializer.class)
private Double salary = 10000.126; //在输出此属性时,使用MySerializer输出
1
2
3
4
5
6
7
8
9
10
11
public class MySerializer extends JsonSerializer<Double> {

@Override
public void serialize(Double aDouble, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
//将Double salary 的值 四舍五入
String number = BigDecimal.valueOf(aDouble).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
//输出 四舍五入之后的值
jsonGenerator.writeNumber(number);

}
}

7.5 FastJson

7.5.1 导入依赖

1
2
3
4
5
6
<!-- FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>

7.5.2 安装FastJson

web.xml中安装FastJson

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 注解驱动 -->
<mvc:annotation-driven>
<!-- 安装FastJson转换器 -->
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!-- 声明转换类型 -->
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

7.5.3 使用FastJson

@ResponseBody @RequestBody @RestController 使用方法不变

7.5.4 常用注解

  • 日期格式化:@JSONField(format=“yyyy/MM/dd”)
  • 属性名修改:@JSONField(name=“birth”)
  • 忽略属性:@JSONField(serialize=false)
  • 包含null值:@JSONField(serialzeFeatures=SerializerFeature.WriteMapNullValue)默认会忽略所有null值,有此注解会输出null
    • @JSONField(serialzeFeatures=SerializerFeature.WriteNullStringAsEmpty)null的String输出为""
  • 自定义序列化:@JSONField(serializeUsing=MySerializer2.class)
1
2
3
4
5
6
7
8
9
10
11
12
public class User2 {
@JSONField(serialize = false)
private Integer id;
@JSONField(name = "NAME", serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty)
private String name;// ""
@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)
private String city;// null
@JSONField(format = "yyyy/MM/dd")
private Date birth;
@JSONField(serializeUsing = MySerializer2.class)
private Double salary; // 元
}
1
2
3
4
5
6
7
8
9
10
public class MySerializer2 implements ObjectSerializer {

@Override
public void write(JSONSerializer jsonSerializer, Object o, Object o1, Type type, int i) throws IOException {
Double value = (Double) o;
String text = value + "元"; //拼接“元”
jsonSerializer.write(text);

}
}

八、异常解析器

8.1 现有方案,分散处理

Controller中的每个Handler自己处理异常

此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理

8.2 异常解析器,统一处理

Controller中的每个Handler不再自己处理异常,而是直接throws所有异常。

定义一个“异常解析器”集中捕获处理所有异常

此种方案,在集中管理异常方面,更有优势!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 异常解析器
//任何一个Handler中抛出异常
public class MyExceptionResolver implements HandlerExceptionResolver {

@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
if (e instanceof MyException1) {
//errorl.jsp
modelAndView.setViewName("redirect:/error1.jsp");
} else if (e instanceof MyException2) {
//error2.jsp
modelAndView.setViewName("redirect:/error2.jsp");
} else if (e instanceof MyException3) {
//error3.jsp
modelAndView.setViewName("redirect:/error3.jsp");
} else if (e instanceof MaxUploadSizeExceededException){
modelAndView.setViewName("redirect:/uploadError.jsp");
}
return modelAndView;
}
}

mvc.xml中声明异常解析器

1
2
<!-- 异常解析器 -->
<bean class="异常解析器类名"/>

九、拦截器

9.1 作用

作用:抽取handler中的冗余功能

9.2 定义拦截器

执行顺序:preHandle–>postHandle–>afterCompletion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MyInterceptor implements HandlerInterceptor {

//在handler之前执行
//再次定义 handler中冗余的功能
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断登录状态
HttpSession session = request.getSession();
if (session.getAttribute("state") != null) {
return true; //放行 执行后续的handler
}
//中断之前响应请求
response.sendRedirect("/login.jsp");
return false; //中断请求,不再执行后续的handler

}

//在handler之后执行 响应之前执行
// 改动请求中的数据
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("post Handle");
}

//在视图渲染完毕后
//资源回收
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("after completion");
}
}

9.3 配置拦截路径

mvc.xml中添加:

1
2
3
4
5
6
7
8
9
10
<!-- 拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- <mvc:mapping path="/inter/test1"/>-->
<!-- <mvc:mapping path="/inter/test2"/>-->
<mvc:mapping path="/inter/*"/>
<mvc:exclude-mapping path="/inter/login"/>
<bean class="拦截器类名"/>
</mvc:interceptor>
</mvc:interceptors>

十、上传

10.1 导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>

10.2 上传表单

1
2
3
4
<form action="${pageContext.request.contextPath}/upload/test1" method="post" enctype="multipart/form-data">
file: <input type="file" name="source"><br>
<input type="submit" value="上传">
</form>

10.3 上传解析器

1
2
3
4
5
6
7
<!-- 
上传解析器 id必须是"multipartResolver"
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大可上传的文件大小 单位byte 超出后会抛出异常 -->
<property name="maxUploadSize" value="1048576"/>
</bean>

10.4 Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Controller
@RequestMapping("/upload")
public class UploadController {

@RequestMapping("/test1")
public String test1(MultipartFile source, HttpSession session) throws IOException {
System.out.println("test1");
//获取上传文件的名称
String filename = source.getOriginalFilename();
//获取上传文件的类型
String contentType = source.getContentType();

//生成一个唯一的文件名
String uniqueFilename = UUID.randomUUID().toString();
//获取文件后缀
String extension = FilenameUtils.getExtension(filename);
//拼接唯一文件名
String uniqueFilename2 = uniqueFilename + "." + extension;

System.out.println(filename);
System.out.println(contentType);

//保存文件
// source.transferTo(new File("d:/abd.img"));
String realPath = session.getServletContext().getRealPath("/upload");
System.out.println("realpath" + realPath);
source.transferTo(new File(realPath + "\\" + uniqueFilename2));
return "index";
}
}

十一、下载

11.1 超链

1
<a href="${pageContext.request.contextPath}/download/test1?name=c93d8b03ecf53.jpg">下载</a>

11.2 Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
@RequestMapping("/download")
public class DownloadController {

@RequestMapping("/test1")
public void test1(String name, HttpSession session, HttpServletResponse response) throws IOException {
String realPath = session.getServletContext().getRealPath("/upload");
String filePath = realPath + "\\" + name;

//设置响应头告知浏览器,要以附件的形式保存内容 filename=浏览器显示的下载文件名
response.setHeader("content-disposition", "attachment;filename=" + name);

//响应过程
IOUtils.copy(new FileInputStream(filePath), response.getOutputStream());

}
}

十二、验证码

12.1 导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Kaptcha -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>

12.2 声明验证码组件

web.xml中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!-- 验证码组件 -->
<servlet>
<servlet-name>cap</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>

<!-- 验证码的图片是否有边框 -->
<init-param>
<param-name>kaptcha.border</param-name>
<param-value>no</param-value>
</init-param>

<!-- 验证码字符长度 -->
<init-param>
<param-name>kaptcha.textproducer.char.length</param-name>
<param-value>4</param-value>
</init-param>

<!-- 验证码字符集 -->
<init-param>
<param-name>kaptcha.textproducer.char.string</param-name>
<param-value>abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</param-value>
</init-param>

<!-- 图片底色 -->
<init-param>
<param-name>kaptcha.backgroud.clear.to</param-name>
<param-value>211,229,237</param-value>
</init-param>

<!-- 将验证码存入session -->
<init-param>
<!--session.setAttribute("captchd","验证码")-->
<param-name>kaptcha.session.key</param-name>
<param-value>captcha</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>cap</servlet-name>
<!-- 生成验证码的路径 -->
<url-pattern>/captcha</url-pattern>
</servlet-mapping>

十三、REST

13.1 开发风格

是一种开发风格,遵从此风格开发软件,符合REST风格,则RESTFUL。

两个核心要求:

  • 每个资源都有唯一的标识(URL)
  • 不同的行为,使用对应的http-method
访问标识 资源
http://localhost:8989/xxx/users 所有用户
http://localhost:8989/xxx/users/1 用户1
http://localhost:8989/xxx/users/1/orders 用户1的所有订单
请求方式 标识 意图
GET http://localhost:8989/xxx/users 查询所有用户
POST http://localhost:8989/xxx/users 在所有用户中增加一个
PUT http://localhost:8989/xx/users 在所有用户中修改一个
DELETE http://localhost:8989/xxx/users/1 删除用户1
GET http://localhost:8989/xxx/users/1 查询用户1
GET http://localhost:8989/xxx/users/1/orders 查询用户1的所有订单
POST http://localhost:8989/xxx/users/1/orders 在用户1的所有订单中增加一个

13.2 使用

13.2.1定义Rest风格的Controller

@RequestMapping(value="/users",method=RequestMethod.GET)

等价于

@GetMapping("/users")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 查询:所有用户 id=xx 某一个用户
* 删除:id=xx 某一个用户
* 增加:在所有用户中 增加一个
* 修改:在所有用户中 修改一个
* <p>
* 资源:所有用户 /users
* id=xx 某一个用户 /users/{id}
*/
@RestController()
public class MyRestController {

@GetMapping("/users")
public List<User> queryUsers() {
System.out.println("query users with get");
User user = new User(1, "张三");
User user1 = new User(2, "李四");
return Arrays.asList(user, user1);
}

@GetMapping("/users/{id}")
public User queryOne(@PathVariable Integer id) {
System.out.println("query one user with get:" + id);
return new User(1, "张三");
}

@DeleteMapping("/users/{id}")
public String deleteOne(@PathVariable Integer id) {
System.out.println("delete one user with delete:" + id);
return "ok";
}

@PostMapping("/users")
public String saveUser(@RequestBody User user) {
System.out.println("save user with post:" + user);
return "ok";
}

@PutMapping("/users")
public String updateUser(@RequestBody User user) {
System.out.println("update user with put:" + user);
return "ok";
}
}

13.2.2 Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<input type="button" value="queryALL" onclick="queryAll();">
<input type="button" value="queryOne" onclick="queryOne();">
<input type="button" value="saveUser" onclick="saveUser();">
<input type="button" value="updateUser" onclick="updateUser();">
<input type="button" value="deleteUser" onclick="deleteUser();">
<script>
function queryAll() {
$.ajax({
type: "get",
url: "${pageContext.request.contextPath}/users",
success: function (ret) {
console.log("查询所有:");
console.log(ret);
}
});
}

function queryOne() {
$.ajax({
type: "get",
url: "${pageContext.request.contextPath}/users/100",
success: function (ret) {
console.log("查询单个用户");
console.log(ret);
}
});
}

function saveUser() {
var user = {name: "shine", birth: "2020-12-12 12:12:20"}
$.ajax({
type: "post",
url: "${pageContext.request.contextPath}/users",
data: JSON.stringify(user),
contentType: "application/json",
success: function (ret) {
console.log("增加用户");
console.log(ret);
}
});
}

function updateUser() {
var user = {id: 1, name: "shine2", birth: "2020-12-13 12:12:20"}
$.ajax({
type: "put",
url: "${pageContext.request.contextPath}/users",
data: JSON.stringify(user),
contentType: "application/json",
success: function (ret) {
console.log("更新用户");
console.log(ret);
}
});
}

function deleteUser() {
$.ajax({
type: "delete",
url: "${pageContext.request.contextPath}/users/200",
success: function (ret) {
console.log("删除用户");
console.log(ret);
}
});
}
</script>

十四、跨域请求

14.1 域

域:协议+IP+端口

14.2 Ajax跨域问题

Ajax发送请求时,不允许跨域,以防用户信息泄露。

当Ajax跨域请求时,响应会被浏览器拦截(同源策略),并报错。即浏览器默认不允许ajax跨域得到响应内容。

互相信任的域之间如果需要ajax访问,(比如前后端分离项目中,前端项目和后端项目之间),则需要额外的设置才可正常请求。

14.3 解决方案

允许其他域访问

在被访问方的Controller类上,添加注解

1
@Crossorigin("http://localhost:8080")//允许此域发请求访问
  • 携带对方cookie,使得session可用
  • 在访问方,ajax中添加属性:withCredentials:true

十五、SpringMVC执行流程

SpringMVC执行流程

十六、Spring整合

16.1 整合思路

此时项目中有两个工厂

  • DispatcherServlet启动的springMVC工厂=负责生产C及springMVC自己的系统组件
  • ContextLoaderListener启动的spring工厂==负责生产其他所有组件
  • springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
  • 整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可

16.2 整合技巧

两个工厂不能有彼此侵入,即,生产的组件不能有重合。

mvc.xml中添加:

1
2
3
4
5
6
7
8
9
<!--
告知SpringMVC哪些包中存在被注解的类
use-default-filters=true 凡是被 @Controller @Service @Repository 注解的类,都会被扫描
use-default-filters=false 默认不扫描包内的任何类,只扫描include-filter中指定的类
只扫描被@Controller注解的类
-->
<context:component-scan base-package="com.techoc" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

applicationContext.xml中添加:

1
2
3
4
5
6
7
<!--
告知Spring
唯独不扫描eController注解的类
-->
<context:component-scan base-package="com.techoc" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!