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 注解

import org.springframework.stereotype.Controller;

@Controller
public class HelloControl {


}

2.加载网页

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

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

import org.springframework.stereotype.Controller;

@Controller
public class HelloControl {

    public String say(){
        return "hello.html";
    }

}
    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 注解就可以完成配置。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloControl {

    @RequestMapping("/hello")
    public String say(){
        return "html/hello.html";
    }

}

 一般情况下,我们会把 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
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class SongListControl {

    @RequestMapping("/songlist")
    public String index( @RequestParam("id") String id){
        return "html/songList.html";
    }

}

  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

多个参数使用&分隔。

import org.springframework.web.bind.annotation.*;


@GetMapping("/songlist")
public String index(@RequestParam("id") String id,@RequestParam("pageNum") int pageNum){
  return "html/songList.html";
}

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

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

 输出JSON数据

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam("id") String id) {
  return "ID: " + id;
}

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

Thymeleaf

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

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

初始化Thymeleaf

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

 数据传递

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

模板文件

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

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

@Controller
public class SongListControl {

  @Autowired
  private SongListService songListService;

  @RequestMapping("/songlist")
  public String index(@RequestParam("id")String id,Model model){

    SongList songList = songListService.get(id);
    //传递歌单对象到模板当中
    //第一个 songList 是模板中使用的变量名
    // 第二个 songList 是当前的对象实例
    model.addAttribute("songList",songList);

    return "songList";
  }
}

模板文件的后缀虽然也是 .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 字符串.

import org.springframework.ui.Model;

@Controller
public class DemoControl {

  @RequestMapping("/demo")
  public String index(Model model){
    String str = "你好";
    model.addAttribute("msg",str);
    return "demo";
  }
}
  • 第一个参数设置的就是上下文变量名(变量名是可以随便定义)
  • 第二参数设置的是变量值(可以是任意的对象).

对象变量 

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

import org.springframework.ui.Model;

@Controller
public class DemoControl {

  @RequestMapping("/demo")
  public String index(Model model){
    
    SongList songList = new SongList();
    songList.setId("0001");
    songList.setName("爱你一万年");

    model.addAttribute("sl",songList);
    return "demo";
  }
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <span th:text="${sl.id}"></span>
    <span th:text="${sl.name}"></span>
  </body>
</html>

 Thymeleaf循环语句

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

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

<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-java8time</artifactId>
  <version>3.0.4.RELEASE</version>
</dependency>

 dates/temporals

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

<p th:text="${#dates.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}"></p>
<p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}"></p>
  @RequestMapping("/demo")
  public String index(Model model){

    Date dateVar = new Date();

    model.addAttribute("dateVar",dateVar);
    return "demo";
  }

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 代表的是否定条件,但不满足时会执行。

<div>
  <li th:each="user : ${users}">
    <span>[[${user.name}]]</span>
    <span th:if="${user.sex == 'male'}">男</span>
    <span th:unless="${user.sex == 'male'}">女</span>
  </li>
</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表单

<form>
  <div>
    <label>书的名称:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的作者:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的描述:</label>
    <textarea></textarea>
  </div>
  <div>
    <label>书的编号:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的价格:</label>
    <input type="text" />
  </div>
  <div>
    <label>书的封面:</label>
    <input type="text" />
  </div>
  <div>
    <button type="submit">注册</button>
  </div>
</form>
package com.bookstore.control;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
public class BookControl {
  // 当页面访问 http://localhost:8080/book/add.html 时
  // 渲染 addBook.html 模板
  @GetMapping("/book/add.html")
  public String addBookHtml(Model model){
    return "addBook";
  }
}
package com.bookstore.control;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.*;

import com.bookstore.model.*;

@Controller
public class BookControl {
  //缓存所有书籍数据
  private static List<Book> books = new ArrayList<>();

  @GetMapping("/book/add.html")
  public String addBookHtml(Model model){
    return "addBook";
  }

  @PostMapping("/book/save")
  public String saveBook(Book book){
    books.add(book);
    return "saveBookSuccess";
  }

}

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

