admin管理员组文章数量:1574959
1. Web 开发
1.1 jar 入门案例
基于 war 的调试、部署都较为麻烦,因此 Spring 官方也建议,采用 jar 方式来开发 web 项目,好处有:
- 仍然通过 main 方法测试运行,开发方便
- 打包打成 jar 包,内嵌 tomcat,无需再安装 tomcat 服务器
- 代价是不再支持 jsp
步骤1:创建模块,打包方式选择 jar
勾选 Spring Web 支持,这与之前一样
步骤2:编写控制器
@Controller
public class MyController {
@RequestMapping("/hello")
@ResponseBody
public String abc() {
System.out.println("进入了控制器");
return "Hello, Spring Boot";
}
}
与前面例子不同的是:
- 新加 @ResponseBody 注解,它的含义是不再去寻找 jsp 视图,而是把控制器方法的返回结果直接作为响应体
步骤3:运行引导类的 main 方法启动程序
步骤4:打开浏览器,输入如下地址访问控制器方法
http://localhost:8080/hello
可以看到,少了 jsp 的拖累,开发的简捷性大大提升
注意
- 作为了解,底层请求都会进入一个统一入口 DispatcherServlet
1.2 进阶
⭐️1) 路径映射
@RequestMapping 用来映射请求路径,除了可以加在方法上以外,还可以同时加在类上,如果类和方法都加了 @RequestMapping,那么最终的请求路径由二者共同决定
例如
@Controller
@RequestMapping("/person")
public class PersonController {
private static final Logger log = LoggerFactory.getLogger(PersonController.class);
@RequestMapping("/save")
@ResponseBody
public String save() {
log.debug("save");
return "Person Save";
}
@RequestMapping("/update")
@ResponseBody
public String update() {
log.debug("update");
return "Person Update";
}
}
- 访问新增用户时,请求路径为
/person/save
- 访问修改用户时,请求路径为
/person/update
当然写成下面也是效果是一样的
@Controller
public class PersonController {
private static final Logger log = LoggerFactory.getLogger(PersonController.class);
@RequestMapping("/person/save")
@ResponseBody
public String save() {
log.debug("save");
return "Person Save";
}
@RequestMapping("/person/update")
@ResponseBody
public String update() {
log.debug("update");
return "Person Update";
}
}
注意
- spring mvc 中的路径映射不如 servlet 那么严格,路径前不加 / 也不会报错
让改动快速生效
加入依赖后
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
可以在类或配置改变后,无需重新启动程序,devtools 会利用 restart 技术让改动快速生效。idea 中触发 restart 的方法如图所示
注解简化
注意到每个方法都需要加 @ResponseBody,这里可以简化为
@RestController
@RequestMapping("/person")
public class PersonController {
private static final Logger log = LoggerFactory.getLogger(PersonController.class);
@RequestMapping("/save")
public String save() {
log.debug("save");
return "Person Save";
}
@RequestMapping("/update")
public String update() {
log.debug("update");
return "Person Update";
}
}
原理是 @RestController 是一个组合注解,同时含有 @Controller 与 @ResponseBody
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
// ...
}
调整端口号
可以调整 Spring Boot 内嵌服务器的端口号
server.port=端口号
⭐️2) 静态资源映射
如果希望使用 html、css、js、图片这样的静态资源,可以把它们放在下面目录结构中的 static 目录下
src
|- main
|- java
|- resources
|- static (此处)
除此以外,静态资源放在下面一些目录,也能够被 Spring Boot 所找到
src
|- main
|- java
|- resources
|- static (此处)
|- public (此处)
|- resources (此处)
|- META-INF
|- resources (此处)
比如浏览器中输入 http://localhost:8080/aaa.html
这个静态资源路径,Spring 会在下面的位置进行搜索
src/main/resources/static/
下找有没有 aaa.htmlsrc/main/resources/public/
下找有没有 aaa.htmlsrc/main/resources/resources/
下找有没有 aaa.htmlsrc/main/resources/META-INF/resources/
下找有没有 aaa.html
再比如浏览器中输入 http://localhost:8080/bbb/aaa.html
这个静态资源路径,Spring 会在下面的位置进行搜索
src/main/resources/static/
下先找 bbb 这个目录,再找内部有没有 aaa.htmlsrc/main/resources/public/
下先找 bbb 这个目录,再找内部有没有 aaa.htmlsrc/main/resources/resources/
下先找 bbb 这个目录,再找内部有没有 aaa.htmlsrc/main/resources/META-INF/resources/
下先找 bbb 这个目录,再找内部有没有 aaa.html
自定义静态资源映射
如果以上位置都不能满足你、或是 url 路径与资源路径不一致,需要编写如下代码
@SpringBootApplication
public class Demo2Application implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/image/**").addResourceLocations("classpath:/pic/");
}
}
这样当浏览器中输入 http://localhost:8080/image/
开头的静态资源时
- 就会到
src/main/resources/pic
下去查找同名资源 classpath:
表示类路径,对 maven 项目而言,对应src/main/resources/
- 也可以映射到磁盘路径,例如:
file:d:/pic/
⭐️3) 处理请求参数
例1
只要请求参数名与方法参数名一一对应,将来请求参数就会被填充至方法参数,并且支持常见的数据类型转换,非常方便
@RestController
public class ParameterController {
private static final Logger log = LoggerFactory.getLogger(ParameterController.class);
@RequestMapping("/param/r1")
public String r1(Integer id, String username) {
String format = String.format("编号:%s 用户名:%s", id, username);
log.debug(format);
return format;
}
}
浏览器通过地址栏发送 GET 请求
http://localhost:8081/param/r1?id=1&username=zhang
输出
[DEBUG] 09:12:22.544 [http-nio-8081-exec-19] c.i.c.c.ParameterController - 编号:1 用户名:zhang
但要注意,如果类型转换出现错误,在没有配置异常处理器时,会出现 400 响应
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTGL3y63-1648028816996)(img/image-20211021174021318.png)]
例2
当请求参数较多时,例如:
如果定义同样数量的方法参数显得非常繁琐,这时可以改用对象接收,请求参数名与对象属性名对应
@RestController
public class ParameterController {
// ...
@RequestMapping("/param/r2")
public String r2(User user) {
String format = String.format("%s", user);
log.debug(format);
return format;
}
}
其中 User 对象
public class User {
private Integer id;
private String username;
private Integer[] luckyNumber;
// 省略 get set toString
}
多值参数
<input type="checkbox" name="luckyNumber" value="1"/>1
<input type="checkbox" name="luckyNumber" value="2"/>2
<input type="checkbox" name="luckyNumber" value="3"/>3
java 这边可以用数组属性或 List 集合属性来接收
例3
还有一个比较有用的注解 @RequestParam 可以用来接收请求参数的默认值,代码
@RestController
public class ParameterController {
// ...
@RequestMapping("/param/r3")
public String r3(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
String format = String.format("页号:%s 每页记录数:%s", page, size);
log.debug(format);
return format;
}
}
4) 文件上传
请求体格式区别
对于 post 请求,其请求体的数据格式可以有多种,常见的有
- application/x-www-form-urlencoded
- multipart/form-data
- application/json(暂时放一下,后面有例子)
那么它们的区别是什么呢?
表单1
<form enctype="application/x-www-form-urlencoded" method="post" action="/upload">
<input type="text" name="username"/>
<input type="file" name="file"/>
<input type="submit" value="提交">
</form>
表单2
<form enctype="multipart/form-data" method="post" action="/upload">
<input type="text" name="username"/>
<input type="file" name="file"/>
<input type="submit" value="提交">
</form>
填写数据一样:
- text 框都填写 zhangsan
- file 框都选择 1.txt 文件,其内容为
hello
用 firefox 浏览器(推荐,因为能看到更详细的请求信息),打开 Web 开发者->网络
面板,分别提交这两个表单(先不管错误,只看请求参数)
表单1结果 - 可以看到格式与 get 请求参数格式一样,都是 名1=值1&名2=值2
表单2结果 - 可以看到表单由一个分隔线划分成了多个部分(这也是 multipart 的由来),可以包含更多的信息
接收 multipart/form-data 数据
对于 application/x-www-form-urlencoded,我们前面的例子中编写的例子都是。那么该如何接收 multipart/form-data 格式的数据呢?
@RestController
public class UploadController {
private static final Logger log = LoggerFactory.getLogger(UploadController.class);
@RequestMapping("/upload")
public String upload(String username, MultipartFile file) throws IOException {
if (file.isEmpty()) {
return "未选中文件";
}
String filename = file.getOriginalFilename();
log.debug("获取原始文件名:{}", filename);
log.debug("文件的后缀名:{}", FilenameUtils.getExtension(filename));
log.debug("文件大小:{}", file.getSize());
log.debug("文件类型:{}", file.getContentType());
log.debug("其他参数:{}", username);
file.transferTo(Paths.get("d:\\", filename));
return "上传成功";
}
}
其中 FilenameUtils 是工具类,用来获取文件扩展名(非必须,可以自己写),需要导入下面依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
5) header 与 cookie 处理
@RestController
public class HeaderCookieController {
private static final Logger log = LoggerFactory.getLogger(HeaderCookieController.class);
@RequestMapping("/headerCookie")
public String getHeaderAndCookie(
@RequestHeader("Accept-Language") String header,
@CookieValue(value = "aaa",required = false) String cookie) {
String format = String.format("header:%s, cookie:%s", header, cookie);
log.debug(format);
return format;
}
}
@RequestHeader 与 @CookieValue 分别可以用来获得请求头与 Cookie 的信息
在浏览器中可以手动设置 cookie:
document.cookie="aaa=abc;domain=localhost"
location.reload();
注意
- 如果对 js 不熟悉,可以改用 postman 来发送自定义 cookie
6) request,response,session 处理
如果要用到 Servlet API 中的 request,response,session 对象,可以把它们当做控制器方法的参数传入
@RestController
public class ServletObjectController {
private static final Logger log = LoggerFactory.getLogger(ServletObjectController.class);
@RequestMapping("/servletObject")
public String getServletObject(
HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
String format = String.format("请求URI: %s,响应状态码: %s,session id: %s",
request.getRequestURI(), response.getStatus(), session.getId());
log.debug(format);
return format;
}
}
⭐️7) 异常处理
回忆一下以前学习的异常处理原则
- dao,service 层向上抛即可
- 但 controller 层不同,它如果再向上抛,此异常必然暴露给最终用户,这是不允许的
处理方式有两种
- 简单的方式就是自己
try ... catch
在 catch 块中返回合适信息 - Spring MVC 还提供了另一种处理异常的方式,如下所示:
@RestController
public class StudentController {
private static final Logger log = LoggerFactory.getLogger(StudentController.class);
@RequestMapping("/student/save")
public String save() throws FileNotFoundException {
log.debug("student save...");
new FileInputStream("aaa");
return "Student save";
}
@RequestMapping("/student/update")
public String update() {
log.debug("student update...");
int i = 1 / 0;
return "Student update";
}
@ExceptionHandler
public String ioException(IOException e) {
log.debug("LocalExceptionHandler ...");
return "错误为:" + e.getMessage();
}
}
其中 @ExceptionHanlder 标注的方法,可以在方法参数处声明需要捕获的异常,本例中
- 访问 /student/save 会走 ioException 方法
- 访问 /student/update 则不会走 ioException 方法,因为异常的类型匹配不上
控制器内未捕获的异常,还可以通过一个全局通知类来处理
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler
public String globalException(Exception e) {
log.debug("GlobalExceptionHandler...");
return "全局错误:" + e.getMessage();
}
}
注意
- @RestControllerAdvice 也是组合注解,相当于 @ControllerAdvice + @ResponseBody
- @ControllerAdvice 标注的类会对所有 Controller 增强,但要注意其底层原理并非 AOP
- 方法参数处的异常类型为 XxxException 可以匹配
- 所有 XxxException 以及 XxxException 的子类异常
- 实际异常对象中,cause 为 XxxException 类型的异常
⭐️8) 拦截器
拦截器会在控制器方法前、后、完成时进行拦截增强,有一点像之前学习过的 Filter 过滤器
使用步骤
- 编写目标 - 代码片段1
- 编写拦截器代码 - 代码片段2
- 配置拦截器 - 代码片段3.1 与 代码片段3.2 二选一
代码片段1
@RestController
public class TeacherController {
private static final Logger log = LoggerFactory.getLogger(TeacherController.class);
@RequestMapping("/teacher/save")
public String save() {
log.debug("teacher save...");
return "Teacher save";
}
@RequestMapping("/teacher/update")
public String update() {
log.debug("teacher update...");
return "Teacher update";
}
}
代码片段2
public class TeacherInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(TeacherInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.debug("preHandle...");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
log.debug("postHandle...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
log.debug("afterCompletion...");
}
}
编写要点
- 拦截器需要实现接口 HandlerInterceptor,但不必实现其全部方法,因为它们默认都是 default 方法
- preHandle 在控制器方法之前被执行,方法返回 true 表示放行,返回 false 表示拦截
- postHandle 在控制器方法之后被执行,如果控制器方法执行出错,则不会调用 postHandle
- afterCompletion 在更后被执行,无论控制器方法执行是否出错,都会调用 afterCompletion
配置要点
- 在引导类添加较为方便,引导类要实现 WebMvcConfigurer 接口并覆盖其 addInterceptors 方法,其中
registry.addInterceptor(拦截器对象)
用来注册拦截器,并为之设置拦截路径 - 由于 addInterceptors 方法是接口中定义的,已经规定死了,不能把拦截器通过方法参数传递进来
- 解决这个问题有两种方法
代码片段3.1:用 @Bean 把拦截器交给 Spring 管理,然后通过方法调用来获得拦截器
@SpringBootApplication
public class Demo2Application implements WebMvcConfigurer {
// ...
@Bean
public TeacherInterceptor teacherInterceptor() {
return new TeacherInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(teacherInterceptor()).addPathPatterns("/teacher/**");
// 注意与下面代码含义不同
// * 下面的拦截器是自己 new 的,不受 spring 管理
// * 而经过 teacherInterceptor() 调用得到的拦截器受到 spring 管理,可以享受 spring 各种特性
// registry.addInterceptor(new TeacherInterceptor()).addPathPatterns("/teacher/**");
}
}
代码片段3.2:用 @Component 或 @Bean 把拦截器交给 Spring 管理,然后在引导类中把拦截器当做成员变量注入进来
@SpringBootApplication
public class Demo2Application implements WebMvcConfigurer {
// ...
@Bean
public TeacherInterceptor teacherInterceptor() {
return new TeacherInterceptor();
}
@Autowired
private TeacherInterceptor teacherInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(teacherInterceptor).addPathPatterns("/teacher/**");
}
}
handler
handler 对象可以获取被拦截对象及方法的信息
public class TeacherInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(TeacherInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.debug("preHandle...");
log.debug("handler的类型是:" + handler.getClass());
if(handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
log.debug("拦截的方法所在的类:" + handlerMethod.getBean().getClass());
log.debug("控制器处理请求的方法名:" + handlerMethod.getMethod().getName());
}
return true;
}
// ...
}
🈵如何拦截
例如要实现如下功能,username 为 admin 允许访问控制器方法,否则拦截
@RestController
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@RequestMapping("/user/save")
public String save(String username) {
log.debug("user save...{}", username);
return "User save";
}
@ExceptionHandler
public String handler(RuntimeException e) {
return "错误:" + e.getMessage();
}
}
方案1 - 在拦截器中抛异常,在控制器里根据异常跳转
public class UserInterceptor1 implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(UserInterceptor1.class);
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.debug("preHandle...");
String username = request.getParameter("username");
if (!"admin".equals(username)) {
log.debug("拦截...");
throw new RuntimeException("没有权限执行此操作,请重新登录");
}
log.debug("放行...");
return true;
}
}
方案2 - 在拦截器里请求转发,返回 false 拦截
public class UserInterceptor2 implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(UserInterceptor2.class);
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException {
log.debug("preHandle...");
String username = request.getParameter("username");
if (!"admin".equals(username)) {
log.debug("拦截...");
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("错误:没有权限执行此操作,请重新登录");
return false;
}
log.debug("放行...");
return true;
}
}
配置时,拦截器实现类从 UserInterceptor1 和 UserInterceptor2 择一即可
@SpringBootApplication
public class Demo2Application implements WebMvcConfigurer {
// ...
@Bean
public UserInterceptor1 userInterceptor1() {
return new UserInterceptor1();
}
@Bean
public UserInterceptor2 userInterceptor2() {
return new UserInterceptor2();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(teacherInterceptor()).addPathPatterns("/teacher/**");
registry.addInterceptor(userInterceptor2()).addPathPatterns("/user/**");
}
}
✒️练习 - web
题目见 exercise1 中的 1~6 题
2. RESTful 开发
⭐️2.1 RESTful 风格
RESTful 是一种软件架构风格,它采用了以上提到的要素来构建网络应用程序
- 设计要访问的资源【名词】,用统一的 URI 表示
- 选择资源的展现的方式:一般是 json 格式,也可以是其它格式
- 用 HTTP Method 【动词】来转换资源状态
例如有一本《神雕侠侣》这个资源,它有唯一 id=10 ,这样设计
获取资源,Accept 一般用来指示我想要的格式
GET /api/books/10 HTTP/1.1
Accept: application/json;charset=utf-8
服务器返回,Content-Type 一般用来指示我提供的格式
HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
{
"id":10,
"title":"神雕侠侣",
"author":"金庸"
}
如果想换一种格式,注意资源 URI 不用变!
GET /api/books/10 HTTP/1.1
Accept: application/xml;charset=utf-8
服务器返回
HTTP/1.1 200 OK
Content-Type: application/xml;charset=utf-8
<book>
<id>10</id>
<title>10</title>
<author>金庸</author>
</book>
更新资源,注意资源 URI 不用变!
PUT /api/books/10 HTTP/1.1
Content-Type: application/json;charset=utf-8
{
"title":"神雕侠侣",
"author":"查良镛"
}
删除资源,注意资源 URI 不用变!
DELETE /api/books/10 HTTP/1.1
新增资源,可以这么干:如果希望服务器生成 id,则用 post,否则用 put
POST /api/books HTTP/1.1
Content-Type: application/json;charset=utf-8
{
"title":"侠客行",
"author":"金庸"
}
注意
- RESTful 是一种风格,并没有什么规范强制你【必须】怎么做,所以常常可以看到一些与 RESTful 理念不符的实例,因开发者而不同,此为正常现象
2.2 开发 RESTful 应用
⭐️1) 设计统一 URI
要实现【设计要访问的资源【名词】,用统一的 URI 表示】这一特性,我们发现 RESTful 风格中,唯一标识 id,并不是像之前一样从请求参数(即 ? 后)传递过来,而是此 id 就是路径的组成部分,因此需要方便的办法获取路径中的参数。
Spring 提供了 @PathVariable 来解析资源路径中的参数信息
例1:当路径参数名与请求参数名一致时
@RestController
public class Controller01 {
private static final Logger log = LoggerFactory.getLogger(Controller01.class);
@RequestMapping("/test1/{id}")
public String test1(@PathVariable int id) {
log.debug("编号:{}", id);
return "test";
}
}
其中 {参数名}
代表路径中变化的部分,比如这里的 {id}
就代表设置了一个 id 参数,可以用来接收
/test1/100
/test1/101
等路径中的 100、101 这些编号值,SpringMVC 会检查标注了 @PathVariable 的方法参数,将这些值传递给同名参数
测试
http://localhost:8080/test1/100
服务器控制台输出
[DEBUG] 08:36:43.731 [http-bio-8080-exec-8] c.i.c.c.Controller01 - 编号:100
注意
RESTful 的路径参数作用与普通?参数名=参数值
没有两样,都是用来获取请求中的信息。区别仅在于风格格式不同,一种是将信息包含在路径里,一种是将信息跟在 ? 号后
例2:当路径参数名与请求参数名不一致时
@RequestMapping("/test2/{sid}")
public String test2(@PathVariable("sid") int id) {
log.debug("编号:{}", id);
return "test";
}
测试
http://localhost:8080/test2/200
服务器输出
[DEBUG] 08:37:47.302 [http-bio-8080-exec-1] c.i.c.c.Controller01 - 编号:200
例3:路径参数可以有多个
@RequestMapping("/test3/{id}/{name}")
public String test3(@PathVariable int id, @PathVariable String name) {
log.debug("编号:{} 用户名:{}", id, name);
return "test";
}
测试
http://localhost:8080/test3/300/zhangsan
服务器输出
[DEBUG] 08:38:33.937 [http-bio-8080-exec-1] c.i.c.c.Controller01 - 编号:300 用户名:zhangsan
注意
当参数比较多时,仍然建议把信息放在请求体中而不是路径里,路径里一般只放关键信息
⭐️2) 区分请求方法
要实现【用 HTTP Method 【动词】来转换资源状态】就需要更方便的办法按 **请求方法(GET POST 等)**来区分请求,访问资源时,增删改查,路径都是同一个(增可能会有不同),这时就需要对路径加以区分,否则就会有歧义:
@RestController
public class Controller02 {
private static final Logger log = LoggerFactory.getLogger(Controller02.class);
@RequestMapping("/users/{id}")
public void get(@PathVariable Integer id) {
log.debug("get...");
}
@RequestMapping("/users/{id}") // 当然新增一般不需指定 id
public void post(@PathVariable Integer id) {
log.debug("post...");
}
}
启动时会报错
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'controller02' method
com.itheima.controller.case05.Controller02#post(Integer)
to { /user/{id}}: There is already 'controller02' bean method com.itheima.controller.case05.Controller02#get(Integer) mapped.
显然必须加以区分,怎么区分呢?可以通过 Request 的不同 Method 来区分。其实想想也是正常,RESTful 不就是建议用 Request Method 来区分对资源的增删改查嘛
代码改成下面这样就可以了
@RestController
public class Controller02 {
private static final Logger log = LoggerFactory.getLogger(Controller02.class);
@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public void get(@PathVariable Integer id) {
log.debug("get...");
}
@RequestMapping(value = "/users/{id}", method = RequestMethod.POST)
public void post(@PathVariable Integer id) {
log.debug("post...");
}
}
但显然有些繁琐,这里 SpringMVC 为我们提供了几个简化(衍生)注解,相信一看就明白
@RestController
public class Controller02 {
private static final Logger log = LoggerFactory.getLogger(Controller02.class);
@GetMapping("/users/{id}")
public void get(@PathVariable Integer id) {
log.debug("get...");
}
@PostMapping("/users/{id}")
public void post(@PathVariable Integer id) {
log.debug("post...");
}
@DeleteMapping("/users/{id}")
public void delete(@PathVariable Integer id) {
log.debug("delete...");
}
@PutMapping("/users/{id}")
public void put(@PathVariable Integer id) {
log.debug("put...");
}
}
顺便把 @DeleteMapping 和 @PutMapping 也加上了
不过又有了新的问题:浏览器表单可以发送 POST、GET 请求,但它不支持 PUT、DELETE 请求,虽然有方法可以让表单通过 POST 模拟后两种,但属于偏门知识,这里就不讲了,正规的、常见方法有三种:
- ajax 可以支持各种请求的发送,但属于前台知识,不是重点
- Spring 提供了 RestTemplate 发送各种请求,底层就是用 java 代码发送 http 请求
- 第三方工具发送请求,底层也都是 http
- 图形界面的有 postman
- 命令行界面的有 curl
postman 基本使用
postman 采用了一个比较旧的版本 6.6.1,够用(其它版本类似),默认安装位置为:
C:\Users\{用户名}\AppData\Local\Postman\app-6.6.1
新建一个 collection
collection 的作用就好比一个文件夹,里面可以存放多个相关的请求,方便管理
接下来创建 request
可以对请求进行各方面设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZesuoE61-1648028816997)(img/15.png)]
⭐️3) @RequestBody
客户端的参数比较多,比较复杂时,常会在请求体中传递过来 json 字符串,而 controller 这边可以用 @RequestBody 将此 json 字符串转换为 java 对象
客户端数据
{"name":"张三", "age":18}
根据参数设计一个用来接收的 java 类
static class Student {
String name;
Integer age;
// 省略 get set 方法
}
方法参数用 @RequestBody 标注
@RestController
public class Controller03 {
@PostMapping("/students")
public void post(@RequestBody Student student) {
log.debug("{}", student);
}
// ...
}
如果客户端数据更复杂一些,一样没问题
[
{"name":"张三", "age":18},
{"name":"李四", "age":20}
]
控制器方法
@RestController
public class Controller03 {
@PutMapping("/students")
public void put(@RequestBody List<Student> students) {
for (Student student : students) {
log.debug("{}", student);
}
}
// ...
}
4) 请求参数校验
请求参数接收完,还有一步重要操作,那就是校验。数据不校验,就可能导致非法、残缺数据入库,严重还可能引起系统漏洞
步骤
- 添加依赖 - 代码片段1
- 对象属性上添加校验规则 - 代码片段2
- 控制器方法改动 - 代码片段3
代码片段1(pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
代码片段2
static class Student {
@NotEmpty
String name;
@Min(18)
Integer age;
// ...
}
校验规则有不同注解进行控制,代码中的
- @NotEmpty 表示该参数不能为
null
,也不能为''
- @Min 用来验证参数是整数,并且最小值不能小于 18
代码片段3
@RestController
public class ValidateController {
private static final Logger log = LoggerFactory.getLogger(ValidateController.class);
@PostMapping("/validate")
public void validate(@Valid @RequestBody Student student, BindingResult result) {
log.debug(student.toString());
if (result.hasErrors()) {
for (FieldError error : result.getFieldErrors()) {
log.debug("{} 验证错误: {}", error.getField(), error.getDefaultMessage());
}
}
}
}
说明
- @Valid 用来标注是哪个对象需要验证
- BindingResult result 用来保存验证结果,获取结果方法见以上代码
- BindingResult result 与被 @Valid 对象必须紧邻
结果1 - 当姓名与年龄都不正确时
2021-11-02 09:03:12.147 DEBUG 30652 --- [nio-8080-exec-2] com.itheima.demo3.Controller06 : Student{name='', age=11}
2021-11-02 09:03:12.147 DEBUG 30652 --- [nio-8080-exec-2] com.itheima.demo3.Controller06 : age 验证错误: 最小不能小于18
2021-11-02 09:03:12.147 DEBUG 30652 --- [nio-8080-exec-2] com.itheima.demo3.Controller06 : name 验证错误: 不能为空
结果2 - 当年龄正确,姓名为空时
2021-11-02 09:03:43.525 DEBUG 30652 --- [nio-8080-exec-3] com.itheima.demo3.Controller06 : Student{name='', age=18}
2021-11-02 09:03:43.525 DEBUG 30652 --- [nio-8080-exec-3] com.itheima.demo3.Controller06 : name 验证错误: 不能为空
⭐️5) @ResponseBody
虽然前面讲到了 RESTful 软件架构风格中,资源可以有多种表现形式,但实际用的最多的还是 json,之前见过的 @ResponseBody 的作用将控制器方法的返回值做相应的转换,再写入响应:
- 如果方法返回的是字符串,这时结果仍被视为普通字符串,即 text/plain
- 如果方法返回的是 Java Bean、List、Map 等,这时结果会转换为 json 字符串,即 application/json
- 方法可以声明为 void,表示响应体没有内容
下面的例子都用到同一个 User 类
public class User {
private Integer id;
private String username;
private String password;
private Date birthday;
// ...
}
例1 - 转换 java bean
@RequestMapping("/json/c1")
@ResponseBody
public User c1() {
return new User(1001, "张三", "123", new Date());
}
访问后返回响应
{
"id": 1001,
"username": "张三",
"password": "123",
"birthday": 1611019823007
}
例2 - Jackson 注解
注意到
- 日期被转换为毫秒值
- 当然这个值用起来也挺方便的,只是可读性差,可以用 @JsonFormat 注解来格式化
- 密码属性被转换成了 json
- 如果转换时,希望某一属性被忽略,可以使用 @JsonIgnore 注解
public class User {
private Integer id;
private String username;
@JsonIgnore
private String password;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date birthday;
// ...
}
访问后响应
HTTP/1.1 200 OK
{
"id": 1001,
"username": "张三",
"birthday": "2021-01-19 09:41:33"
}
注意
- 这几个注解 jackson 类库提供的,如果你将来用其他 json 实现,需要根据实际情况修改
- @JsonFormat 注解也可以用以下全局配置,更为方便
- spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
- spring.jackson.time-zone=GMT+8:00
例3 - 忽略 null 值
@RequestMapping("/json/c2")
@ResponseBody
public User c2() {
return new User(null, "张三", null, null);
}
访问后响应
HTTP/1.1 200 OK
{
"id":null,
"username":"张三",
"birthday":null
}
有时候希望 null 值的属性不要参与转换 json,可以这么做
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
// ...
}
或者通过配置
spring.jackson.default-property-inclusion=non_null
结果变成
HTTP/1.1 200 OK
{
"username":"张三"
}
例4 - 转换 List
@RequestMapping("/json/c4")
@ResponseBody
public List<String> c4() {
return Arrays.asList("张三", "李四");
}
访问后响应
HTTP/1.1 200 OK
[
"张三",
"李四"
]
例5 - 转换 Map
@RequestMapping("/json/c5")
@ResponseBody
public Map<String, String> c5() {
Map<String, String> map = new HashMap<>();
map.put("beijing", "北京");
map.put("shanghai", "上海");
map.put("shenzhen", "深圳");
return map;
}
访问后响应
HTTP/1.1 200 OK
{
"shanghai": "上海",
"shenzhen": "深圳",
"beijing": "北京"
}
可以看到它的转换结果与 java bean 是一样的,但 Map 有缺点:
- Map 类型是弱类型,将来接参数时,key还好说都是 String,但 value 转换时不能控制该转换为何种类型
- Map 上无法方便配合上述注解,对转换 json 时进行控制
6) 响应码与响应头
问题
@RestController
public class Controller06 {
private static final Logger log = LoggerFactory.getLogger(Controller06.class);
@RequestMapping("/rs/c1")
public User c1() {
int i = 1 / 0;
return new User(1001, "张三", "123", new Date());
}
@ExceptionHandler
public Map<String, String> ex(ArithmeticException e) {
Map<String, String> map = new HashMap<>();
map.put("error", e.getMessage());
return map;
}
}
访问后响应
HTTP/1.1 200 OK
{
"error": "/ by zero"
}
虽然也可以,但缺点是,必须解析响应体后才知道出现了错误
@ResponseStatus
可以用 @ResponseStatus 来指定控制器方法或异常处理方法的响应头
修改代码为
@RestController
public class Controller06 {
// ...
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, String> ex(ArithmeticException e) {
Map<String, String> map = new HashMap<>();
map.put("error", e.getMessage());
return map;
}
}
访问后响应
HTTP/1.1 500 Internal Server Error
{
"error": "/ by zero"
}
其实在 RESTful 架构风格中,建议响应状态码应当更精确,例如
响应状态码 | 用法 |
---|---|
200 OK | 查询、更新、删除成功时 |
201 Created | 新增成功时,一般响应中应含有新资源的 URI |
202 Accepted | 请求成功接收,会在未来处理(异步处理) |
204 No Content | 成功但没有响应体时 |
400 Bad Request | 请求有误(例如参数不正确) |
401 Unauthorized | 请求身份验证失败 |
403 Forbidden | 请求身份验证成功,但没有权限 |
404 Not Found | 请求资源不存在 |
405 Method Not Allowed | 请求 Method 不被支持 |
415 Unsupported Media Type | 请求资源的表现形式不支持 |
429 Too Many Requests | 请求次数超过限额 |
500 Internal Server Error | 服务器内部错误,服务仍可用 |
503 Service Unavailable | 服务器已不可用 |
这时候 @ResponseStatus 就可以派上用场了
响应码中还有一个比较重要的 304 这个讲到缓存时再说
ResponseEntity
如果希望控制响应的各个部分:响应码、响应头、响应体,还可以使用 ResponseEntity 类型作为控制器方法的返回值
例如:
@RequestMapping("/rs/c2")
public ResponseEntity<User> c2() {
return ResponseEntity.ok()
.header("My-Header", "yeah")
.body(new User(1001, "张三", "123", new Date()));
}
访问后响应
HTTP/1.1 200 OK
My-Header: yeah
{
"id": 1001,
"username": "张三",
"birthday": "2021-01-19 11:05:38"
}
7) 统一响应格式
- 响应状态码的不足:上一节说到,可以用状态码较为精确地描述此操作是成功失败、是客户端的问题、还是服务端的问题,但状态码毕竟有限,是最为通用的描述,如果牵扯业务,就有些不够用了
- 返回的响应体中,可能会表示正常的数据,也有可能包含错误提示,若不统一,就增加了前端解析成本
因此一般应用开发时,会定义一个 AppResult 统一响应格式
AppResult
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AppResult {
// 应用状态码,对响应状态码算是补充和扩展
private Integer code;
// 应用出错描述
private String msg;
// 应用响应数据
private Object data;
public AppResult(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// ...
}
对应的 json 数据如下
HTTP/1.1 响应状态码
{
"code": "应用状态码",
"msg": "应用出错描述",
"data": "应用响应数据"
}
应用状态码都是应用内自定义的,以豆瓣 api 的为例,根据豆瓣 api 中通用错误码来看,它的这些错误码有两层分类:大类关联响应状态码
400 - 客户端校验错误
401 - 客户端认证失败
403 - 客户端权限错误
404 - 资源不存在
而每大类下又分了多个小类,小类关联应用状态码,例如:
400
999 unknown_v2_error
1002 missing_args 缺失参数
1003 image_too_large 图片太大
1004 has_ban_word 有违禁词
1005 input_too_short
1006 target_not_fount
1008 image_unknow
1009 image_wrong_format
1012 title_missing
1013 desc_missing
403
1001 need_permission 需要权限
1007 need_captcha 需要验证码
1010 image_wrong_ck
1011 image_ck_expired 图片过期
✒️练习 - RESTful
题目见 exercise2 中的 1~7 题
✒️大作业
完成 homework
附录
本章注解
Web 注解
注解名称 | 位置 | 注解作用 | 备注 |
---|---|---|---|
@RequestMapping | 方法 | 映射路径 | |
@RequestMapping | 类 | 为映射路径加统一前缀 | |
@ResponseBody | 方法 | 该控制器方法的返回值即为响应体内容 | |
@ResponseBody | 类 | 影响该类的所有控制器方法 | |
@RestController | 类 | 组合注解 @Controller + @ResponseBody | |
@RequestParam | 参数 | 主要设置请求参数默认值 | |
@RequestHeader | 参数 | 获取请求头,头名称不区分大小写 | |
@CookieValue | 参数 | 获取 cookie 值 | |
@ExceptionHandler | 方法 | 处理控制器异常 | |
@RestControllerAdvice | 类 | 该类用来全局处理控制器异常,@ControllerAdvice + @ResponseBody | |
@ResponseStatus | 方法 | 控制返回响应的状态码 |
JSON 注解
注解名称 | 位置 | 注解作用 | 备注 |
---|---|---|---|
@JsonIgnore | 成员变量 | 转 json 时忽略此成员变量 | |
@JsonFormat | 成员变量 | 转 json 时控制日期格式和时区 | 有等价配置 |
@JsonInclude | 类 | 控制该类取值 null 的成员变量不参与转换 | 有等价配置 |
校验注解
注解名称 | 位置 | 注解作用 | 备注 |
---|---|---|---|
@Valid | 方法参数 | 该参数需要校验 | |
@NotEmpty | 成员变量 | 字符串不能为 null、空、集合不能为空 | |
@Min | 成员变量 | 数字不能小于某个值 | |
@Max | 成员变量 | 数字不能大于某个值 | 扩 |
@NotNull | 成员变量 | 不能为 null | 扩 |
@NotBlank | 成员变量 | 字符串不能为 null、空、不能全为空白字符 | 扩 |
@DecimalMin | 成员变量 | 与 @Min 类似,但可以校验字符串 | 扩 |
@DecimalMax | 成员变量 | 与 @Max 类似,但可以校验字符串 | 扩 |
@Digits | 成员变量 | 校验数字的整数、小数位数 | 扩 |
@Size | 成员变量 | 字符串长度、集合大小 | 扩 |
@Past | 成员变量 | 必须是过去的时间 | 扩 |
@Future | 成员变量 | 必须是未来的时间 | 扩 |
@Pattern | 成员变量 | 必须符合正则表达式 | 扩 |
成员变量 | 必须符合 Email 格式 | 扩 |
备注为【扩】的表示课堂没讲,将来用到时再去了解
本章扩展
war 入门案例
步骤1:创建模块,区别在于打包方式选择 war
接下来勾选 Spring Web 支持
生成的模块,多了一个 ServletInitializer,它的作用是在 Tomcat 启动时,根据它找到 Spring Boot 引导类,初始化 Spring 容器
步骤2:添加如下依赖,因为本项目需要用到 jsp,后期 jsp 用的少,现阶段用它是案例需要
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
步骤3:编写控制器
@Controller
public class MyController {
@RequestMapping("/hello")
public String abc() {
System.out.println("进入了控制器");
return "hello";
}
}
其中
- @Controller 注解表示此类为控制器类
- @RequestMapping("/hello") 用来指定通过 /hello 这个 uri 路径能够找到它标注的方法处理请求
- 返回类型为 String,返回结果代表视图逻辑名称
步骤4:编写 jsp 视图,新建 webapp 目录和一个 hello.jsp 文件,注意文件名与控制器方法返回的视图逻辑名一致
src
|- main
|- java
|- resources
|- webapp
|- hello.jsp
步骤5:配置视图路径,打开 application.properties 文件
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
将来 prefix + 控制器方法返回值 + suffix 即为视图完整路径
步骤6:运行服务器,这回不能用 main 方法来运行了,打开 terminal 窗口,在模块根目录下运行
mvn spring-boot:run
即可启动 tomcat 服务器,并初始化 Spring 容器
步骤7:打开浏览器,输入如下地址访问控制器方法
http://localhost:8080/hello
资源映射
Spring Boot 中与路径映射相关的 bean 叫做 HandlerMapping,默认已配置了如下 5 种:
-
requestMappingHandlerMapping = RequestMappingHandlerMapping
-
welcomePageHandlerMapping = WelcomePageHandlerMapping
-
beanNameHandlerMapping = BeanNameUrlHandlerMapping
-
routerFunctionMapping = RouterFunctionMapping
-
resourceHandlerMapping = SimpleUrlHandlerMapping
其中等号前是该 bean 的名称,等号后是该 bean 的类型,跟目前有关系的有1、2、5
- 其中 1 对应 @RequestMapping 的路径映射
- 2 对应的是欢迎页,会找静态资源中名为 index.html 的资源作为欢迎页
- 5 对应的就是静态资源映射,可以通过调用它的 getUrlMap() 方法来查看它映射了哪些路径
RequestMappingHandlerMapping 会在初始化时扫描所有加了 @RequestMapping 及其衍生注解的类及方法,用作后续请求映射
字符编码过滤器
- POST 请求参数如果有中文,无需特殊设置,这是因为 Spring Boot 已经配置了 org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter
- 对应配置 server.servlet.encoding.charset=UTF-8,默认就是 UTF-8
- 当然,它只影响非 json 格式的数据
文件上传
Spring Boot 能自动处理文件上传,是因为它通过
- org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration 配置了 org.springframework.web.multipart.support.StandardServletMultipartResolver
- 后者用来解析 multipart/form-data 格式的数据
请求处理流程
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,服务器使用了 Spring Boot 或 SSM 技术,其处理流程是类似的:
-
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术,默认映射路径为
/
,即会匹配到所有请求 URL- 在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- DispatcherServlet 作为请求的统一入口,也被称之为前控制器
-
DispatcherServlet 会利用 HandlerMapping 进行路径匹配,找到 @RequestMapping("/hello") 对应的控制器方法
- RequestMappingHandlerMapping 会识别 @RequestMapping,前面提过
- 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
- HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
-
DispatcherServlet 接下来会:
-
调用拦截器的 preHandle 方法
-
使用 HandlerAdapter 准备 HandlerMethod 需要的参数
-
使用 HandlerAdapter 调用 HandlerMethod
-
使用 HandlerAdapter 处理 HandlerMethod 执行返回的结果(统一封装为 ModelAndView)
-
调用拦截器的 postHandle 方法
-
调用拦截器的 afterCompletion 方法
-
-
返回响应
- 标注了 @ResponseBody 的控制器方法,会在 3.4 这一步调用 HttpMessageConverter 来将结果转换为 JSON
- 否则,走 ModelAndView 及视图解析的逻辑
什么是 RESTful
它是 Representational State Transfer(表现层状态转换)的缩写,但要注意的是:它省略了主语 Resources(资源),合在一起,应当是【资源的表现形式及状态转换】,下面逐一解释
1) Resources
资源就是网络上一个实体【名词】,可以是
- 一支专辑
- 一只股票
- 一部电影、一本书
- 一个 github 仓库
- 一场比赛
- 一日天气
每个资源可以被表示为一个 URI
2) Representational
即资源的表现形式,可以是 html,也可以是 json,还可以是一张图片等等,以周董《范特西》这个资源为例
可以是 html 格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJ615JUz-1648028816999)(img/9.png)]
也可以是 json 格式
可以演示 spring_case_mvc_show 这个小案例,案例的 postman 测试数据在资料文件夹中:springmvc restful show.json
用 json 或 xml 作为资源的表现形式,最大的好处是仅返回数据,而数据如何表现交给客户端来完成,这是前后端分离的基础
3) State
所谓【状态】就是一些信息,例如专辑名称《范特西》,专辑作者周董,都可以视为状态
状态分为两类
- 资源自身状态,这些状态存储于服务器端,可以被所有客户端共享
- 例如《范特西》这张专辑信息存储于服务器端,任多少客户端来访问,都是一样的
- 用户状态,这些状态存储于客户端,需要通过请求每次传递给服务器
- 例如张三客户端是VIP,想看一部付费电影,他必须告诉服务器【我是张三】这个信息
- 而李四客户端不是VIP,想看那部付费电影看就看不了
- 服务器根据【用户】这个状态判断该操作能否被执行
4) State Transfer
【转换】也可以从两个角度来理解
资源自身状态的变化,如:
- 资源从无到有,这是一种状态转换【新增】
- 资源信息变更,这是一种状态转换【修改】
- 资源从有到无,这还是一种状态转换【删除】
客户端与服务器端的交互也会发生状态的转换(传输)
服务器URI?username=张三
,这相当于将客户端的状态信息传输至服务器- 随后服务器将资源的状态信息返回给客户端
这些增删改查、可以用 HTTP Method 来表示,分别为
HTTP Method | 说明 | 是否安全 | 是否幂等 | 可否缓存 |
---|---|---|---|---|
GET | 获取资源状态 | 是 | 是 | 是 |
PUT | 修改资源状态 | 否 | 是 | 否 |
DELETE | 删除资源状态 | 否 | 是 | 否 |
POST | 新增或修改资源状态 | 否 | 否 | 是 |
解释
- 安全 - 意味着不会改变服务器资源状态
- 幂等 - 意味着对某一资源【访问多次】,与对此资源【访问一次】结果相同
- 缓存 - 意味着允许客户端存储响应结果,以便将来能重用
Ajax 跨域
参考 资料文件夹中 ajax.html
页面中通过该 axios 里的 get、post、put、delete 方法发送请求,然而,会发现这些请求统统访问不了,比如点击了 get 后,浏览器控制台上会出现如下错误信息
已拦截跨源请求:同源策略禁止读取位于 http://localhost:8080/users/2 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')
这里的请求报错原因是,它的底层是通过 ajax 技术发送的,而浏览器为了保障 ajax 请求的安全性,默认只允许同源请求
同源请求的含义是,必须 ajax 所请求的资源必须与当前页面是:
- 相同协议
- 相同域名
- 相同端口
有一项不一样,就认为是跨源请求(或称之为跨域请求),默认是不允许访问的。说白了,就是默认情况下,只能是ajax 访问自己的网站的其它资源是 ok 的,访问其它网站的资源就得征求该网站的同意了!
现在这种情况下,【客户端】需要发送一个 Origin
头告诉【目标网站】原始域是什么,而【目标网站】返回一个 Access-Control-Allow-Origin
响应头,这个头用来描述【目标网站】究竟允许哪些别的网站来访问,例如
Access-Control-Allow-Origin: *
表示允许所有网站的 ajax 请求访问Access-Control-Allow-Origin: http://www.163
只允许http://www.163
网站发来的 ajax 请求访问Access-Control-Allow-Origin: null
只允许通过双击 html 文件打开的网页,发来的 ajax 请求访问
SpringMVC 可以使用 @CrossOrigin 来标注控制器类或控制器方法来生成必要的 Access-Control-Allow-Origin
响应头:
例1:如果想允许 http://www.163
的 ajax 请求来访问本站资源,做如下设置即可
@CrossOrigin("http://www.163")
@RestController
public class Controller07 {
// ...
}
- 加在类上会影响类中所有方法
- 加在方法上只会影响此方法
例2:如果想允许所有域的 ajax 请求来访问本站资源,可以进行全局配置
@SpringBootApplication
public class HomeworkApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(HomeworkApplication.class, args);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
}
RestTemplate
@SpringBootTest
public class TestRestTemplate {
private String uri = "https://www.tianqiapi/free/day?appid=89955246&appsecret=9JJ8QmZs&unescape=1";
@Autowired
private RestTemplateBuilder builder;
@Test
@DisplayName("发送get请求, 以字符串接收")
public void test1() {
RestTemplate template = builder.build();
// 构造请求:uri 以及请求方法
RequestEntity<Void> entity = RequestEntity.method(HttpMethod.GET, uri).build();
// 发送请求,接收响应,响应类型为 String.class
ResponseEntity<String> response = template.exchange(entity, String.class);
System.out.println(response.getBody());
}
@Test
@DisplayName("发送get请求, 以对象接收")
public void test2() {
RestTemplate template = builder.build();
// 构造请求:uri 以及请求方法
RequestEntity<Void> entity = RequestEntity.method(HttpMethod.GET, uri).build();
// 发送请求,接收响应,响应类型为自定义Java对象
ResponseEntity<Weather> response = template.exchange(entity, Weather.class);
System.out.println(response.getBody());
}
// {"cityid":"101200101","city":"武汉","update_time":"16:57","wea":"多云","wea_img":"yun","tem":"15","tem_day":"18","tem_night":"1","win":"西北风","win_speed":"1级","win_meter":"2km\/h","air":"65"}
static class Weather {
private String cityid;
private String city;
private String updateTime;
private String wea;
private String weaImg;
private String tem;
private String temDay;
private String temNight;
private String win;
private String winSpeed;
private String winMeter;
private String air;
// 省略 get set toString
}
}
做了下面的配置,来实现驼峰下划线转换
spring.jackson.property-naming-strategy=SNAKE_CASE
本章参考
- devtools 使用
- 控制器方法支持的参数类型
- 阮一峰 - 理解 RESTful 架构
- RESTful 释义 - 维基百科中文
- RESTful 释义 - 维基百科英文
- POST 可否缓存响应
- RESTful 的提出者
- 原生校验注解
- 也可以参考 com.itheima.demo3.TestValidator,以单元测试方式演示了常见原生校验注解的使用
- 响应状态码的作用
RESTful API 实例
-
网易云音乐 http://cloud-music.pl-fe
- http://cloud-music.pl-fe/album?id=18915
-
新浪财经
- http://hq.sinajs/list=sh601003,sh601001
- http://image.sinajs/newchart/daily/n/sh601006.gif
-
豆瓣 API https://douban-api-docs.zce.me/
-
github https://docs.github/en/rest/overview
-
极电竞 https://www.jdj007/
-
天气API http://www.tianqiapi/
- 免费 api https://www.tianqiapi/free/day?appid=89955246&appsecret=9JJ8QmZs&unescape=1
-
ElasticSearch REST api
-
MongoDB REST api
注意
- 一些 api 网址会随时间产生变化甚至不可用,请以当时实际情况为准
h",“air”:“65”}
static class Weather {
private String cityid;
private String city;
private String updateTime;
private String wea;
private String weaImg;
private String tem;
private String temDay;
private String temNight;
private String win;
private String winSpeed;
private String winMeter;
private String air;
// 省略 get set toString
}
}
做了下面的配置,来实现驼峰下划线转换
```properties
spring.jackson.property-naming-strategy=SNAKE_CASE
本章参考
- devtools 使用
- 控制器方法支持的参数类型
- 阮一峰 - 理解 RESTful 架构
- RESTful 释义 - 维基百科中文
- RESTful 释义 - 维基百科英文
- POST 可否缓存响应
- RESTful 的提出者
- 原生校验注解
- 也可以参考 com.itheima.demo3.TestValidator,以单元测试方式演示了常见原生校验注解的使用
- 响应状态码的作用
RESTful API 实例
-
网易云音乐 http://cloud-music.pl-fe
- http://cloud-music.pl-fe/album?id=18915
-
新浪财经
- http://hq.sinajs/list=sh601003,sh601001
- http://image.sinajs/newchart/daily/n/sh601006.gif
-
豆瓣 API https://douban-api-docs.zce.me/
-
github https://docs.github/en/rest/overview
-
极电竞 https://www.jdj007/
-
天气API http://www.tianqiapi/
- 免费 api https://www.tianqiapi/free/day?appid=89955246&appsecret=9JJ8QmZs&unescape=1
-
ElasticSearch REST api
-
MongoDB REST api
注意
- 一些 api 网址会随时间产生变化甚至不可用,请以当时实际情况为准
本文标签: SpringMvc
版权声明:本文标题:SpringMVC 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1727772959a1128781.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论