当前位置:   article > 正文

SPring BOOT(框架)

SPring BOOT(框架)

面向微服务的框架 Spring Boot

在 Spring Boot 方案里,一个网页请求到了服务器后,首先我们进入的是 Java Web 服务器,然后进入到 Spring Boot 应用,最后匹配到某一个 Spring Controller (这其实也是一个 Spring Bean),然后路由到具体某一个 Bean 的方法,执行完后返回结果,输出到客户端来。

Spring Controller 技术有三个核心点:

  • Bean 的配置:Controller 注解运用
  • 网络资源的加载:加载网页
  • 网址路由的配置:RequestMapping 注解运用

 1. Controller 注解

Spring Controller 本身也是一个 Spring Bean,只是它多提供了 Web 能力。我们只需要在类上提供一个 @Controller 注解

  1. import org.springframework.stereotype.Controller;
  2. @Controller
  3. public class HelloControl {
  4. }

2.加载网页

在 Spring Boot 应用中,一般把网页存放在 src/main/resources/static 目录下。

在 controller 中,会自动加载 static 下的 html 内容。

  1. import org.springframework.stereotype.Controller;
  2. @Controller
  3. public class HelloControl {
  4. public String say(){
  5. return "hello.html";
  6. }
  7. }
    1. 定义返回类型为 String
    1. return "hello.html" 返回的是 html 文件路径

当执行这段代码的时候,Spring Boot 实际加载的是 src/main/resources/static/hello.html 文件。

 resouces 属于 classpath 类型的文件,Spring Boot 很强大,自动帮我们做了加载,所以我们只需要写hello.html 即可。

意文件路径使用的是 / 进行分割。

3. RequestMapping 注解

对于 Web 服务器来说,必须要实现的一个能力就是解析 URL,并提供资源内容给调用者。这个过程一般我们称为路由。

Spring MVC 完美的支持了路由能力,并且简化了路由配置,只需要在需要提供 Web 访问的 方法上添加一个 @RequestMapping 注解就可以完成配置。

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. @Controller
  4. public class HelloControl {
  5. @RequestMapping("/hello")
  6. public String say(){
  7. return "html/hello.html";
  8. }
  9. }

 一般情况下,我们会把 controller 类存放在 control子包里。

//

@RequestMapping 注释是Spring框架中用来映射HTTP请求和控制器方法的注释。通过在控制器方法上使用 @RequestMapping 注释,可以指定该方法处理的请求路径、HTTP方法、请求参数、请求头等信息。这样,当有符合条件的请求到达时,Spring框架就会调用相应的控制器方法来处理请求。

例如,以下是一个使用 @RequestMapping 注释的控制器方法示例:

@Controller
public class HelloController {
    
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String sayHello() {
        return "hello";
    }
}

在上面的示例中,@RequestMapping(value = "/hello", method = RequestMethod.GET) 指定了该方法处理路径为 "/hello" 的 GET 请求。当有符合条件的 GET 请求到达时,Spring框架会调用 sayHello() 方法来处理请求,并返回字符串 "hello"。

总之,@RequestMapping 注释的作用是定义控制器方法与HTTP请求之间的映射关系,从而实现请求的路由和处理。

Get Request

获取 Http URL 参数

@RequestMapping

每个 Http URL 都可以设定自定义的参数.

https://域名/songlist?id=xxxx
  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.*;
  3. @Controller
  4. public class SongListControl {
  5. @RequestMapping("/songlist")
  6. public String index( @RequestParam("id") String id){
  7. return "html/songList.html";
  8. }
  9. }

  RequestParam 注解的参数"id" 这个值必须要和 URL 的param key一样哦,因为我们在url中定义的是id,所以这里写id。

@RequestParam作用是用于从请求中获取参数的注解,可以用在方法的参数上,指定方法参数与请求参数的映射关系。@RequestParam注解可以指定参数的名称、是否必须、默认值等属性,用于从请求中获取特定名称的参数值。常用于处理GET请求中的查询参数或POST请求中的表单数据。

@GetMapping

@RequestMapping 注解用于解析 URL 请求路径,这个注解默认是支持所有的 Http Method 的。放开所有的 Http Method 这样不是很安全,一般我们还是会明确制定 method,比如说 get 请求

http://xxxx/songlist?id=xxx&pageNum=1

多个参数使用&分隔。

  1. import org.springframework.web.bind.annotation.*;
  2. @GetMapping("/songlist")
  3. public String index(@RequestParam("id") String id,@RequestParam("pageNum") int pageNum){
  4. return "html/songList.html";
  5. }

如果不想某个参数必须传递,那么你可以修改一下参数的注解

  1. @GetMapping("/songlist")
  2. public String index(@RequestParam(name="pageNum",required = false) int pageNum,@RequestParam("id") String id){
  3. return "html/songList.html";
  4. }

 输出JSON数据

  1. @GetMapping("/api/foos")
  2. @ResponseBody
  3. public String getFoos(@RequestParam("id") String id) {
  4. return "ID: " + id;
  5. }

 一般我们会把这种输出JSON数据的方法称为 API。