form表单

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

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

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>添加书籍</title>
</head>

<body>
  <h2>添加书籍</h2>
  <form action="/book/save" method="POST">
    <div>
      <label>书的名称:</label>
      <input type="text" name="name">
    </div>
    <div>
      <label>书的作者:</label>
      <input type="text" name="author">
    </div>
    <div>
      <label>书的描述:</label>
      <textarea name="desc"></textarea>
    </div>
    <div>
      <label>书的编号:</label>
      <input type="text" name="isbn">
    </div>
    <div>
      <label>书的价格:</label>
      <input type="text" name="price">
    </div>
    <div>
      <label>书的封面:</label>
      <input type="text" name="pictureUrl">
    </div>
    <div>
      <button type="submit">注册</button>
    </div>
  </form>
</body>

</html>

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

Java Specification Requests(jsr380)

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

<dependency>
  <groupId>jakarta.validation</groupId>
  <artifactId>jakarta.validation-api</artifactId>
  <version>2.0.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</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 和 空格

package com.bookstore.model;

import javax.validation.constraints.*;

public class User {

    @NotEmpty(message = "名称不能为 null")
    private String name;

    @Min(value = 18, message = "你的年龄必须大于等于18岁")
    @Max(value = 150, message = "你的年龄必须小于等于150岁")
    private int age;

    @NotEmpty(message = "邮箱必须输入")
    @Email(message = "邮箱不正确")
    private String email;
 
    // standard setters and getters 
}

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

mapping
import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.BindingResult;
import com.bookstore.model.*;

@Controller
public class UserControl {

  @GetMapping("/user/add.html")
  public String addUser() {
    return "user/addUser";
  }

}

执行检验 

package com.bookstore.control;

import com.bookstore.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import javax.validation.Valid;

@Controller
public class UserControl {

    @GetMapping("/user/add.html")
    public String addUser() {
        return "user/addUser";
    }

    @PostMapping("/user/save")
    public String saveUser(@Valid User user, BindingResult errors) {
        if (errors.hasErrors()) {
            // 如果校验不通过,返回用户编辑页面
            return "user/addUser";
        }
        // 校验通过,返回成功页面
        return "user/addUserSuccess";
    }

}

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

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

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

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

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

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

改造Control

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

改造html

增加错误的样式和文案

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

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

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

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

.error {
  color: red;
}

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

th:classappend

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

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

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

<div class="error">
</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

显示上一次输入的内容,

<div th:classappend="${#fields.hasErrors('age')} ? 'error' : ''">
  <label>年龄:</label>
  <input type="text" th:field="*{age}" />
  <p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>
</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"
<div th:fragment="content">
</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 加一个参数,告知系统需要额外扫描的包:

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

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

@ComponentScan({"fm.service", "fm.app"})
public class SpringConfiguration {
  ... ...
}

使用 @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 或更高级别,即可做到不改代码而控制只输出关心的日志。

编码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;

@RestController
public class SongListControl {
    private static final Logger LOG = LoggerFactory.getLogger(SongListControl.class);

    @PostConstruct
    public void init(){
        LOG.info("SongListControl 启动啦");
    }
}

先定义一个类变量 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

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

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

import org.springframework.beans.factory.annotation.Value;

public class SongListControl {
    @Value("${song.name}")
    private String songName;
}

只需要使用 @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 名字。

import org.springframework.web.bind.annotation.CookieValue;

@RequestMapping("/songlist")
public Map index(@CookieValue("JSESSIONID") String jSessionId) {
  Map returnData = new HashMap();
  returnData.put("result", "this is song list");
  returnData.put("author", songAuthor);
  returnData.put("JSESSIONID", jSessionId);

  return returnData;
}

写cookie

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

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

