文章目录
- MVC
- SpringMVC与JavaEE对比
- SpringMVC
- SpringMVC的核心流程
- SpringMVC入门案例
- `@RequestMapping`注解的使用
- Handler方法的返回值
- Handler方法的形参
- `key=value`形式的请求参数
- Json请求参数
- RESTful风格接口
- 静态资源处理
- Filter
- HandlerInterceptor
- 异常处理
- SpringMVC核心流程
- 流程图
MVC
- MVC设计模式的任务是将包含业务数据的模块与显示模块的视图解耦
- SpringMVC是在Spring框架的基础上做的
SpringMVC与JavaEE对比
- SpringMVC是通过一个Servlet(DispatcherServlet)来接收全部请求,然后分发到不同的方法上
SpringMVC
SpringMVC的核心流程
SpringMVC入门案例
- 导入依赖
<dependencies>
<!--spring的核心包 + spring-web 、 spring-webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 启动项配置
AACDSI
→ 抽象类AbstractAnnotationConfigDispatcherServletInitializer
- 类似于SpringMVC的启动类
onStartup
方法,里面做了两次ac = new AnnotationConfigWebApplicationContext
- 抽象类的特点是:如果里面有抽象方法我们需要是实现其抽象方法
- 在抽象类的子类中去写这两个抽象方法的实现 → 提供自定义的配置类
- 示意图如下:
eg:
config
目录下ApplicationInitialization.java
// AACDSI == AbstractAnnotationConfigDispatcherServletInitializer
/**
* 启动类的配置
*/
public class ApplicationInitialization
extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* AnnotationConfigWebApplicationContext 对比 ApplicationContext 多了对web应用的支持
*
* 创建ApplicationContext的时候提供配置类
* ApplicationContext ac1 = new AnnotationConfigWebApplicationContext(clazz);
*
* 创建ApplicationContext的时候提供配置类
* ApplicationContext ac2 = new AnnotationConfigWebApplicationContext(clazz);
*
* 应用程序中有两个ApplicationContext?
* 两个ac之间存在着包含关系
*
*/
/**
* 内部的ApplicationContext的配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfiguration.class};
}
/**
* 外部的ApplicationContext的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfiguration.class};
}
/**
* 配置DispatcherServlet的ServletMapping = /
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
RootConfiguration.java
/**
* 在容器中排除掉controller组件
*/
/**
* 这里可以整合Mybatis、增加事务等
*/
@Configuration
@ComponentScan(value = "com.coo1heisenberg.demo1",
// excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class))
// excludeFilters = @ComponentScan.Filter(value = Controller.class))
excludeFilters = @ComponentScan.Filter(value =
{Controller.class, EnableWebMvc.class}))
public class RootConfiguration {
}
WebConfiguration.java
// @Configuration
@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo1.controller")
// WebMvcConfigurer接口:后面还会做一些和SpringMVC相关的配置
// 需要实现接口 WebMvcConfigurer,如果实现接口里的方法就是提供一些额外的配置信息
public class WebConfiguration implements WebMvcConfigurer {
}
- SpringMVC的使用
- 在
Controller
目录下
- 在
eg:
@Controller
public class UserController {
public UserController() {
System.out.println("UserController 组件的初始化");
}
@SneakyThrows
@RequestMapping("/hello")
public void hello(HttpServletRequest request, HttpServletResponse response) {
BaseRespVo vo = BaseRespVo.ok("hello world");
ObjectMapper objectMapper = new ObjectMapper();
String jsonStr = objectMapper.writeValueAsString(vo);
response.getWriter().println(jsonStr);
}
// Handler -> HandlerMethod -> Handler方法
@RequestMapping("hello2")
@ResponseBody // SpringMVC帮我们使用Jackson,把我们的返回值转换成JSON字符串进行响应
public BaseRespVo hello2() {
return BaseRespVo.ok("hello world2");
}
}
@RequestMapping
注解的使用
- 通过
@RequestMapping
注解的不同属性,实现不同功能上的限定- 请求URL限定
- 窄化请求
- 窄化请求:将
@RequestMapping
写在类上 - 该Controller组件中的方法映射的URL:类上的@RequestMapping的value属性值 + 方法上的@RequestMapping的value属性值
- 窄化请求:将
- 请求方法限定
- 请求参数限定
- 请求头限定
- 注解的定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default ""; // 没啥用
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {}; // value属性:映射URL是什么,可以映射多个值,还可以写通配符
/**
eg:@RequestMapping("hello/*", "hello*");
*/
RequestMethod[] method() default {}; // 请求方法限定 GET/POST
String[] params() default {}; // 请求参数限定 ?后面的
String[] headers() default {}; // 请求体限定 携带的token等等
String[] consumes() default {}; // 和请求头有关,对应的content-type
// content-type指请求体里正文的类型
String[] produces() default {}; // 和请求头有关,对应的accept
// accept指服务端响应报文响应体里正文的格式
}
- value属性的使用
/**
* value属性的使用
*/
@Controller
@RequestMapping("user")
public class UserController {
/**
* localhost:8080/use/hello/111
* localhost:8080/use/helloXXX
* @return
*/
@RequestMapping(value = {"hello/*", "hello*"})
@ResponseBody
public BaseRespVo hello() {
return BaseRespVo.ok("hello aaaaa");
}
}
- 请求方法限定
- 引申注解:
@GetMapping
、@PostMapping
- 这两个注解就是限定了请求方法的
@RequestMapping
- 这两个注解就是限定了请求方法的
- 引申注解:
@RequestMapping(
method = {RequestMethod.GET}
)
public @interface GetMapping {}
@RequestMapping(
method = {RequestMethod.POST}
)
public @interface PostMapping {}
eg:
/**
* method属性:请求方法限定
*/
@Controller
@RequestMapping("method")
public class MethodLimitController {
/**
* localhost:8080/demo2/method/get
*
* @return
*/
@ResponseBody
// @RequestMapping(value = "get", method = RequestMethod.GET)
@GetMapping("get") // 与上面等同
public BaseRespVo methodGet() {
return BaseRespVo.ok();
}
@ResponseBody
//@RequestMapping(value = "post", method = RequestMethod.POST)
@PostMapping("post") // 与上面等同
public BaseRespVo methodPost() {
return BaseRespVo.ok();
}
/**
* 多个值之间的关系是or的关系
* @return
*/
@ResponseBody
@RequestMapping(value = "getorpost", method = {RequestMethod.GET, RequestMethod.POST})
public BaseRespVo methodGetOrPost() {
return BaseRespVo.ok();
}
}
- 请求参数限定
- 可以写多个值,写多个值的情况,多个值之间的关系是and(或着说是:且)
- 错误码:400 → 是请求参数封装有问题
eg:
@Controller
@RequestMapping("parameter")
public class ParameterLimitController {
/**
* localhost:8080/demo2/parameter/limit?username=zs&password=123456
* @return
*/
@ResponseBody
// params = {"username","password"} 含义:既要携带username这个请求参数,也要携带password
@RequestMapping(value = "limit", params = {"username", "password"}) // params里面是且的关系
public BaseRespVo limit() {
return BaseRespVo.ok();
}
}
- 请求头限定
- 是字符串数组,多个值之间的关系也是and(或者说是且)
eg:
@Controller
@RequestMapping("header")
public class HeaderLimitController {
/**
* localhost:8080/demo2/header/limit
* @return
*/
@ResponseBody
// headers也是且的关系
@RequestMapping(value = "limit", headers = {"x-coo1heisenberg-token", "language"})
public BaseRespVo limit() {
return BaseRespVo.ok();
}
}
- Content-Type请求头值限定consumes属性
- 限定的是Content-Type这个请求头的值 → 这个请求头的含义:请求携带的 正文的类型(请求体)
- 比如一个jpg文件:image/jpeg
- 比如文本:text/html
- 比如json:application/json
- 语法的格式:
xxx/xxx
- Accept请求头值限定produces属性
- 限定的是Accept这个请求头的值 → 这个请求的含义 → 客户端希望接收到的服务端响应的正文类型
- 语法的格式:
xxx/xxx
eg:
@Controller
@RequestMapping("header")
public class HeaderLimitController {
/**
* localhost:8080/demo2/header/consumes
* localhost:8080/demo2/header/produces
*
* @return
*/
@ResponseBody
@RequestMapping(value = "consumes", consumes = "abc/def")
// 限定content-type:abc/def
public BaseRespVo consumes() {
return BaseRespVo.ok();
}
@ResponseBody
@RequestMapping(value = "produces", produces = "application/json")
// 限定accept:application/json
public BaseRespVo produces() {
return BaseRespVo.ok();
}
}
- 解决中文乱码问题
- JavaEE阶段解决响应中文乱码问题:响应头中Content-Type → 响应体的内容的字符集
- SpringMVC阶段解决中文乱码问题:请求头中的Accept → 响应体的内容的字符集
eg:
@Controller
@RequestMapping("chinese")
public class ChineseResolveController {
@RequestMapping(value = "resolve", produces = "application/json;charset=utf-8")
@ResponseBody
public String chinese() {
return "你好中文";
}
}
Handler方法的返回值
DispatcherServlet获取 → 判断类型
- 如果是ModelAndView → 转发到jsp页面,在jsp页面上渲染model提供的数据 → 已经不是主流了
- 如果是Json需要的形式 →
response.getWriter.write(jsonStr)
- ModelAndView
- ModelAndView主要是为单体应用服务的,单体应用的话,前端和后端都整合在一个应用程序中
- ModelAndView主要是给访问前端的视图和数据的,当服务器的这个方法返回了一个ModelAndView的话,会访问到前端的内容
- Tomcat对jsp处理本身就是特殊的处理 → jsp文件会被编译Servlet
- 返回值写为了ModelAndView,在方法上并没有增加@ResponseBody
eg:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>hello ${name}</h1>
</body>
</html>
@Controller
@RequestMapping("mav")
public class ModelAndViewController {
/**
* localhost:8080/demo3/mav/hello?name=zs
* localhost:8080/demo3/mav/hello2?name=zs
*/
/**
* 访问到hello.jsp,里面显示的也是hello xxx
* @return
*/
// @SneakyThrows
// @RequestMapping("hello")
// public ModelAndView hello(HttpServletRequest request, HttpServletResponse response) {
// request.setAttribute("name", request.getParameter("name"));
/**
* 请求转发:
* 将请求从一个Servlet转发到另一个Servlet或JSP页面,但URL不会改变。
* 在转发过程中,请求对象(HttpServletRequest)和响应对象(HttpServletResponse)在Servlet之间共享
* @param request
* @return
*/
// request.getRequestDispatcher("/WEB-INF/hello.jsp").forward(request, response);
//
// }
@RequestMapping("hello")
public ModelAndView hello(HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/WEB-INF/hello.jsp");
modelAndView.addObject("name", request.getParameter("name"));
return modelAndView;
}
// 如果返回值是视图名,不能加@RequestBody注解
@RequestMapping("/hello2")
public String hello2(Model model, HttpServletRequest request) {
model.addAttribute("name", request.getParameter("name"));
return "/WEB-INF/hello.jsp"; // ModelAndView中的视图名
// 等价于:modelAndView.setViewName("/WEB-INF/hello.jsp");
}
}
- Json
- 准备事务
- jackson相关依赖(jackson-databind)
@EnableWebMvc
- 方法上(或类上)增加注解
@ResponseBody
- 注意事项:如果要响应的是引用类型的对象,提供构造方法和getter/setter方法
@ResponseBody
注解:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseBody { }
- 引申注解:@RestController = @Controller + @ResponseBody
- 准备事务
- 有两种方式:
String
(并不建议继续使用了)- 后续我们并不直接返回String了,因为这样子写比较繁琐,大家还需要处理字符集问题,返回值为String的话,它的字符集默认是iso-8859-1
Object
(你写啥类型都行)- 直接返回集合或引用类型对象就可以了
- 直接返回对象,Jackson会自动的帮我们进行转换, 并且也不需要设置字符集,返回值的中文没有乱码
eg:
@RequestMapping("json")
//@Controller
//@ResponseBody // 意味着所有的方法上都加上了@ResponseBody
@RestController // 复合注解 等同于:@Controller + @ResponseBody
public class JsonController {
/**
* 1. 直接响应字符串
* 2. 直接响应Object对象,然后Jackson会将该对象转换成Json字符串响应出去
*/
@RequestMapping("hello1")
// @ResponseBody
public String hello1() {
return "hello world";
}
@RequestMapping("hello2")
// @ResponseBody
public BaseRespVo hello2() {
return BaseRespVo.ok("hello2");
}
}
Handler方法的形参
方法的形参:主要做的事情是接收请求参数 以及一些其他的信息
key=value
形式的请求参数Json
形式的请求参数- 其他的信息
key=value
形式的请求参数
- String、基本类型以及对应的包装类
- 直接接收:请求参数名和方法的形参名一致
- 可以使用基本类型或其包装类接收的时候,建议使用包装类来接收 → 健壮性
eg:
/**
* key = value形式的请求参数的接收
*/
@RequestMapping("user")
@RestController
public class UserController {
/**
* localhost:8080/demo4/user/register?username=zs&password=nihaoya&age=30
*/
@RequestMapping("/register")
public BaseRespVo register(String username, String password, Integer age) {
return BaseRespVo.ok();
}
}
- vo的封装是开发过程中非常常规的使用
eg:
@Data
public class BaseRespVo<T> {
T data;
String errmsg;
int errno;
public static <T> BaseRespVo ok(T data) {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrmsg("成功");
baseRespVo.setData(data);
return baseRespVo;
}
public static <T> BaseRespVo ok() {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrmsg("成功");
return baseRespVo;
}
public static <T> BaseRespVo invalidData(String msg) {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrno(504);
baseRespVo.setErrmsg(msg);
return baseRespVo;
}
public static <T> BaseRespVo invalidData() {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrno(504);
baseRespVo.setErrmsg("更新数据已失效");
return baseRespVo;
}
public static <T> BaseRespVo invalidParameter(String msg) {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrno(400);
baseRespVo.setErrmsg(msg);
return baseRespVo;
}
public static <T> BaseRespVo unAuthc() {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrno(502);
baseRespVo.setErrmsg("认证失败");
return baseRespVo;
}
public static <T> BaseRespVo expired() {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrno(502);
baseRespVo.setErrmsg("认证信息过期,请重新登录");
return baseRespVo;
}
public static BaseRespVo badArgument() {
return fail(401, "参数不对");
}
public static BaseRespVo fail(int errno, String errmsg) {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrno(errno);
baseRespVo.setErrmsg(errmsg);
return baseRespVo;
}
public static BaseRespVo fail(String errmsg) {
BaseRespVo baseRespVo = new BaseRespVo();
baseRespVo.setErrno(500);
baseRespVo.setErrmsg(errmsg);
return baseRespVo;
}
}
- Date形式
- SpringMVC提供的
String → Date
的转换器,执行转换的过程中也是需要pattern信息的- 第一种是:采用默认的pattern信息 →
yyyy/MM/dd
- 第二种是:使用
@DateTimeFormat
注解的pattern属性指定
- 第一种是:采用默认的pattern信息 →
- 注意:请求参数名和方法的形参名一致
- SpringMVC提供的
eg:
@RequestMapping("register3")
public BaseRespVo register3(String username, String password, Integer age, Date birthday) {
/**
* 期望使用Date来接收birthday,能否接收取决于SpringMVC是否有提供 String -> Date的Converter
*
* → 可以使用Date来接收 → SpringMVC提供了转换器 → 有DateFormat的要求 yyyy/MM/dd
*
* eg:localhost:8080/demo4/user/register3?username=ls&password=nihao&age=30&birthday=2022/06/21
*/
return BaseRespVo.ok();
}
@RequestMapping("register4")
public BaseRespVo register4(String username, String password, Integer age,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
// 也可以提供自定义的DateFormat
/**
* eg:localhost:8080/demo4/user/register4?username=ww&password=byebye&age=30&birthday=2022-06-21
*/
return BaseRespVo.ok();
}
- 数组
- 可以直接来形参中使用数组来接收
- 前面能够接收到的这个类型的值,也可以使用对应类型的数组来接收
eg:
@RequestMapping("register5")
public BaseRespVo register5(String username, String password, Integer age,
String[] hobbies, Integer[] ids) {
// 之前得使用这个方法
// String[] hobbies = request.getParametersValues("hobbies");
/**
* localhost:8080/user/register5?username=zs&password=nihaoya&age=30
* &hobbies=sing&hobbies=dance&hobbies=rap
* &ids=1&ids=2&ids=3
*/
return BaseRespVo.ok();
}
- 文件 MultipartFile
- 导入
commons-io
和commons-fileupload
依赖
<!--引入commons-fileupload时候也会将commons-io引入-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
- 先向容器中注册一个组件
eg:
@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo4.controller")
// WebMvcConfigurer接口:MVC相关的配置要用到这个接口提供的方法和参数
public class WebConfiguration implements WebMvcConfigurer {
/**
* 如果要使用SpringMVC的文件接收,接收MultipartFile的话,
* 需要向容器中注册MultipartResolver组件
* 处理MultipartFile封装过程中,需要使用这个组件
* 而这个组件 → ac.getBean(beanName)
* beanName=multipartResolver → 也就意味着组件名称只能叫multipartResolver
*
* @return
*/
@Bean
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
}
MultipartFile
类中的内容
public interface MultipartFile extends InputStreamSource {
String getName();
@Nullable
String getOriginalFilename();
@Nullable
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws IOException;
InputStream getInputStream() throws IOException;
default Resource getResource() {
return new MultipartFileResource(this);
}
void transferTo(File var1) throws IOException, IllegalStateException;
default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
}
}
MultipartFile
提供的方法
方法名 | 描述 | 返回值 |
---|---|---|
getOriginFileName() | 获得上传时的文件名 | String |
getContentType() | 获得上传的文件的正文类型,比如上传banner.png,正文类型就是image/png | String |
getName() | 获得是请求参数名(没啥用) | String |
getSize() | 获得文件大小 | long |
transferTo(File) | 提供的参数是File类型的值,File提供的保存位置及文件名,就可以保存这个文件 | void |
- 向指定路径上传一个或者多个文件
@RestController
@RequestMapping("upload")
public class uploadController {
/**
* localhost:8080/demo4/upload/file
*
* 请求参数名和handler方法的形参名一致
* @return
*/
// 上传单个文件
@SneakyThrows
@RequestMapping("file")
public BaseRespVo fileUpload(MultipartFile myfile /*, HttpServletRequest request */) {
/**
// 之前对参数信息的封装
Part part = request.getPart("1.jpg");
InputStream inputStream = part.getInputStream();
long size = part.getSize();
String originFileName = part.getSubmittedFileName();
String name = part.getName();
String contentType = part.getContentType();
*/
InputStream inputStream = myfile.getInputStream();
long size = myfile.getSize();
String originFileName = myfile.getOriginalFilename();
String name = myfile.getName();
String contentType = myfile.getContentType();
/*
FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\tmp", originFileName));
byte[] bytes = new byte[1024];
int length = 0;
while ((length = inputStream.read(bytes)) != -1) {
fileOutputStream.write(bytes,0,length);
}
inputStream.close();
fileOutputStream.close();
*/
// 上面可以优化
File file = new File("d:/tmp", originFileName);
myfile.transferTo(file);
return BaseRespVo.ok();
}
// 上传多个文件
@RequestMapping("files")
public BaseRespVo uploadFiles(MultipartFile[] myfiles) throws IOException {
File fatherPath = new File("d:/tmp");
for (MultipartFile myfile : myfiles) {
File file = new File(fatherPath, myfile.getOriginalFilename());
myfile.transferTo(file);
}
return BaseRespVo.ok();
}
}
- 使用引用类型接收
- 使用引用类型的话,将接收到的形参,封装为一个引用类型对象,这个引用类型的对象的成员变量封装的就是这些形参的值
- 请求参数名和引用类型的成员变量名一致
eg:
/**
* localhost:8080/user/register6?username=songge&password=niupi&age=30
* &hobbies=sing&hobbies=dance&hobbies=rap&ids=1&ids=2&ids=3
* &birthday=2022-06-21
* @return
*/
@RequestMapping("register6")
public BaseRespVo register6(String username, String password, Integer age,
String[] hobbies, Integer[] ids,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
/**
* User user = new User();
* user.setUsername(username);
* user.setPassword(password);
* user.setAge(age);
* user.setHobbies(hobbies);
*/
return BaseRespVo.ok();
- 将这个形参变为一个实体类的成员变量,形参这里写这个实体类的实例
eg:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
String username;
/**
* request.getParameter("username")
* user.setUsername(username)
*/
String password;
Integer age;
/**
* request.getParameter("age")
* Integer.parseInt(ageStr)
* user.setAge()
*/
String[] hobbies;
Integer[] ids;
@DateTimeFormat(pattern = "yyyy-MM-dd")
Date birthday;
}
@RequestMapping("register7")
public BaseRespVo register7 (User user) {
return BaseRespVo.ok();
}
总结:
- 使用形参直接接收:如果参数比较少,直接使用形参
- 使用引用类型的对象来接收:参数比较多;在多个请求中都使用了相同的请求参数
- 可以都用:有些参数就用一次或者用的次数比较少(直接接收),有的参数用了多次(封装为实体类)
Json请求参数
- 如果说一个请求携带Json请求参数,这个请求的特点是:
- 请求方法:
POST
- 正文类型Content-Type:
application/json
- 数据是Json字符串
- 请求方法:
- Json请求参数仍然是在形参中进行接收,可以使用以下几种类型来接收
String
- 引用类型对象
Map
- 形参前需要增加一个注解
@RequestBody
eg:
/**
* String类型
* {"username":"root","password":"123456","birthday":"2022-07-12"}
*/
// @RequestMapping("login1")
// public BaseRespVo login1(HttpServletRequest request) {
// String jsonStr = request.getReader().readLine();
// return BaseRespVo.ok();
// }
@RequestMapping("login1")
public BaseRespVo login1(@RequestBody String jsonStr) {
return BaseRespVo.ok();
}
/**
* 接收到这个json字符串,将字符串转换成map映射
* @param map
* @return
*/
@RequestMapping("login2")
public BaseRespVo login2(@RequestBody Map map) {
// jsonStr -> map
return BaseRespVo.ok();
}
@RequestMapping("login3")
public BaseRespVo login3(@RequestBody UserVo userVo) {
return BaseRespVo.ok();
}
@ResponseBody
@RequestMapping("create")
public BaseRespVo create(@RequestBody GoodVo goodVo) {
return goodsService.create(goodVo);
}
@RequestBody
和@ResponseBody
这两个注解都是和Json打交道的时候使用的注解- 接收的时候用的是
@RequestBody
- 响应的时候用的是
@ResponseBody
- 接收的时候用的是
RESTful风格接口
- REST → 是单词的首字母缩写 → 表述性状态传递 →
Representational State Transfer
- 通过请求(报文)能够提供一些对应的信息,提供给服务器,也就是通过一些方法来获得对应的信息
- 其实就是对Request的封装
- 当前定义接口的URL,通过URL的不同区分不同的操作:
/user/list
查询/user/remove
删除/user/create
新增/user/modify
修改
- 过去使用RESTful风格接口的时候,构建一个场景,做user的增删改查,请求URL都是
/user
/user
查询GET
/user
删除DELETE
/user
新增PUT
/user
修改POST
eg:
@RestController
@RequestMapping("user")
public class UserController {
// User的新增
// @RequestMapping(value = "user", method = RequestMethod.PUT)
@RequestMapping("create")
public BaseRespVo create() {
return BaseRespVo.ok();
}
// User的删除
// @RequestMapping(value = "user", method = RequestMethod.DELETE)
@RequestMapping("delete")
public BaseRespVo delete() {
return BaseRespVo.ok();
}
// User的修改
// @RequestMapping(value = "user", method = RequestMethod.POST)
@RequestMapping("modify")
public BaseRespVo modify() {
return BaseRespVo.ok();
}
// User的查询
// @RequestMapping(value = "user", method = RequestMethod.GET)
@RequestMapping("list")
public BaseRespVo list() {
return BaseRespVo.ok();
}
}
- 我们还可以获得一些其他的信息:
- 请求URL的信息 →
@PathVariable
- 请求参数信息 →
@RequestParam
- 请求头信息 →
@RequestHeader
- Cookie信息 →
@CookieValue
- Session信息 →
@SessionAttribute
- 请求URL的信息 →
- 之前JavaEE阶段的方法:
@RestController
public class ArticleController {
// @RequestMapping("cscscs/article/details/12321241")
@RequestMapping("*/article/details/*")
public BaseRespVo articleDetails(HttpServletRequest request) {
/**
* localhost:8080/demo5/cscscs/article/details/12321241
*/
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();// /demo5
String suffix = requestURI.replaceAll(contextPath + "/", "");
// → cscscs/article/details/12321241
String username = suffix.substring(0, suffix.indexOf("/"));
String id = suffix.substring(suffix.lastIndexOf("/") + 1);
return BaseRespVo.ok();
}
}
@PathVariable
注解 → URI@PathVariable
→ 获得请求URL的一部分值 → 在@RequestMapping
的value属性写占位符{}
- 获得指定占位符位置的值给到所对应的形参 → 形参通过注解接收指定占位符位置的值
- 通过这个注解,就可以把一部分请求参数写到URL中
eg:
@RestController
public class ArticleController {
@RequestMapping("{username}/article/details/{id}")
public BaseRespVo articleDetails(@PathVariable("username") String username,
@PathVariable("id") Integer id) {
return BaseRespVo.ok();
}
}
@RequestParam
→ 请求参数- 形参通过这个注解获得指定请求参数,如果使用了这个注解,就一定要携带对应的请求参数
eg:
/**
* localhost:8080/demo5/param?username=zs&password=123456
*/
@RequestMapping("param")
public BaseRespVo param(@RequestParam("username") String usernamex,
@RequestParam("password") String passwordy) {
return BaseRespVo.ok();
}
@RequestHeader
→ 请求头- 形参通过这个注解获得指定请求头的值
eg:
/**
* localhost:8080/demo5/header
* <p>
* 构造请求体 X-token:a1b2c3
*/
// @RequestMapping("header")
// public BaseRespVo header(HttpServletRequest request) {
// request.getHeader("X-token");
// return BaseRespVo.ok();
// }
@RequestMapping("header")
public BaseRespVo header(@RequestHeader("X-token") String tokenValue) {
return BaseRespVo.ok();
}
/**
* 也可以使用String[] 来接收,如果使用数组来接收,将字符串根据逗号进行分割,分割为数组
*/
@RequestMapping("header1")
public BaseRespVo header1(@RequestHeader("X-token") String tokenValue,
@RequestHeader("Accept") String[] acceptArray) {
return BaseRespVo.ok();
}
@CookieValue
→ Cookie- 形参通过这个注解获得指定Cookie的值,根据key获得value
eg:
/**
* localhost:8080/demo5/cookie
* 构造请求头 Cookie: username=zs
*/
/**
* 之前JavaEE的方法
* @param request
* @return
*/
@RequestMapping("cookie")
public BaseRespVo cookie(HttpServletRequest request) {
String value = null;
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if("username".equals(cookie.getName())) {
value = cookie.getValue();
}
}
}
return BaseRespVo.ok();
}
@RequestMapping("cookie1")
public BaseRespVo cookie1(@CookieValue("username") String value) {
return BaseRespVo.ok();
}
- 之前JavaEE的方法:
/**
* localhost:8080/demo5/session/put
*/
/**
* 向session里存入数据
* localhost:8080/demo5/session/put?username=zs
*/
@RequestMapping("session/put")
public BaseRespVo putSession(HttpSession httpSession, String username) {
httpSession.setAttribute("username", username);
return BaseRespVo.ok();
}
/**
* 向session里取出数据
* localhost:8080/demo5/session/get
*/
@RequestMapping("session/get")
public BaseRespVo getSession(HttpSession httpSession) {
String value = (String) httpSession.getAttribute("username");
return BaseRespVo.ok();
}
@SessionAttribute
→ Session- 形参通过这个注解获得指定Session的值,根据key获得value
/**
* localhost:8080/demo5/session/put
*/
/**
* 向session里存入数据
* localhost:8080/demo5/session/put?username=zs
*/
@RequestMapping("session/put")
public BaseRespVo putSession(HttpSession httpSession, String username) {
httpSession.setAttribute("username", username);
return BaseRespVo.ok();
}
/**
* 向session里取出数据
* localhost:8080/demo5/session/get
*/
@RequestMapping("session/get")
public BaseRespVo getSession(@SessionAttribute("username") String username) {
return BaseRespVo.ok();
}
静态资源处理
- JavaEE阶段如果将图片放在webapp目录,它会编译到web资源根目录,图片能访问到
- 应用程序整合SpringMVC之后,放在web资源根目录的图片访问不到了,原因是:
- JavaEE阶段,缺省的servlet是default → 这个缺省的servlet做的事情就是根据请求找目录下的静态资源
- SpringMVC这里,缺省的Servlet是DispatcherServlet → localhost:8080/1.jpg找的是DispatcherServlet而不是default
- 解决办法:
- SpringMVC有提供对应的类,提供的是
ResourceHandler
,需要我们自己来配置- 处理静态资源
- 配置其映射范围
- SpringMVC有提供对应的类,提供的是
- 之前的做法:
controller目录下:
@RestController
@RequestMapping("pic/storage")
public class PictureStorageController {
@Autowired
StorageService storageService;
@RequestMapping("fetch/{filename}")
public void fetchImage(HttpServletResponse response,
@PathVariable("filename")String filename) {
storageService.show(response, filename);
}
}
service目录下:
public interface StorageService {
void show(HttpServletResponse response, String filename);
public StorageData create(MultipartFile file) throws IOException;
}
@Service
public class StorageServiceImpl implements StorageService {
@Value("d:/tmp")
String path;
@Override
public StorageData create(MultipartFile file) throws IOException {
// 随机获取一个 UUID,有极小的概率会重复
String uuid = UUID.randomUUID().toString();
String savedFileName = uuid + ".jpeg";
file.transferTo(new File(path, savedFileName));
// http://localhost:8083/wx/storage/fetch/yq5f2kgrno9q1eoyhz1u.jpeg
String urlPrefix = "http://localhost:8080/demo6/wx/storage/fetch/";
String url = urlPrefix + savedFileName;
StorageData storageData = new StorageData(null, savedFileName, file.getOriginalFilename(),
file.getContentType(), file.getSize(), url, new Date(), new Date());
//storageMapper.insert(storageData); // 略,插入到数据库中,并且获得自增的主键封装给id
return storageData;
}
@Override
@SneakyThrows
public void show(HttpServletResponse response, String filename) {
File file = new File(path, filename);
ServletOutputStream outputStream = response.getOutputStream();
FileInputStream fileInputStream = new FileInputStream(file);
int length = 0;
byte[] bytes = new byte[1024];
while ((length = fileInputStream.read()) != -1) {
outputStream.write(bytes, 0, length);
}
outputStream.close();
fileInputStream.close();
}
}
- SpringMVC中的方法
- 注册
ResourceHandler
使用ResourceHandlerRegistry
addResourceHandler
:配置ResourceHandler的映射范围addResourceLocations
:告知当前ResourceHandler你的资源文件处于什么位置**
代表多级任意url- location写的时候注意:最后的位置
/
不要漏掉
- 建议使用文件路径
- 过去应用程序打包的时候打包为
wa
r包,后面我们使用SpringBoot应用 → 应用程序打包的时候是jar
包
- 过去应用程序打包的时候打包为
- 注册
eg:
@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo6.controller")
// WebMvcConfigurer接口:MVC相关的配置要用到这个接口提供的方法和参数
public class WebConfiguration implements WebMvcConfigurer {
/**
* 如果要使用SpringMVC的文件接收,接收MultipartFile的话,
* 需要向容器中注册MultipartResolver组件
* 处理MultipartFile封装过程中,需要使用这个组件
* 而这个组件 → ac.getBean(beanName)
* beanName=multipartResolver → 也就意味着组件名称只能叫multipartResolver
*
* @return
*/
@Bean
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/**
* addResourceHandler():配置的是path
* addResourceLocations():配置的是location
*/
/**
* localhost:8080/demo6/pic/1.jpg
*
* localhost:8080/demo6/pic/2.jpg
*/
/**
* pic/** : 匹配多级任意值
*/
/**
* 如果指定文件路径,需要加上 file: 这样的前缀
*
* location最后要加上一个 /
*/
registry.addResourceHandler("/pic/*").addResourceLocations("file:d:/tmp/");
/**
* 如果指定类加载路径(target/artifactid-version/WEB-INF/classes),要增加classpath:这样的前
* 缀
*/
// 得先进行编译
registry.addResourceHandler("/pic2/**").addResourceLocations("classpath:/");
/**
* 如果指定web资源路径(target/artifactid-version),就不要增加前缀,如果要指定根路径,只写 / 就行
*/
}
}
Filter
- Filter和SpringMVC之间的关系:本质上就是Filter和Servlet之间的关系,执行SpringMVC的核心
DispatcherServlet
的doDispatch
方法之前先去执行的是Filter的doFilter方法 - SpringMVC给我们提供了一个抽象类
OncePerRequestFilter
→ 实现了Filter接口 → 里面包含了doFilter方法的实现(非抽象方法)OncePerRequestFilter
能够保证doFilterInternal
只执行一次- 相当于之前在JavaEE阶段的
doFilter
方法 - 避免因为JavaEE容器的原因导致Filter会执行多次
eg:
- 配置
ApplicationInitialization
@Override
protected Filter[] getServletFilters() {
// 提供Filter的配置 → 告知web应用,你有哪个或者哪些Filter
return new Filter[]{new CustomFilter()};
}
- 配置
filter
目录下的CustomFilter
public class CustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// 相当于之前在JavaEE阶段的doFilter方法
// 避免因为JavaEE容器的原因导致Filter会执行多次
// OncePerRequestFilter能够保证doFilterInternal只执行一次
request.setCharacterEncoding("utf-8");
System.out.println("执行了CustomFilter");
filterChain.doFilter(request, response);
}
}
- SpringMVC有提供一个处理字符集的Filter →
CharacterEncodingFilter
,也是继承了OncePerRequestFilter
eg:
- 配置
ApplicationInitialization
@Override
protected Filter[] getServletFilters() {
// 提供Filter的配置 → 告知web应用,你有哪个或者哪些Filter
// return new Filter[]{new CustomFilter()};
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
// 强制设置utf-8
characterEncodingFilter.setEncoding("utf-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[]{characterEncodingFilter};
}
HandlerInterceptor
- Handler的拦截器,在Handler执行之前做的拦截,是SpringMVC提供的拦截器
- HandlerMapping起作用 →
HandlerExecutionChain
实例 - 每次发送请求:都会生成新的
HandlerExecutionChain
的实例- 封装了
Handler
- 封装了多个
HandlerInterceptor
- 封装了
public class HandlerExecutionChain {
// Handler
private final Object handler;
//多个HandlerInterceptor
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable
private List<HandlerInterceptor> interceptorList;
// 标记(记号)
private int interceptorIndex;
/**
interceptorIndex就是一个标记,标记HandlerInterceptorList或数组中哪一些preHandle返回值为true,
提供的是下标(序号)
*/
}
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
- 配置HandlerInterceptor:
- 实现
HandlerInterceptor
接口preHandle
→ 在Handler执行之前执行的,返回值为boolean
- 如果返回值为true则继续流程
- 如果返回值为false则中断流程;会去执行返回值为true的部分的afterCompletion
- 如果有多个HandlerInterceptor,preHandle方法是正序遍历
Handler
→ 通常就是Controller组件中的Handler方法postHandle
→ 在Handler之后执行的- 如果执行不到Handler(前面的preHandle返回值为false),那么一个postHandle都执行不到
- 如果能够执行到Handler就能够执行到全部的postHandle
- 如果有多个HandlerInterceptor,postHandle方法是倒序遍历
afterCompletion
→ 执行完postHandle、preHandle返回值为false- 执行完postHandle之后执行afterCompletion,能够执行到全部的afterCompletion
- preHandle返回值为false的时候执行afterCompletion的话,执行的是preHandle返回值为true的部分afterCompletion
- 如果有多个HandlerInterceptor,afterCompletion方法是倒序遍历
- 实现
- 配置HandlerInterceptor以及其作用范围
- WebMvcConfigurer接口中的方法:
getInterceptors
- WebMvcConfigurer接口中的方法:
eg:
- 在
WebConfiguration
中配置HandlerInterceptor
@EnableWebMvc // 功能有:@Configuration + 使用SpringMVC的相关配置
@ComponentScan("com.coo1heisenberg.demo7.controller")
// WebMvcConfigurer接口:MVC相关的配置要用到这个接口提供的方法和参数
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* 默认的作用范围是 /**
*/
registry.addInterceptor(new HandlerInterceptor1()).addPathPatterns("/hello");
registry.addInterceptor(new HandlerInterceptor2()).addPathPatterns("/hello/**");
registry.addInterceptor(new HandlerInterceptor3()).addPathPatterns("/goodbye");
registry.addInterceptor(new HandlerInterceptor4()).addPathPatterns("/goodbye/**");
//registry.addInterceptor(new HandlerInterceptor5()) //.addPathPatterns("/**");
}
}
interceptor
目录下的handlerInterceptor1/2/3/4
@Component
public class HandlerInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle1");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle1");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion1");
}
}
@Component
public class HandlerInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle2");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle2");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion2");
}
}
@Component
public class HandlerInterceptor3 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle3");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle3");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion3");
}
}
@Component
public class HandlerInterceptor4 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle4");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle4");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion4");
}
}
Filter与HandlerInterceptor之间的对比:
- 实现的功能都是类似的:
- Filter是在Servlet之前、之后都可以执行一些业务
- HandlerInterceptor是在Handler之前和之后可以执行一些业务
- 如果访问不到对应的Handler,Filter可以执行到;如果没有对应的Handler处理对应的请求,HandlerExecutionChain也为空
HandlerInterceptor
的使用和Filter
不一样 → 容器 → 注册进去,可以维护和其他组件之间的依赖关系(成员变量注入容器中的其他组件) → 取出来的时候其成员变量就已经赋值了
异常处理
- 在Handler中制造异常
- 如果不做异常处理:不友好、有可能泄露信息
- HandlerExceptionResolver
- 处理全局的全部异常
- 返回值为ModelAndView
eg:
@Component
public class CustomHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object handler, // 抛出异常的handler
Exception e) { // handler抛出的异常
ModelAndView modelAndView = new ModelAndView("/exception.jsp");
if (e instanceof ArithmeticException) {
// 算数异常 做特定的处理
}
if (e instanceof NullPointerException) {
// 空指针异常 做特定的处理
}
// 能否响应Json字符串
// httpServletResponse.getWriter().println(jsonStr);
return modelAndView;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>应用程序正在升级维护,请稍后</h1>
</body>
</html>
- @ExceptionHandler → 建议
- 处理的特定类型的异常
- 返回值可以为ModelAndView,也可以为String或Json字符串
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
}
eg:
@ControllerAdvice
@ResponseBody
@RestControllerAdvice // @RestControllerAdvice == @ResponseBody + @ControllerAdvice
public class ExceptionControllerAdvice {
/**
* 下面两个都是响应ModelAndView
* @return
*/
@ExceptionHandler(value = ArithmeticException.class)
public ModelAndView arithmeticExceptionResolveMethod() {
return new ModelAndView("/exception.jsp");
}
@ExceptionHandler(value = ArithmeticException.class)
public String arithmeticExceptionResolveMethod() {
return "/exception.jsp"; // //返回值作为ModelAndView中的视图名
}
/**
* 响应Json字符串
*/
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public BaseRespVo arithmeticExceptionResolveMethod() {
return BaseRespVo.fail("除零异常");
}
}
SpringMVC核心流程
DispatcherServlet
处理我们(几乎)全部请求Controller
组件中的方法处理请求,这也是我们主要开发的内容
流程图
DispatcherServlet和ApplicationContext的关系:
- 方法存在于Controller组件中
- Controller组件存储在容器中 ,容器就是ApplicationContext
- 当我们发送请求的时候,会执行到这些方法;当我们发送请求的时候,DispatcherServlet处理我们全部的请求, DispatcherServlet → 方法
- Handler:处理器,method1、method2、method3其实就是处理器,处理我们的请求,也称之为HandlerMethod(Handler方法)
- DispatcherServlet如果能够找到ApplicationContext(容器),就可以执行到容器中的Controller组件中Handler(方法)