Thymeleaf

动态页面的开发,Thymeleaf是一个模板框架,支持多种格式的动态渲染。

通过模板引擎,可以把 Java 对象数据+模板页面动态的渲染出一个真实的 HTML 页面

初始化Thymeleaf

添加maven
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  4. </dependency>

 数据传递

Spring MVC 把页面数据层封装的非常完善,只需要我们在方法参数里引入一个Model对象,就可以通过这个 Model 对象传递数据到页面中了。

模板文件

Spring MVC 中对于模板文件是有固定的存放位置,放置在工程的 src/main/resources/templates

Thymeleaf 模板文件也是以 html 作为文件格式的。

  1. @Controller
  2. public class SongListControl {
  3. @Autowired
  4. private SongListService songListService;
  5. @RequestMapping("/songlist")
  6. public String index(@RequestParam("id")String id,Model model){
  7. SongList songList = songListService.get(id);
  8. //传递歌单对象到模板当中
  9. //第一个 songList 是模板中使用的变量名
  10. // 第二个 songList 是当前的对象实例
  11. model.addAttribute("songList",songList);
  12. return "songList";
  13. }
  14. }

模板文件的后缀虽然也是 .html ,大部分内容跟 HTML 文件很像,但因为它放置在 src/main/resources/templates 目录下,而且里面可以写变量:th:text="${...}" ,所以它其实不是 HTML 文件,而是 thymeleaf 模板 。

<html lang="en" xmlns:th="http://www.thymeleaf.org">

xmlns:th="http://www.thymeleaf.org" 的作用是,在写代码时,让软件能识别 thymeleaf 语法。 

Thymeleaf变量

th:text 这个属性就是 Thymeleaf 自定义的 HTML 标签属性,th是Thymeleaf 的缩写.

th:text 语法的作用就是会动态替换掉 html 标签的内部内容.

这段代码的执行结果就是用 msg 变量值替换了 span 标签内的 Hello 字符串.

  1. import org.springframework.ui.Model;
  2. @Controller
  3. public class DemoControl {
  4. @RequestMapping("/demo")
  5. public String index(Model model){
  6. String str = "你好";
  7. model.addAttribute("msg",str);
  8. return "demo";
  9. }
  10. }
  • 第一个参数设置的就是上下文变量名(变量名是可以随便定义)
  • 第二参数设置的是变量值(可以是任意的对象).

对象变量 

模板语言还可以支持复杂对象的输出,我们完全可以使用 . 把属性调用出来

  1. import org.springframework.ui.Model;
  2. @Controller
  3. public class DemoControl {
  4. @RequestMapping("/demo")
  5. public String index(Model model){
  6. SongList songList = new SongList();
  7. songList.setId("0001");
  8. songList.setName("爱你一万年");
  9. model.addAttribute("sl",songList);
  10. return "demo";
  11. }
  12. }
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8" />
  5. </head>
  6. <body>
  7. <span th:text="${sl.id}"></span>
  8. <span th:text="${sl.name}"></span>
  9. </body>
  10. </html>

 Thymeleaf循环语句

th:each 代表的就是循环语句

  1. <ul th:each="song : ${songs}">
  2. <li th:text="${song.name}">歌曲名称</li>
  3. </ul>
  • ${songs} 是从模板上下文中获取 songs 这个变量
  • song 是 ${songs} 变量遍历后的每一个对象
  • ${song.name} 就可以读取遍历中歌曲名称了
  1. <ul th:each="song,it: ${songs}">
  2. <li>
  3. <span th:text="${it.count}"></span>
  4. <span th:text="${song.name}"></span>
  5. </li>
  6. </ul>

 列表需要显示当前行数。

  • it.index

    当前迭代对象的 index(从 0 开始计算),如果想从 0 开始显示行数用这个就可以了

  • it.count

    当前迭代对象的 index(从 1 开始计算),如果显示行数用这个就可以了

  • it.size

    被迭代对象的大小,如果想获取列表长度,用这个就可以了

  • it.current

    当前迭代变量,等同与上面的 song

  • it.even/odd

    布尔值,当前循环是否是偶数/奇数(从 0 开始计算)

  • it.first

    布尔值,当前循环是否是第一个

  • it.last

    布尔值,当前循环是否是最后一个

表达式

Thymeleaf 表达式主要用于两种场景

  • 字符串处理
  • 数据转化
字符串处理 

在 Thymeleaf 中这种显示借助+就可以完成字符串拼接。

<span th:text="'00:00/'+${totalTime}"></span>

也可以用‘|’包围字符串

<span th:text="|00:00/${totalTime}|"></span>
数据转化 