@RequestMapping("/songlist")
public Map index(HttpServletResponse response) {
  Map returnData = new HashMap();
  returnData.put("result", "this is song list");
  returnData.put("name", songName);

  Cookie cookie = new Cookie("sessionId","CookieTestInfo");
  // 设置的是 cookie 的域名,就是会在哪个域名下生成 cookie 值
  cookie.setDomain("youkeda.com");
  // 是 cookie 的路径,一般就是写到 / ,不会写其他路径的
  cookie.setPath("/");
  // 设置cookie 的最大存活时间,-1 代表随浏览器的有效期,也就是浏览器关闭掉,这个 cookie 就失效了。
  cookie.setMaxAge(-1);
  // 设置是否只能服务器修改,浏览器端不能修改,安全有保障
  cookie.setHttpOnly(false);
  response.addCookie(cookie);

  returnData.put("message", "add cookie successful");
  return returnData;
}

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 

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RequestMapping("/songlist")
public Map index(HttpServletRequest request, HttpServletResponse response) {
  Map returnData = new HashMap();
  returnData.put("result", "this is song list");

  // 取得 HttpSession 对象
  HttpSession session = request.getSession();
  // 读取登录信息
  UserLoginInfo userLoginInfo = (UserLoginInfo)session.getAttribute("userLoginInfo");
  if (userLoginInfo == null) {
    // 未登录
    returnData.put("loginInfo", "not login");
  } else {
    // 已登录
    returnData.put("loginInfo", "already login");
  }

  return returnData;
}

写操作 

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RequestMapping("/loginmock")
public Map loginMock(HttpServletRequest request, HttpServletResponse response) {
  Map returnData = new HashMap();

  // 假设对比用户名和密码成功
  // 仅演示的登录信息对象
  UserLoginInfo userLoginInfo = new UserLoginInfo();
  userLoginInfo.setUserId("12334445576788");
  userLoginInfo.setUserName("ZhangSan");
  // 取得 HttpSession 对象
  HttpSession session = request.getSession();
  // 写入登录信息
  session.setAttribute("userLoginInfo", userLoginInfo);
  returnData.put("message", "login successful");

  return returnData;
}

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

Spring Session会话

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

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringHttpSessionConfig {
  @Bean
  public TestBean testBean() {
    return new TestBean();
  }
}

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

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

session配置
<!-- spring session 支持 -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-core</artifactId>
</dependency>

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

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

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

import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

import java.util.concurrent.ConcurrentHashMap;

@Configuration
@EnableSpringHttpSession
public class SpringHttpSessionConfig {
  @Bean
  public CookieSerializer cookieSerializer() {
    DefaultCookieSerializer serializer = new DefaultCookieSerializer();
    serializer.setCookieName("JSESSIONID");
    // 用正则表达式配置匹配的域名,可以兼容 localhost、127.0.0.1 等各种场景
    serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
    serializer.setCookiePath("/");
    serializer.setUseHttpOnlyCookie(false);
    // 最大生命周期的单位是秒
    serializer.setCookieMaxAge(24 * 60 * 60);
    return serializer;
  }

  // 当前存在内存中
  @Bean
  public MapSessionRepository sessionRepository() {
    return new MapSessionRepository(new ConcurrentHashMap<>());
  }
  • CookieSerializer:读写 Cookies 中的 SessionId 信息
  • MapSessionRepository:Session 信息在服务器上的存储仓库。

Spring Request拦截器(HandlerInterceptor)

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

1.创建拦截器

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

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class InterceptorDemo implements HandlerInterceptor {

  // Controller方法执行之前
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // 只有返回true才会继续向下执行,返回false取消当前请求
    return true;
  }

  //Controller方法执行之后
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      ModelAndView modelAndView) throws Exception {

  }

  // 整个请求完成后(包括Thymeleaf渲染完毕)
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

  }
}

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

 2.管理拦截器(WebMvcConfigurer)

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

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

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

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

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebAppConfigurerDemo implements WebMvcConfigurer {

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 多个拦截器组成一个拦截器链
    // 仅演示,设置所有 url 都拦截
    registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**");
  }
}

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

请求

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

post请求

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

import org.springframework.web.bind.annotation.PostMapping;

@PostMapping(path = "/authenticate")
public String loginAction(@RequestParam String name, @RequestParam String password) {
  return user.toString();
}

 页面 跳转

String loginPageUrl = "/login";
response.sendRedirect(loginPageUrl);
  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值