课堂笔记
起步依赖 + 自动配置
在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率。为此,Spring Boot框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,而无需手动重启项目
对测试的支持
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
@RunWith(SpringRunner.class) @SpringBootTest class SpringbootDemoApplicationTests { @Autowired private DemoController demoController; @Test void contextLoads() { String demo = demoController.demo(); System.out.println(demo); } }
热部署
演示:
1.添加spring-boot-devtools热部署依赖启动器
在Spring Boot项目进行热部署测试之前,需要先在项目的pom.xml文件中添加spring-boot-devtools热部署依赖启动器:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
由于使用的是IDEA开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对IDEA开发工具进行热部署相关的功能设置.
全局配置文件
全局配置文件能够对一些默认配置 值进行修改。Spring Boot使用application.properties或者 application.yaml的文件作为全局配置文件,该文件存放在src/main/resource目录或者类路径的/config,一般会选择resource目录。接下来,将针对这两种全局配置文件进行讲解:
application.properties配置文件
使用Spring Initializr方式构建Spring Boot项目时,会在resource目 录下自动生成一个空的application.properties文件,Spring Boot项目启动时会自动加载
application.properties文件。我们可以在application.properties文件中定义Spring Boot项目的相关属性,当然,这些相关属性可以是系统属性、环境变量、命令参数等信息,也可以是自定义配置文件名称和位置.
编写 application. properties配置文件时,由于要配置的 Person对象属性是我们自定义的, Spring Boot无法自动识别,所以不会有任何书写提示。在实际开发中,为了出现代码提示的效果来方便配置,在使用@Configuration Properties注解进行配置文件属性值注入时,可以在pom.xml文件中添 Spring加一个
Boot提供的配置处理器依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
application.yaml 配置文件
YAML文件格 Spring式是 Boot支持的一种JSON超集文件格式,相较于传统的 Properties配置文件, YAML文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式。 application.yaml配置 文件的工作原理和 application. properties是一样的,只不过yaml格式配置文件看起来更简洁一些。
YAML文件的扩展名可以使用 yml 或者 yaml
application.yml文件使用 “key:(空格)value" 格式配置属性,使用缩进控制层级关系。
这里,针对不同数据类型的属性值,介绍一下 YAML
(1)value值为普通数据类型(例如数字、字符串、布尔等)
当YAML配置文件中配置的属性值为普通数据类型时,可以直接配置对应的属性值,同时对于字符串类型的属性值,不需要额外添加引号,示例代码如下
server: port: 8081 path: /hello
(2)value值为数组和单列集合
当YAML配置文件中配置的属性值为数组或单列集合类型时,主要有两种书写方式:缩进式写法和行内
式写法。
其中,缩进式写法还有两种表示形式,示例代码如下
person: hobby: - play - read - sleep person: hobby: play, read, sleep
person: hobby: [play,read,sleep]
(3)value值为Map集合和对象
当YAML配置文件中配置的属性值为Map集合或对象类型时,YAML配置文件格式同样可以分为两种书写
方式:缩进式写法和行内式写法。
其中,缩进式写法的示例代码如下
person: map: k1: v1 k2: v2
person: map: {k1: v1,k2: v2}
使用@ConfigurationProperties注入属性
Spring Boot提供的@ConfigurationProperties注解用来快速、方便地将配置文件中的自定义属性值批
量注入到某个Bean对象的多个对应属性中。假设现在有一个配置文件,如果使用
@ConfigurationProperties注入配置文件的属性,示例代码如下:
@Component @ConfigurationProperties(prefix = "person") public class Person { private int id; // 需要提供对应的 set 方法 public void setId(int id) { this.id = id; }
@Value注解是Spring框架提供的,用来读取配置文件中的属性值并逐个注入到Bean对象的对应属性
中,Spring Boot框架从Spring框架中对@Value注解进行了默认继承,所以在Spring Boot框架中还可以
使用该注解读取和注入配置文件属性值。使用@Value注入属性的示例代码如下
@Component public class Person { @Value("${person.id}") private int id; }
自定义配置
spring Boot免除了项目中大部分的手动配置,对于一些特定情况,我们可以通过修改全局配置文件以适应具体生产环境,可以说,几乎所有的配置都可以写在 application. peroperties文件中, Spring Boot会自动加载全局配置文件从而免除我们手动加载的烦恼。但是,如果我们自定义配置文件, Spring Boot是无法识别这些配置文件的,此时就需要我们手动加载。接下来,将针对 Spring Boot的自定义配置文件及其加载方式进行讲解
使用@PropertySource加载配置文件
对于这种加载自定义配置文件的需求,可以使用@PropertySource注解结合
@Configuration注解配置 类的方式来实现。
@PropertySource注解用于指定自定义配置文件的具体位置和名称。同时,为了保证
Spring Boot能够扫描该注解,还需要类上添加Configuration注解将实体类作为自定义配置类。
当然,如果需要将自定义配置文件中的属性值注入到对应类的属性中,可以使用
@ConfigurationProperties或者@Value注解进行属性值注入
演示:
(1)打开Spring Bootresources项目的目录,在项目的类路径下新建一个test. properties自定义配置文件,在该配置文件中编写需要设置的配置属性
test.id=110 test.name=test
(2)在com.lagou.pojo包下新创建一个配置类MyProperties,提供test.properties自定义配置文件中
对应的属性,并根据@PropertySource注解的使用进行相关配置
@Configuration @PropertySource("classpath:test.properties") @EnableConfigurationProperties(MyProperties.class) @ConfigurationProperties(prefix = "test") public class MyProperties { private int id; private String name; }
使用@Configuration编写自定义配置类
在Spring Boot框架中,推荐使用配置类的方式向容器中添加和配置组件
在Spring Boot框架中,通常使用@Configuration注解定义一个配置类,Spring Boot会自动扫描和识别配置类,从而替换传统Spring框架中的XML配置文件。
当定义一个配置类后,还需要在类中的方法上使用@Bean注解进行组件配置,将方法的返回对象注入到Spring容器中,并且组件名称默认使用的是方法名,当然也可以使用@Bean注解的name或value属性
在Spring Boot配置文件中设置属性时,除了可以像前面示例中显示的配置属性值外,还可以使用随机
值和参数间引用对属性值进行设置。下面,针对配置文件中这两种属性值的设置方式进行讲解
随机值设置
在Spring Boot配置文件中,随机值设置使用到了Spring Boot内嵌的RandomValuePropertySource
类,对一些隐秘属性值或者测试用例属性值进行随机值注入
随机值设置的语法格式为${random.xx},xx表示需要指定生成的随机数类型和范围,它可以生成随机
的整数、uuid或字符串,示例代码如下
my.secret=${random.value} //配置随机值 my.number=${random.int} //配置随机整数 my.bignumber=${random.long} //配置随机long类型数 my.uuid=${random.uuid} //配置随机uuid类型数 my.number.less.than.ten=${random.int(10)} //配置小于10的随机整数 my.number.in.range=${random.int[1024,65536]}//配置范围在[1024,65536]之间的随机整数
上述代码中,使用RandomValuePropertySource类中random提供的随机数类型,分别展示了不同类
型随机值的设置示例
参数间引用
在Spring Boot配置文件中,配置文件的属性值还可以进行参数间的引用,也就是在后一个配置的属性值中直接引用先前已经定义过的属性,这样可以直接解析其中的属性值了。
使用参数间引用的好处就是,在多个具有相互关联的配置属性中,只需要对其中一处属性预先配置,其他地方都可以引用,省去了后续多处修改的麻烦
参数间引用的语法格式为${xx},xx表示先前在配置文件中已经配置过的属性名,示例代码如下
app.name=MyApp app.description=${app.name} is a Spring Boot application
上述参数间引用设置示例中,先设置了“app.name=MyApp”,将app.name属性的属性值设置为了
MyApp;接着,在app.description属性配置中,使用${app.name}对前一个属性值进行了引用
SpringBoot原理深入及源码剖析
传统的Spring框架实现一个Web服务,需要导入各种JAR包,然后编写对应的XML配置文件等,相较而言,Spring Boot显得更加方便、快捷和高效。那么,Spring Boot究竟如何做到这些的呢?
接下来分别针对Spring Boot框架的依赖管理、自动配置和执行流程进行深入分析
依赖管理
问题:(1)为什么导入dependency时不需要指定版本?
在Spring Boot入门程序中,项目pom.xml文件有个核心依赖,spring-boot-starter-parent
从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如activemq、spring、tomcat等,都有与SpringBoot2.2.2版本相匹配的版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。
需要说明的是,如果pom.xml引入的依赖文件不是spring-boot-starter-parent管理的,那么在 pom.xml 引入依赖文件时,需要使用标签指定依赖文件的版本号。
- spring-starter-boot--web依赖
查看 spring--starter--boot--web依赖文件源码,核心代码具体如下
从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖
正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行的统一管理。
Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖,我们可以打开Spring Boot官方文档,搜索"Starters"关键字查询场景依赖启动器
集成 mybatis
为了解决上述由于驼峰命名方式造成的表字段值无法正确映射到类属性的情况,可以在Spring Boot全
局配置文件application.properties中添加开启驼峰命名匹配映射配置,示例代码如下
#开启驼峰命名匹配映射 mybatis.configuration.map-underscore-to-camel-case=true
Springboot 数据访问
Springdata是 Spring提供的一个用于简化数据库访问、支持云服务的开源框架。它是一个伞形项目,包含了大量关系型数据库及非关系型数据库的数据访问解决方案,其设计目的是使我们可以快速且简单地使用各种数据访问技术。 Spring Boot默认采用整合
Springdata的方式統一处理数据访问层,通过添加
大量自动配置,引入各种数据访问模板 xxxtemplate以及统一的 Repositoryi接口,从而达到简化数据访问层的操作。
Spring Datai提供了多种类型数据库支持,对支持的的数据库进行了整合管理,提供了各种依赖启动器,接下来,通过一张表罗列提供的常见数据库依赖启动器,如表所示。
除此之外,还有一些框架技术, Spring Data项目并没有进行统一管理, Spring Boott官方也没有提供对应的依赖启动器,但是为了迎合市场开发需求、这些框架技术开发团队自己适配了对应的依赖启动器,
例如, mybatis- spring-boot- starter支持 My Batis的使用
Springboot 视图技术
集成 Thymeleaf
Thymeleaf 是一个用于 web 和独立环境的现代服务器端 Java 模板引擎。
Thymeleaf 的主要目标是为开发工作流程带来优雅的自然模板ー HTML,它既可以在浏览器中正确显示,也可以作为静态原型工作,从而加强开发团队之间的协作。
插播 - 对 JSP 技术的支持
1.idea在工程源文件夹src/main/下创建web资源文件夹,webapp,并设置为资源文件。
2.application.properties文件设置如下
#jsp 支持 spring.mvc.view.suffix=.jsp spring.mvc.view.prefix=/WEB-INF/page/
spring.mvc.view.prefix=/
代表是src/mian下的webapp下, 紧接着,需要在main下新建一个webapp的文件夹用来存放*.jsp文件。
- 如果需要放在其他目录下, 例如放在 webapp 下的 page 下面, 则需要设置为
spring.mvc.view.prefix=/page/
#关闭默认模板引擎 spring.thymeleaf.cache=false spring.thymeleaf.enabled=false
- maven配置jsp相关依赖。
<!-- servlet 依赖. --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> <!--jstl--> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!-- tomcat 的支持.--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <!--引入springBoot 内嵌的Tomcat对JSP的解析包--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency>
- 【此步骤不可省略】创建 ServletInitalizer 集成 SpringbootServletInitalizer,绑定自己添加了@SpringbootApplication类。
package com.example.demo; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(DemoApplication.class); } }
5.webapp示例。
- 将pom文件打包方式改成war。
<packaging>war</packaging>
7.执行 spring-boot:run
。可以看到可以直接访问静态资源。
http://localhost:8080/page/index.jsp
- 写个controller, 实现 controller 跳转到 对应 index.jsp 页面
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <title>Title</title> </head> <body> Hello JSP. ${attr } ${attr } ${attr } </body> </html>
添加 src/main/java/com/example/demo/StaticController.java
类文件
package com.example.demo; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class StaticController { @RequestMapping("toHome") public String index(Model model){ model.addAttribute("attr","hello jsp"); return "index"; } }
重新spring-boot:run
, 访问链接, 发现 http://localhost:8080/toHome 已生效.
【maven 报错】maven项目执行maven install时报错Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)
解决办法: 在 WEB-INF 下配置 web.xml 即可。
Springboot缓存管理
默认緩存管理
Spring框架支持透明地向应用程序添加缓存对缓存进行管理,其管理绥存的核心是将缓存应用于操作数据的方法,从而减少操作数据的执行次数,同时不会对程序本身造成任何干扰。
Spring Boots继承了 Spring框架的缓存管理功能,通过使用@ Enable Caching注解开启基于注解的缓存支持, Spring Booti就可以启动缓存管理的自动化配置.
作业
编程题:个人博客系统首页展示(文章分页展示)
前台需要显示: 首页 上一页 下一页 尾页
作业文档说明:
1、提供资料:代码工程、验证及讲解视频。(仓库中只有本次作业内容)
2、讲解内容包含:题目分析、实现思路、代码讲解。
3、效果视频验证:
个⼈博客系统首页展示(文章分页展示),分页演示。
create DATABASE blog_system DROP TABLE IF EXISTS t_article; CREATE TABLE t_article ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(50) NOT NULL COMMENT '⽂章标题', content longtext COMMENT '⽂章具体内容', created date NOT NULL COMMENT '发表时间', modified date DEFAULT NULL COMMENT '修改时间', categories varchar(200) DEFAULT '默认分类' COMMENT '⽂章分类', tags varchar(200) DEFAULT NULL COMMENT '⽂章标签', allow_comment tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否允许评论', thumbnail varchar(200) DEFAULT NULL COMMENT '⽂章缩略图', PRIMARY KEY (id) ) ENGINE = InnoDB CHARSET = utf8; -- ---------------------------- -- Records of t_article -- --------------------------- INSERT INTO t_article VALUES ('1', '2019新版Java学习路线图','Java学习路线图具体内容 具体内容具体内容具体内容具体内容具体内容具体内容','2019-10-10', null, '默认分类', '‘2019,Java,学习路线图', '1', null), ('2', '2019新版Python学习线路图','据悉,Python已经⼊驻 ⼩学⽣教材,未来不学Python不仅知识会脱节,可能与⼩朋友都没有了共同话题~~所以,从今天起不要再 找借⼝,不要再说想学Python却没有资源,赶快⾏动起来,Python等你来探索' ,'2019-10-10', null, '默认分类', '‘2019,Java,学习路线图', '1', null), ('3', 'JDK 8——Lambda表达式介绍',' Lambda表达式是JDK 8中⼀个重要的新特性,它使⽤⼀个清晰简洁的表达式来表达⼀个接⼝,同时Lambda表达式也简化了对集合 以及数组数据的遍历、过滤和提取等操作。下⾯,本篇⽂章就对Lambda表达式进⾏简要介绍,并进⾏演示 说明' ,'2019-10-10', null, '默认分类', '‘2019,Java,学习路线图', '1', null), ('4', '函数式接⼝','虽然Lambda表达式可以实现匿名内部类的 功能,但在使⽤时却有⼀个局限,即接⼝中有且只有⼀个抽象⽅法时才能使⽤Lamdba表达式代替匿名内部 类。这是因为Lamdba表达式是基于函数式接⼝实现的,所谓函数式接⼝是指有且仅有⼀个抽象⽅法的接 ⼝,Lambda表达式就是Java中函数式编程的体现,只有确保接⼝中有且仅有⼀个抽象⽅法,Lambda表达式才能顺利地推导出所实现的这个接⼝中的⽅法' ,'2019-10-10', null, '默认分类','‘2019,Java,学习路线图', '1', null), ('5', '虚拟化容器技术——Docker运⾏机制介绍','Docker是⼀个开源的应⽤容器引擎,它基于go语⾔开发,并遵从Apache2.0开源协议。使⽤Docker可以让开发者封装 他们的应⽤以及依赖包到⼀个可移植的容器中,然后发布到任意的Linux机器上,也可以实现虚拟化。 Docker容器完全使⽤沙箱机制,相互之间不会有任何接⼝,这保证了容器之间的安全性' ,'2019-10-10', null, '默认分类', '‘2019,Java,学习路线图', '1', null);
建一个 spring boot 项目, 分析所需加依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
和配置application.yml
spring: application: name: blog datasource: url: jdbc:mysql:///blog_system?serverTimezone=UTC username: root password: 123456 thymeleaf: cache: false encoding: UTF-8 mode: HTML suffix: .html # 不要配置后缀,否则相对路径找不到header 和 footer #prefix: classpath:/templates/client/ # 应用服务web访问端口 server: port: 80 servlet: context-path: /blog
建立 Pojo 和 Service 包, 和简易的controller , 使页面可以跑起来
package edu.lagou.pojo; import javax.persistence.*; import java.util.Date; @Entity(name = "t_article") public class Article { @Id @GeneratedValue private Integer id; // 时间 private Date created; // 标题 private String title; // 内容 private String content; public Integer getId() { return id; } ... get set 方法, toString 方法
package edu.lagou.dao; import edu.lagou.pojo.Article; import org.springframework.data.jpa.repository.JpaRepository; public interface ArticleRepository extends JpaRepository<Article, Integer> { }
package edu.lagou.service; @Service public class ArticleService { @Autowired private ArticleRepository articleRepository; public List<Article> findAll(){ return articleRepository.findAll(); } }
package edu.lagou.controller; @Controller public class ArticleController { @RequestMapping("/index.html") public ModelAndView index(@RequestParam(defaultValue = "0") int page, ModelAndView modelAndView) { final Page<Article> typePage = loginService.findAll(page); modelAndView.addObject("articleList", typePage); modelAndView.setViewName("client/index"); return modelAndView; } @Autowired private ArticleService loginService; }
修改index.html
<div class="am-u-md-8 am-u-sm-12"> <!-- 文章遍历并分页展示 : 需要同学们手动完成,基本样式已经给出,请使用th标签及表达式完成页面展示 --> <div th:each="article: ${articleList}"> <article class="am-g blog-entry-article"> <div class="am-u-lg-6 am-u-md-12 am-u-sm-12 blog-entry-text"> <!-- 文章分类 --> <span class="blog-color"style="font-size: 15px;"><a>默认分类</a></span> <span> </span> <!-- 发布时间 --> <span style="font-size: 15px;" th:text="'发布于 '+ ${article.created}" /> <h2> <!-- 文章标题 --> <div><a style="color: #0f9ae0;font-size: 20px;" th:text="${article.title}" /> </div> </h2> <!-- 文章内容--> <div style="font-size: 16px;" th:text="${article.content}" /> </div> </article> </div> </div> <!-- 博主信息描述 -->
开始分页
分析可得五个属性
- 总页数
- 总记录数 select count(*) from table_name
- 当前所在页(请求第几页)
- 每页固定记录数, 例如固定为每页只显示 2 条数据.
- 查询得到的当前页的数据集合[list]
使用 jpa 具体实现分页
分析 controller 传入查询的第 x 页数据
service 根据 dao层能拿到的操作分别拿到总记录数, 再根据已知的每页固定记录数 和 入参查询第X页数据. 调用 dao层的findAll方法拿到最终的结果集, 封装数据后返给controller.
@Service public class ArticleService { public Page<Article> findAllByPage(int page, int size) { // 总记录数 final long count = articleRepository.count(); // 每页记录数 if (size < 1) { size = DEFAULT_SIZE; } final PageRequest pageRequest = PageRequest.of(page, size); final org.springframework.data.domain.Page<Article> all = articleRepository.findAll(pageRequest); Page<Article> typePage = new Page<>(count, page, size); typePage.setContent(all.getContent()); return typePage; } }
数据怎么传递, 使用泛型类
package edu.lagou.pojo; public class Page<T> { /** * 总条目数 */ private final long totalElements; /** * 当前第几页 */ private final int page; /** * 每页条目数 */ private final long size; private List<T> content; /** * 总页数 */ public long getTotalPages() { long totalPage = totalElements / size; // 如果整除则 + 1 if (totalElements % size == 0) { totalPage++; } return totalPage; } }
改造 controller 的 index 方法
@RequestMapping("/index.html") public ModelAndView index(@RequestParam(defaultValue = "0") int page, ModelAndView modelAndView) { final Page<Article> typePage = loginService.findAllByPage(page); modelAndView.addObject("data", typePage); modelAndView.setViewName("client/index"); return modelAndView; }
由于这次set 的是 modelAndView.addObject("data", typePage);
, 所以还需要修改 index.html
<div th:each="article: ${data.content}">
改造 footer.html 部分
<!-- 四个 a 标签居中显示, 加入 text-align 属性 --> <div style="text-align: center"> <a th:href="@{index.html(page=0)}"}> <span th:text="#{login.homePage}"/> </a> <span th:if="${data.page} > 0"> <a th:href="@{index.html(page=${data.page-1})}"}> <span th:text="#{login.previousPage}"/> </a> </span> <span th:unless="${data.page} > 0" th:text="#{login.previousPage}"></span> <span th:if="${data.page} < ${data.totalPages}"> <a th:href="@{index.html(page=${data.page+1})}"}> <span th:text="#{login.nextPage}"/> </a> </span> <span th:unless="${data.page} < ${data.totalPages}" th:text="#{login.nextPage}"></span> <a th:href="@{index.html(page=${data.totalPages-1})}"}> <span th:text="#{login.endPage}"/> </a> </div>
这里还顺便把一些字面量提取出来, 为以后国际化做准备 messages.properties
.
演示地址
http://localhost/blog/index.html
参考
spring boot 完整集成jsp。(亲测可用) - Another_me - 博客园
https://www.cnblogs.com/mercurys/p/7685102.html