赞
踩
面向微服务的框架 Spring Boot。
在 Spring Boot 方案里,一个网页请求到了服务器后,首先我们进入的是 Java Web 服务器,然后进入到 Spring Boot 应用,最后匹配到某一个 Spring Controller (这其实也是一个 Spring Bean),然后路由到具体某一个 Bean 的方法,执行完后返回结果,输出到客户端来。
Spring Controller 技术有三个核心点:
Spring Controller 本身也是一个 Spring Bean,只是它多提供了 Web 能力。我们只需要在类上提供一个 @Controller 注解
- import org.springframework.stereotype.Controller;
-
- @Controller
- public class HelloControl {
-
-
- }
在 Spring Boot 应用中,一般把网页存放在 src/main/resources/static
目录下。
在 controller 中,会自动加载 static 下的 html 内容。
- import org.springframework.stereotype.Controller;
-
- @Controller
- public class HelloControl {
-
- public String say(){
- return "hello.html";
- }
-
- }
String
return "hello.html"
返回的是 html 文件路径当执行这段代码的时候,Spring Boot 实际加载的是 src/main/resources/static/hello.html
文件。
resouces 属于 classpath 类型的文件,Spring Boot 很强大,自动帮我们做了加载,所以我们只需要写hello.html
即可。
意文件路径使用的是 /
进行分割。
对于 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请求之间的映射关系,从而实现请求的路由和处理。
每个 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请求中的表单数据。
@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是一个模板框架,支持多种格式的动态渲染。
通过模板引擎,可以把 Java 对象数据+模板页面动态的渲染出一个真实的 HTML 页面
- <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 语法。
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>
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>
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>
通过#strings进行逻辑判断和数据处理
检查字符串变量是否为空(或者为 null),在检查之前会先执行 trim() 操作,去掉空格
${#strings.isEmpty(name)}
数组也适用 isEmpty
${#strings.arrayIsEmpty(name)}
集合类也适用 isEmpty
${#strings.listIsEmpty(name)}
检查字符串变量是否包含片段
${#strings.contains(name,'abc')}
${#strings.containsIgnoreCase(name,'abc')}
先忽略大小写字母,然后去判断是否包含指定的字符串
${#strings.startsWith(name,'abc')}
判断字符串是不是以 abc 开头的
${#strings.endsWith(name,'abc')}
判断字符串是不是以 abc 结束的
除了字符串判断语句外,#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)}
忽略大小写后比较两个字符串是否相等
- <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 注解类一样
一般情况下,我们都会把 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 类的属性名要一致哦
@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,系统会按顺序执行校验,任何一条校验触发就会抛出校验错误到上下文中.
- 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";
将数据传递到页面,显示具体的字段的信息,可以结合模型来传递
创建一个实例并传递到模板中
改造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>
通过layout解决模板复用的情况,可以把导航和底部做成布局组件,每个页面套用。
可以使用th:include + th:replace
方案来完成布局的开发
layout。html模板
th:include="::content"其中::content
指的是选择器,这个选择器指的就是加载当前页面的 th:fragment
的值。
当页面渲染的时候,布局会合并 content 这个 fragment 内容一起渲染,
这里指定了布局的名称,这个一旦声明后,页面会被替换成 layout 的内容,记住不要指定布局名称错误哦,这个"layout"
指的是 templates/layout.html
- <div th:fragment="content">
- </div>
fragment是片段的意思,当页面渲染的时候,可以通过选择器指定使用这个片段。在上面 layout.html 文件的 th:include="::content"
指定的就是这个值
问题:
Spring
框架通过解析属性的注解,自动把所需要的 Bean
实例注入到属性中。
@SpringBootApplication
注解的类是启动类,是整个系统的启动入口。
Spring Boot
框架就会默认扫描 fm.douban.app
包(启动类所在的包)及其所有子包(fm.douban.app.*
、fm.douban.app.*.*
)进行解析。
但 fm.douban.service
、fm.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()
打印的内容会输出到什么地方,是不确定的。所以在企业级的项目中,都用日志系统来记录信息。
日志系统的两大优势:
修改 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()
)与日志级别一一对应:
优先级 | 级别 | 方法名 |
---|---|---|
最高 | ERROR | error() |
高 | WARN | warn() |
中 | INFO | info() |
低 | DEBUG | debug() |
如果想输出警告信息就调用 LOG.warn()
方法,类推
配置为 logging.level.root=error
时, warn()
、 info()
、 debug()
三个方法是无效的,都不会在 Console 打印日志内容( 不会报错 哦),只有 error()
可以。
当修改配置为 logging.level.root=warn
后,warn()
自动变的有效,也可以打印日志内容了(高级别 error()
本来就有效),info()
、 debug()
仍然不行。
配置文件格式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的读
为 control
类的方法增加一个 HttpServletRequest
参数,通过 request.getCookies()
取得 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;
- }
为 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
会话机制可以解决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
存放在服务端,没有限制,不过基于服务端的性能考虑也不能放太多的内容。
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
。
- <!-- 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 信息在服务器上的存储仓库。拦截器是一种处理相同逻辑的机制.
拦截器必须实现 HandlerInterceptor
接口。可以在三个点进行拦截:
preHandle()
方法中处理。postHandle()
方法中处理。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。
创建一个类实现 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
请求。
在 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);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。