工具类temporals

  1. <dependency>
  2. <groupId>org.thymeleaf.extras</groupId>
  3. <artifactId>thymeleaf-extras-java8time</artifactId>
  4. <version>3.0.4.RELEASE</version>
  5. </dependency>

 dates/temporals

dates 和 temporals 支持的方法是一样的,只是支持的类型不同,dates 支持的是 Date 类,temporals 支持的是 LocalDate 和 LocalDateTime

  1. <p th:text="${#dates.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}"></p>
  2. <p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}"></p>
  1. @RequestMapping("/demo")
  2. public String index(Model model){
  3. Date dateVar = new Date();
  4. model.addAttribute("dateVar",dateVar);
  5. return "demo";
  6. }

Strings 

支持字符串的数据处理.

  • ${#strings.toUpperCase(name)}

    把字符串改成全大写

  • ${#strings.toLowerCase(name)}

    把字符串改成全小写

  • ${#strings.arrayJoin(array,',')}

    把字符串数组合并成一个字符串,并以,连接,比如["a","b"]执行后会变成a,b

  • ${#strings.arraySplit(str,',')}

    把字符串分隔成一个数组,并以,作为分隔符,比如a,b执行后会变成["a","b"];如果abc没有匹配到,执行后会变成["abc"]

  • ${#strings.trim(str)}

    把字符串去空格,左右空格都会去掉

  • ${#strings.length(str)}

    得到字符串的长度,也支持获取集合类的长度

  • ${#strings.equals(str1,str2)}

    比较两个字符串是否相等

  • ${#strings.equalsIgnoreCase(str1,str2)}

    忽略大小写后比较两个字符串是否相等

内联表达式 

可以将变量直接写在HTML中

<span>Hello [[${msg}]]</span>

 Thymeleaf条件语句

th:if,if 表达式的值是 ture 的情况下就会执行渲染.

th:unless 代表的是否定条件,但不满足时会执行。

  1. <div>
  2. <li th:each="user : ${users}">
  3. <span>[[${user.name}]]</span>
  4. <span th:if="${user.sex == 'male'}"></span>
  5. <span th:unless="${user.sex == 'male'}"></span>
  6. </li>
  7. </div>

String逻辑判断

通过#strings进行逻辑判断和数据处理

isEmpty

检查字符串变量是否为空(或者为 null),在检查之前会先执行 trim() 操作,去掉空格

${#strings.isEmpty(name)}

数组也适用 isEmpty

${#strings.arrayIsEmpty(name)}

集合类也适用 isEmpty

${#strings.listIsEmpty(name)}
contains

检查字符串变量是否包含片段

${#strings.contains(name,'abc')}
  • ${#strings.containsIgnoreCase(name,'abc')}

    先忽略大小写字母,然后去判断是否包含指定的字符串

  • ${#strings.startsWith(name,'abc')}

    判断字符串是不是以 abc 开头的

  • ${#strings.endsWith(name,'abc')}

    判断字符串是不是以 abc 结束的

#strings 的字符串操作函数

除了字符串判断语句外,#strings 还支持字符串的数据处理,比如

  • ${#strings.toUpperCase(name)}

    把字符串改成全大写

  • ${#strings.toLowerCase(name)}

    把字符串改成全小写

  • ${#strings.arrayJoin(array,',')}

    把字符串数组合并成一个字符串,并以,连接,比如["a","b"]执行后会变成a,b

  • ${#strings.arraySplit(str,',')}

    把字符串分隔成一个数组,并以,作为分隔符,比如a,b执行后会变成["a","b"];如果abc没有匹配到,执行后会变成["abc"]

  • ${#strings.trim(str)}

    把字符串去空格,左右空格都会去掉

  • ${#strings.length(str)}

    得到字符串的长度,也支持获取集合类的长度

  • ${#strings.equals(str1,str2)}

    比较两个字符串是否相等

  • ${#strings.equalsIgnoreCase(str1,str2)}

    忽略大小写后比较两个字符串是否相等

Thymeleaf表单

  1. <form>
  2. <div>
  3. <label>书的名称:</label>
  4. <input type="text" />
  5. </div>
  6. <div>
  7. <label>书的作者:</label>
  8. <input type="text" />
  9. </div>
  10. <div>
  11. <label>书的描述:</label>
  12. <textarea></textarea>
  13. </div>
  14. <div>
  15. <label>书的编号:</label>
  16. <input type="text" />
  17. </div>
  18. <div>
  19. <label>书的价格:</label>
  20. <input type="text" />
  21. </div>
  22. <div>
  23. <label>书的封面:</label>
  24. <input type="text" />
  25. </div>
  26. <div>
  27. <button type="submit">注册</button>
  28. </div>
  29. </form>
  1. package com.bookstore.control;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.ui.Model;
  4. import org.springframework.web.bind.annotation.*;
  5. @Controller
  6. public class BookControl {
  7. // 当页面访问 http://localhost:8080/book/add.html 时
  8. // 渲染 addBook.html 模板
  9. @GetMapping("/book/add.html")
  10. public String addBookHtml(Model model){
  11. return "addBook";
  12. }
  13. }
  1. package com.bookstore.control;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.ui.Model;
  4. import org.springframework.web.bind.annotation.*;
  5. import java.util.*;
  6. import com.bookstore.model.*;
  7. @Controller
  8. public class BookControl {
  9. //缓存所有书籍数据
  10. private static List<Book> books = new ArrayList<>();
  11. @GetMapping("/book/add.html")
  12. public String addBookHtml(Model model){
  13. return "addBook";
  14. }
  15. @PostMapping("/book/save")
  16. public String saveBook(Book book){
  17. books.add(book);
  18. return "saveBookSuccess";
  19. }
  20. }

 @PostMapping 和 @GetMapping 不同点在于只接收 http method 为 post 请求的数据,它的包路径和 GetMapping 注解类一样

form表单

一般情况下,我们都会把 Html 表单的 method 设置为 post,这样可以保证数据传输安全,这样 Spring Mvc 就需要接收 Post 请求

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  7. <title>添加书籍</title>
  8. </head>
  9. <body>
  10. <h2>添加书籍</h2>
  11. <form action="/book/save" method="POST">
  12. <div>
  13. <label>书的名称:</label>
  14. <input type="text" name="name">
  15. </div>
  16. <div>
  17. <label>书的作者:</label>
  18. <input type="text" name="author">
  19. </div>
  20. <div>
  21. <label>书的描述:</label>
  22. <textarea name="desc"></textarea>
  23. </div>
  24. <div>
  25. <label>书的编号:</label>
  26. <input type="text" name="isbn">
  27. </div>
  28. <div>
  29. <label>书的价格:</label>
  30. <input type="text" name="price">
  31. </div>
  32. <div>
  33. <label>书的封面:</label>
  34. <input type="text" name="pictureUrl">
  35. </div>
  36. <div>
  37. <button type="submit">注册</button>
  38. </div>
  39. </form>
  40. </body>
  41. </html>

需要修改 input 的 name 属性,属性和 Book 类的属性名要一致哦

Java Specification Requests(jsr380)

@Component 是一个注解,用于在Java中标记一个类为Spring组件。被@Component注解标记的类会被Spring容器自动扫描并注册为Spring的一个bean,可以在应用程序中被使用和管理

  1. <dependency>
  2. <groupId>jakarta.validation</groupId>
  3. <artifactId>jakarta.validation-api</artifactId>
  4. <version>2.0.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-validation</artifactId>
  9. </dependency>

 JSR380实际上就是Bean验证的规范,Bean也就是实例化的pojo类,jSR380实际就是Bean Validation2.0 

JSR 380 定义了一些注解用于做数据校验,这些注解可以直接设置在 Bean 的属性上

  • @NotNull

    不允许为 null 对象

  • @AssertTrue

    是否为 true

  • @Size

    约定字符串的长度

  • @Min

    字符串的最小长度

  • @Max

    字符串的最大长度

  • @Email

    是否是邮箱格式

  • @NotEmpty

    不允许为null或者为空,可以用于判断字符串、集合,比如 Map、数组、List

  • @NotBlank

    不允许为 null 和 空格

  1. package com.bookstore.model;
  2. import javax.validation.constraints.*;
  3. public class User {
  4. @NotEmpty(message = "名称不能为 null")
  5. private String name;
  6. @Min(value = 18, message = "你的年龄必须大于等于18岁")
  7. @Max(value = 150, message = "你的年龄必须小于等于150岁")
  8. private int age;
  9. @NotEmpty(message = "邮箱必须输入")
  10. @Email(message = "邮箱不正确")
  11. private String email;
  12. // standard setters and getters
  13. }

校验的注解是可以累加的,如上面的 @Min 和 @Max,系统会按顺序执行校验,任何一条校验触发就会抛出校验错误到上下文中.

mapping
  1. import javax.validation.Valid;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.ui.Model;
  4. import org.springframework.web.bind.annotation.*;
  5. import org.springframework.validation.BindingResult;
  6. import com.bookstore.model.*;
  7. @Controller
  8. public class UserControl {
  9. @GetMapping("/user/add.html")
  10. public String addUser() {
  11. return "user/addUser";
  12. }
  13. }

执行检验 

  1. package com.bookstore.control;
  2. import com.bookstore.model.User;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.validation.BindingResult;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import javax.validation.Valid;
  8. @Controller
  9. public class UserControl {
  10. @GetMapping("/user/add.html")
  11. public String addUser() {
  12. return "user/addUser";
  13. }
  14. @PostMapping("/user/save")
  15. public String saveUser(@Valid User user, BindingResult errors) {
  16. if (errors.hasErrors()) {
  17. // 如果校验不通过,返回用户编辑页面
  18. return "user/addUser";
  19. }
  20. // 校验通过,返回成功页面
  21. return "user/addUserSuccess";
  22. }
  23. }

在第一个参数 user 那,我们添加了参数注解 @Valid,然后我们新增了第二个参数 errors(它的类型是 BindingResult) 

BindingResult 对象的 hasErrors 方法可以用于判断校验成功还是失败

redirect: 这个用于跳转到某一个页面网址,如果是同一个域名,你可以省略域名,直接写 path,比如这里的 /user/list.html

return "redirect:https://www.baidu.com";
Thymeleaf实现数据的传递

将数据传递到页面,显示具体的字段的信息,可以结合模型来传递

创建一个实例并传递到模板中

改造Control

  1. @GetMapping("/user/add.html")
  2. public String addUser(Model model) {
  3. User user = new User();
  4. model.addAttribute("user",user);
  5. return "user/addUser";
  6. }

改造html

增加错误的样式和文案

 form 标签里增加一个th:object="${user}" 属性

th:object 用于替换对象,使用了这个就不需要每次都编写 user.xxx,可以直接操作 xxx 了

  1. <form action="/user/save" th:object="${user}" method="POST">
  2. ...
  3. </form>

 显示错误的状态,我们就得定义一个错误的 css class。

  1. .error {
  2. color: red;
  3. }

动态的管理表单的样式,当有错误时便将标签增加上这个class

th:classappend

通过th :classappend来实现这个目的

  1. <div th:classappend="${#fields.hasErrors('name')} ? 'error' : ''">
  2. </div>

如果错误信息里有 name 字段,上面的代码会生成

  1. <div class="error">
  2. </div>

${#fields.hasErrors('key')} 这个语法是专门为验证场景提供的,这里的 key 就是对象的属性名称,比如 User 对象的 name、age、email 等。

th:errors

th:errors="*{age}"属性,会自动取出错误信息.

<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>

 th:field

显示上一次输入的内容,

  1. <div th:classappend="${#fields.hasErrors('age')} ? 'error' : ''">
  2. <label>年龄:</label>
  3. <input type="text" th:field="*{age}" />
  4. <p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>
  5. </div>
Thymeleaf布局(layout)

通过layout解决模板复用的情况,可以把导航和底部做成布局组件,每个页面套用。

可以使用th:include + th:replace 方案来完成布局的开发

layout。html模板

th:include="::content"其中::content 指的是选择器,这个选择器指的就是加载当前页面的 th:fragment的值。

当页面渲染的时候,布局会合并 content 这个 fragment 内容一起渲染,

th:replace="layout"

这里指定了布局的名称,这个一旦声明后,页面会被替换成 layout 的内容,记住不要指定布局名称错误哦,这个"layout"指的是 templates/layout.html

th:fragment="content"
  1. <div th:fragment="content">
  2. </div>

fragment是片段的意思,当页面渲染的时候,可以通过选择器指定使用这个片段。在上面 layout.html 文件的 th:include="::content" 指定的就是这个值

Spring Boot CompoentScan

问题:

Spring 框架通过解析属性的注解,自动把所需要的 Bean 实例注入到属性中。

@SpringBootApplication 注解的类是启动类,是整个系统的启动入口。

Spring Boot 框架就会默认扫描 fm.douban.app 包(启动类所在的包)及其所有子包(fm.douban.app.*fm.douban.app.*.*)进行解析。

但 fm.douban.servicefm.douban.service.impl 不是 fm.douban.app 的子包(平级),所以不会自动扫描,也不会自动实例化 Bean,自然不会实例化 SongServiceImpl.

解决:

启动类注解 @SpringBootApplication 加一个参数,告知系统需要额外扫描的包:

  1. @SpringBootApplication(scanBasePackages={"fm.douban.app", "fm.douban.service"})
  2. public class AppApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(AppApplication.class, args);
  5. }
  6. }
  • 参数名是:scanBasePackages
  • 参数值是一个字符串数组,用于指定多个需要额外自动扫描的包。需要把所有的待扫描的包的前缀都写入。

 如果不是 Spring Boot 启动类,可以使用独立的注解 @ComponentScan ,作用也是一样的,用于指定多个需要额外自动扫描的包。

  1. @ComponentScan({"fm.service", "fm.app"})
  2. public class SpringConfiguration {
  3. ... ...
  4. }

使用 @RestController 的类,所有方法都不会渲染 Thymeleaf 页面,而是都返回数据。等同于使用 @Controller 的类的方法上添加 @ResponseBody 注解,效果是一样的。

在Spring框架中,@ResponseBody注解用于将方法返回的对象转换为指定格式(如JSON、XML等)的响应体,并将其写入HTTP响应流中。

@postConstruct

@PostConstruct 是javax.annotation包中的一个注解,用于标记一个方法在类被实例化后立即调用。通常用于执行一些初始化操作。

日志系统

在复杂的系统中System.out.println() 打印的内容会输出到什么地方,是不确定的。所以在企业级的项目中,都用日志系统来记录信息。

日志系统的两大优势:

  1. 日志系统可以轻松的控制日志是否输出。例如淘宝这样超大型的网站,在开发阶段需要打印出调试信息,但是发布到正式环境就不能打印了,因为每天几十几百亿的访问量,大量调试信息到导致磁盘撑爆。这时候就需要控制日志的输出,而不是修改所有的代码。
  2. 日志系统可以灵活的配置日志的细节,例如输出格式,通常在日志输出时,需要自动附带输出日志发生的时间、打印日志的类名等信息,这样能很方便的观察日志分析问题。

 配置

修改 Spring Boot 系统的标准配置文件: application.properties(在项目的 src/main/resources/ 目录下),增加日志级别配置:

logging.level.root=info

表示所有日志(root)都为 info 级别。

我们也可以为不同的的包定义不同的级别,例如

logging.level.fm.douban.app=info
优先级级别含义和作用
最高ERROR错误信息日志
WARN暂时不出错但高风险的警告信息日志
INFO一般的提示语、普通数据等不紧要的信息日志
DEBUG进开发阶段需要关注的调试信息日志

级别的作用

 logging.level.root=error 意味着 不输出 更低 优先级的 WARN、INFO、DEBUG 日志, 只输出 ERROR 日志。

 logging.level.root=warn 意味着 不输出 更低 优先级的 INFO、DEBUG 日志, 只输出 WARN 和 更高 优先级的 ERROR 日志。以此类推。

在开发阶段配置为 DEBUG,在项目发布时调整为 INFO 或更高级别,即可做到不改代码而控制只输出关心的日志。

编码

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.web.bind.annotation.RestController;
  4. import javax.annotation.PostConstruct;
  5. @RestController
  6. public class SongListControl {
  7. private static final Logger LOG = LoggerFactory.getLogger(SongListControl.class);
  8. @PostConstruct
  9. public void init(){
  10. LOG.info("SongListControl 启动啦");
  11. }
  12. }

先定义一个类变量 LOG,然后在 LOG.info() 方法的参数中输入日志内容。

info())与日志级别一一对应:

优先级级别方法名
最高ERRORerror()
WARNwarn()
INFOinfo()
DEBUGdebug()

如果想输出警告信息就调用 LOG.warn() 方法,类推

配置为 logging.level.root=error 时, warn() 、 info() 、 debug() 三个方法是无效的,都不会在 Console 打印日志内容( 不会报错 哦),只有 error() 可以。

当修改配置为 logging.level.root=warn 后,warn() 自动变的有效,也可以打印日志内容了(高级别 error() 本来就有效),info() 、 debug() 仍然不行。

Spring Boot Properties

配置文件格式
application.properties 配置文件的格式也很简单。每一行是一条配置项:配置项名称=配置项值

注意:等号两边不要加空格,要写紧凑一些。

为了方便阅读和维护,书写配置文件时推荐遵守如下约定:

  • 配置项名称能准确表达作用、含义,以点 . 分割单词,
  • 相同前缀的配置项写在一起,
  • 不同前缀的配置项之间空一行

配置的意义

配置的主要作用,是把可变的内容从代码中剥离出来,做到在不修改代码的情况下,方便的修改这些可变的或常变的内容。这个过程称之为避免硬编码、做到解耦

自定义配置项

我们可以在 application.properties 配置文件中加入自定义的配置项。

song.name=God is a girl

框架会 自动加载 并 自动解析 整个文件。

那么代码中怎么使用自定义的配置项呢?实际上很简单:

  1. import org.springframework.beans.factory.annotation.Value;
  2. public class SongListControl {
  3. @Value("${song.name}")
  4. private String songName;
  5. }

只需要使用 @Value 注解即可,注意写法,花括号中的配置项名称,与配置文件中保持一致即可

 项目启动的时候,Spring 系统会自动把 application.properties 配置文件中的 song.name 的值,赋值给 SongListControl 对象实例的 songName 变量。

Cookie的使用

服务端既要返回 Cookie 给客户端,也要读取客户端提交的 Cookie

cookie的读

为 control 类的方法增加一个 HttpServletRequest 参数,通过 request.getCookies() 取得 cookie 数组。然后再循环遍历数组即可。

使用注解读取cookie

如果知道 cookie 的名字,就可以通过注解的方式读取

为 control 类的方法增加一个 @CookieValue("xxxx") String xxxx 参数即可,注意使用时要填入正确的 cookie 名字。

  1. import org.springframework.web.bind.annotation.CookieValue;
  2. @RequestMapping("/songlist")
  3. public Map index(@CookieValue("JSESSIONID") String jSessionId) {
  4. Map returnData = new HashMap();
  5. returnData.put("result", "this is song list");
  6. returnData.put("author", songAuthor);
  7. returnData.put("JSESSIONID", jSessionId);
  8. return returnData;
  9. }

写cookie

为 control 类的方法增加一个 HttpServletResponse 参数,调用 response.addCookie() 方法添加 Cookie 实例对象即可。

  1. import javax.servlet.http.Cookie;
  2. import javax.servlet.http.HttpServletResponse;
  3. @RequestMapping("/songlist")
  4. public Map index(HttpServletResponse response) {
  5. Map returnData = new HashMap();
  6. returnData.put("result", "this is song list");
  7. returnData.put("name", songName);
  8. Cookie cookie = new Cookie("sessionId","CookieTestInfo");
  9. // 设置的是 cookie 的域名,就是会在哪个域名下生成 cookie 值
  10. cookie.setDomain("youkeda.com");
  11. // 是 cookie 的路径,一般就是写到 / ,不会写其他路径的
  12. cookie.setPath("/");
  13. // 设置cookie 的最大存活时间,-1 代表随浏览器的有效期,也就是浏览器关闭掉,这个 cookie 就失效了。
  14. cookie.setMaxAge(-1);
  15. // 设置是否只能服务器修改,浏览器端不能修改,安全有保障
  16. cookie.setHttpOnly(false);
  17. response.addCookie(cookie);
  18. returnData.put("message", "add cookie successful");
  19. return returnData;
  20. }

Cookie 类的构造函数,第一个参数是 cookie 名称,第二个参数是 cookie 值。

Session机制

采用 Session 会话机制可以解决cookie不安全的问题,用户ID登录状态等重要信息不存放在客户端,而是存放在服务端,从而避免安全隐患。通讯过程。

使用会话机制时,Cookie 作为 session id 的载体与客户端通信.

名字为 JSESSIONID 的 cookie,是专门用来记录用户session的。JSESSIONID 是标准的、通用的名字。

读操作

从 HttpServletRequest 对象中取得 HttpSession 对象,使用的语句是 request.getSession()

返回结果不是数组,是对象。在 attribute 属性中用 key -> value 的形式存储多个数据。

假设存储登录信息的数据 key 是 userLoginInfo,那么语句就是 session.getAttribute("userLoginInfo")

登录信息类

登录信息实例对象因为要在网络上传输,就必须实现序列化接口 Serializable 

  1. import javax.servlet.http.HttpServletRequest;
  2. import javax.servlet.http.HttpServletResponse;
  3. import javax.servlet.http.HttpSession;
  4. @RequestMapping("/songlist")
  5. public Map index(HttpServletRequest request, HttpServletResponse response) {
  6. Map returnData = new HashMap();
  7. returnData.put("result", "this is song list");
  8. // 取得 HttpSession 对象
  9. HttpSession session = request.getSession();
  10. // 读取登录信息
  11. UserLoginInfo userLoginInfo = (UserLoginInfo)session.getAttribute("userLoginInfo");
  12. if (userLoginInfo == null) {
  13. // 未登录
  14. returnData.put("loginInfo", "not login");
  15. } else {
  16. // 已登录
  17. returnData.put("loginInfo", "already login");
  18. }
  19. return returnData;
  20. }

写操作 

写入登录信息就用 setAttribute() 方法。

  1. import javax.servlet.http.HttpServletRequest;
  2. import javax.servlet.http.HttpServletResponse;
  3. import javax.servlet.http.HttpSession;
  4. @RequestMapping("/loginmock")
  5. public Map loginMock(HttpServletRequest request, HttpServletResponse response) {
  6. Map returnData = new HashMap();
  7. // 假设对比用户名和密码成功
  8. // 仅演示的登录信息对象
  9. UserLoginInfo userLoginInfo = new UserLoginInfo();
  10. userLoginInfo.setUserId("12334445576788");
  11. userLoginInfo.setUserName("ZhangSan");
  12. // 取得 HttpSession 对象
  13. HttpSession session = request.getSession();
  14. // 写入登录信息
  15. session.setAttribute("userLoginInfo", userLoginInfo);
  16. returnData.put("message", "login successful");
  17. return returnData;
  18. }

Cookie 存放在客户端,一般不能超过 4kb ,要特别注意,放太多的内容会导致出错;而 Session 存放在服务端,没有限制,不过基于服务端的性能考虑也不能放太多的内容。

Spring Session会话

application.properties 是 SpringBoot 的标准配置文件,配置一些简单的属性。

SpringBoot 也提供了编程式的配置方式,主要用于配置 Bean 。

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. @Configuration
  4. public class SpringHttpSessionConfig {
  5. @Bean
  6. public TestBean testBean() {
  7. return new TestBean();
  8. }
  9. }

在类上添加 @Configuration 注解,就表示这是一个配置类,系统会自动扫描并处理。

在方法上添加 @Bean 注解,表示把此方法返回对象实例注册成 Bean

session配置
  1. <!-- spring session 支持 -->
  2. <dependency>
  3. <groupId>org.springframework.session</groupId>
  4. <artifactId>spring-session-core</artifactId>
  5. </dependency>

在类上额外添加一个注解:@EnableSpringHttpSession ,开启 session 

@EnableSpringHttpSession 注解用于启用Spring Session框架来管理HttpSession。通过在Spring配置类上添加@EnableSpringHttpSession注解,可以让Spring Session框架来替代Servlet容器默认的HttpSession实现,提供更多的功能和灵活性。

在使用@EnableSpringHttpSession注解后,可以配置Spring Session的相关属性,如存储方式、超时时间等。这样可以实现在分布式环境中共享Session数据,提高系统的可伸缩性和可靠性

  1. import org.springframework.session.MapSessionRepository;
  2. import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
  3. import org.springframework.session.web.http.CookieSerializer;
  4. import org.springframework.session.web.http.DefaultCookieSerializer;
  5. import java.util.concurrent.ConcurrentHashMap;
  6. @Configuration
  7. @EnableSpringHttpSession
  8. public class SpringHttpSessionConfig {
  9. @Bean
  10. public CookieSerializer cookieSerializer() {
  11. DefaultCookieSerializer serializer = new DefaultCookieSerializer();
  12. serializer.setCookieName("JSESSIONID");
  13. // 用正则表达式配置匹配的域名,可以兼容 localhost、127.0.0.1 等各种场景
  14. serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
  15. serializer.setCookiePath("/");
  16. serializer.setUseHttpOnlyCookie(false);
  17. // 最大生命周期的单位是秒
  18. serializer.setCookieMaxAge(24 * 60 * 60);
  19. return serializer;
  20. }
  21. // 当前存在内存中
  22. @Bean
  23. public MapSessionRepository sessionRepository() {
  24. return new MapSessionRepository(new ConcurrentHashMap<>());
  25. }
  • CookieSerializer:读写 Cookies 中的 SessionId 信息
  • MapSessionRepository:Session 信息在服务器上的存储仓库。

Spring Request拦截器(HandlerInterceptor)

 拦截器是一种处理相同逻辑的机制.

1.创建拦截器

拦截器必须实现 HandlerInterceptor 接口。可以在三个点进行拦截:

  1. Controller方法执行之前。这是最常用的拦截点。例如是否登录的验证就要在 preHandle() 方法中处理。
  2. Controller方法执行之后。例如记录日志、统计方法执行时间等,就要在 postHandle() 方法中处理。
  3. 整个请求完成后。不常用的拦截点。例如统计整个请求的执行时间的时候用,在 afterCompletion 方法中处理。

  1. import javax.servlet.http.HttpServletRequest;
  2. import javax.servlet.http.HttpServletResponse;
  3. import org.springframework.web.servlet.HandlerInterceptor;
  4. import org.springframework.web.servlet.ModelAndView;
  5. public class InterceptorDemo implements HandlerInterceptor {
  6. // Controller方法执行之前
  7. @Override
  8. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  9. // 只有返回true才会继续向下执行,返回false取消当前请求
  10. return true;
  11. }
  12. //Controller方法执行之后
  13. @Override
  14. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
  15. ModelAndView modelAndView) throws Exception {
  16. }
  17. // 整个请求完成后(包括Thymeleaf渲染完毕)
  18. @Override
  19. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  20. }
  21. }

preHandle() 方法的参数中有 HttpServletRequest 和 HttpServletResponse,可以像 control 中一样使用 Session。

 2.管理拦截器(WebMvcConfigurer)

创建一个类实现 WebMvcConfigurer,并实现 addInterceptors() 方法。这个步骤用于管理拦截器。

注意:实现类要加上 @Configuration 注解,让框架能自动扫描并处理。

 管理拦截器,比较重要的是为拦截器设置拦截范围。常用 addPathPatterns("/**") 表示拦截所有的 URL .

也可以调用 excludePathPatterns() 方法排除某些 URL.

  1. import org.springframework.context.annotation.Configuration;
  2. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  3. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  4. @Configuration
  5. public class WebAppConfigurerDemo implements WebMvcConfigurer {
  6. @Override
  7. public void addInterceptors(InterceptorRegistry registry) {
  8. // 多个拦截器组成一个拦截器链
  9. // 仅演示,设置所有 url 都拦截
  10. registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**");
  11. }
  12. }

通常拦截器,会放在一个包(例如interceptor)里。而用于管理拦截器的配置类,会放在另一个包(例如config)里。

请求

从服务端获取数据,是 GET 请求。向服务端提交或写入数据,是 POST 请求。

post请求

在 control 中使用 @PostMapping 注解把方法定义为 POST 请求

  1. import org.springframework.web.bind.annotation.PostMapping;
  2. @PostMapping(path = "/authenticate")
  3. public String loginAction(@RequestParam String name, @RequestParam String password) {
  4. return user.toString();
  5. }

 页面 跳转

  1. String loginPageUrl = "/login";
  2. response.sendRedirect(loginPageUrl);
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号