仅为学习笔记
目录
1)生成Bean对象person,交给Spring管理:在声明Person类的语句上一行添加注解@Component(注意导包)。
案例2:实现任务:创建模板hello.html,由后端提供姓名数据,作为h1标签的内容展示到前端页面。
任务:创建模板hello.html,由后端提供姓名、年龄、性别等数据,作为table标签的一行内容展示到前端页面。结果如下所示:
任务:数据查询。使用Spring Boot整合JPA从数据库表中查询一行数据。
创建XML映射文件:编写对应的SQL语句。创建XML映射文件ArticleMapper.xml
完成任务:使用SpringBoot整合JPA对book表做数据访问,要求在控制台输出执行数据库操作时所生成的 SQL 语句。
当把 spring.jpa.show-sql 属性设置为 true 时,Spring Data JPA 会在控制台输出它在执行数据库操作时所生成的 SQL 语句。
编写业务操作类Service文件CommentService,在该类中编写数据的查询、修改和删除操作
编写Web访问层Controller文件CommentController,使用注入的CommentService实例对象编写对Comment评论数据的查询、修改和删除方法。
浏览器输入http://localhost:8080/get/2显示t_comment表中id=2的数据
浏览器输入http://localhost:8080/update/2/xiaoMing 将t_comment表中id=2行的作者更新成xiaoMing并返回更新后的数据。
浏览器输入http://localhost:8080/delete/2 将t_comment表中id=2的一行删掉
(3)访问http://localhost:8080/book/admin/manag时进入图书管理页面(book_manag.html)。
(4)点击编辑按钮实现:跳转到编辑页面:updateBooks.html,页面中获取被编辑行数据。单选按钮默认被选中属性为th:checked。
(5)确认修改:将修改后的内容存到数据库中并返回管理员首页(book_manag.html)。
(6)点击删除按钮实现:将此行数据删除,并返回管理员首页(book_manag.html)。
任务1-1:访问http://localhost:8080/则跳转到后台管理首页。
设计模式
MVC全名是Model View Controller,是三个核心组件:模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范。
Model(模型)模型是应用程序的数据部分,它负责处理数据,包括数据的存储、检索和验证等。
View(视图)视图是应用程序的用户界面部分,负责显示模型中的数据同时负责与用户进行交互,收集用户的操作信息。
Controller(控制器)控制器是应用程序中的中介,负责接收用户的输入(如鼠标点击、键盘按键等),并解释这些输入以调用业务方法执行相应的操作。
服务层是在软件开发,特别是企业级应用开发中常用的一个概念,它处于控制器和数据访问层之间,承担着业务逻辑处理和服务协调的重要职责。
例如: 电商系统:在用户下单的过程中,服务层需要处理多个业务逻辑, 如检查商品库存是否充足、计算订单总价(包括商品价格、运费、优惠等)、 生成订单记录、更新商品库存、处理用户积分等,讲这些业务进行封装。 为了提高系统的性能,服务层可以引入缓存机制,对一些经常访问的数据进行缓存。 当需要获取数据时,首先检查缓存中是否存在,如果存在则直接从缓存中获取,避免频繁访问数据库。 银行转账:从一个账户扣款和向另一个账户入账这两个操作必须在同一个事务中完成。 服务层可以使用事务管理机制来保证这两个操作的原子性,避免出现数据不一致的情况。
开发框架
框架是提供标准化基础设施和工具套件的软件架构,通过封装通用技术细节(如网络通信、数据持久化、安全认证等),使开发者能专注于业务逻辑实现,从而提高开发效率、保证系统稳定性和可维护性。
SpringBoot框架
在Spring Boot框架出现之前,为了解决Java企业级应用开发笨重臃肿的问题,Java EE最常用的框架是Spring。Spring是2003年兴起的轻量级的Java开源框架,旨在简化Java企业级开发。
Spring框架就像Java开发的万能工具箱像搭积木一样轻松组装企业级应用,省去手动拼装零件的麻烦。
依赖注入、面向切面、官网:Spring.io
---------
Pivotal团队在原有Spring框架的基础上通过注解的方式进一步简化了Spring框架的使用,并基于Spring框架开发了全新的Spring Boot框架。 2014年4月正式推出了Spring Boot 1.0版本,同时在2018年3月又推出了Spring Boot 2.0版本。Spring Boot 2.x版本在Spring Boot 1.x版本的基础上进行了诸多功能的改进和扩展,同时进行了大量的代码重构。
Spring Boot是基于Spring框架开发的全新框架,其设计目的是简化新Spring应用的初始化搭建和开发过程。 Spring Boot整合了许多框架和第三方库配置,几乎可以达到“开箱即用”。
环境
1.JDK
Spring Boot 2.7.6依赖Spring Framework 5.3.24,而运行Spring Framework 5.3.24需要Java 8及以上版本进行支撑,在此选择当前使用较为广泛并运行较为稳定的JDK 8。
idea我在java的文章里面已经讲了。
2.项目构建工具(Maven)
Spring Boot 2.7.6官方声明支持的项目构建工具包括有Maven和Gradle,其中Maven的版本需要3.5及以上版本。在此采用Maven 3.6.3对Spring Boot进行项目构建管理。
一、 下载Maven 从Apache官网下载Maven。官网:https://maven.apache.org/
二、 解压Maven工具包(推荐解压到硬盘根目录,因为Maven的目录路径不能包含中文)
三、 右键我的电脑 -> 属性 -> 高级系统设置 -> 环境变量 在系统变量处新建:MAVEN_HOME,变量值为Maven的目录
四、 回到环境变量双击Path,打开后点击新建,输入%MAVEN_HOME%\bin
五、 打开CMD,输入mvn -v,若出现版本号则说明配置完成。
六、 配置私服,因为中央仓库在国外导致下载jar包很慢或者失败,所以我们改为国内的服务器。 在apache-maven-3.6.3\conf\settings文件中的<mirrors>标签内添加以下内容。
3.开发工具(IDEA)
业界较为常用有Eclipse和IntelliJ IDEA,相比Eclipse而言,IntelliJ IDEA的开发效率更高。在此选择IntelliJ IDEA的Ultimate 进行项目开发。
---------------
Maven是Apache的一个开源项目,它在Java开发环境中主要用于管理和构建项目,以及维护项目中的依赖关系。
依赖管理(工具管理):Maven提供了一套标准的构建和项目管理工具,这些构建工具通过中央仓库管理项目的依赖关系,使得开发人员可以轻松地添加、更新和删除依赖项,而无需手动下载和安装。这有助于减少依赖冲突和错误,提高项目的可维护性。
在Java开发中需要用到大量第三方库和组件,它们通常以JAR(Java Archive)文件的形式提供。 第三方库和组件可能包括各种功能,如日志记录(如Log4j)、JSON处理(如Jackson或Gson)、数据库连接(如JDBC驱动或JPA实现如Hibernate)、测试框架(如JUnit)、Web框架(如Spring MVC或Spring Boot)等等。 使用Maven构建工具,可以在项目配置文件中声明依赖,构建工具会自动从中央仓库或配置的仓库中下载这些JAR包,并将它们添加到项目的类路径中。在构建和运行时,这些JAR包会被自动引用,而无需手动下载。
入门
创建和运行
上面是坐标
什么是坐标? Maven 中的坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置。 使用坐标来定义项目或引入项目中需要的依赖。 Maven 坐标主要组成 groupld:定义当前Maven项目隶属组织名称。 artifactld: 定义当前Maven项目名称。 version:定义当前项目版本号。
标记该类为主程序启动类
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 Java 语言中的类、方法、变量、参数和包等都可以被标注。Java 注解(是一种代码元数据,它为程序的元素(类、方法、字段等)提供额外信息,但不会直接影响程序的执行逻辑。
在项目pom.xml文件中导入新依赖或修改其他内容后,通常会自动更新而无须手动管理。但有些情况下,依赖文件可能还是无法自动加载,这时候就需要重新手动加载依赖文件,具体操作方法为:右键单击项目名,选择“Maven”→“Reload project”重新加载项目即可。
简单测试
该注解为标注此类为控制器类,并让这个类成为一个处理请求的处理器。
-----------
用于处理请求方法的get类型。
RestController和Controller
Spring Boot项目打包
在实际开发中,通常项目完成后不会将源代码公布给所有人,而是将项目和其依赖的组件组织成一个可执行文件分发到目标系统上运行或者交付给其他人使用,这个组织的过程也称为打包。 项目打包后在其他环境可以很方便的运行,Spring Boot项目打包时通常会被创建为可执行的JAR包或WAR包,这两种包内部的文件结构不同,其运行的方式也不相同。下面,分别对这两种方式的打包和运行进行讲解。
Spring Boot应用内嵌了Web服务器,所以基于Spring Boot开发的Web应用也可以独立运行,无须部署到其他Web服务器中。下面,以打包chapter01项目为例,将Spring Boot项目打包为可执行的JAR包并运行。
(1)添加Maven打包插件。SpringBoot程序是基于Maven创建的, 在对Spring Boot项目进行打包前, 需要在项目pom.xml文件中加入Maven打包插件后刷新maven(如有则无需添加)。 <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
(2)使用IDEA进行打包。IDEA右侧Maven。 clean命令:可以清除所有在构建过程中生成的文件。 test命令:可以使用合适的单元测试框架来测试编译的源代码。 package命令:可以完成项目编译、单元测试、打包功能。
打包时候跳过测试阶段(右上角有个○禁止符号)
如果打包成功会在项目的target文件夹下创建项目对应的可执行JAR包。
JAR包运行
一个可执行的JAR包可以由JVM 直接执行而无须事先提取文件或者设置类路径。对此可以直接使用Java命令运行可执行的JAR包。
java –jar JAR包名称.jar
运行JAR包时可以选择在IDEA中或者在CMD窗口中执行,这两种方式没有什么区别,这里选择在CMD窗口中执行,在本地打开项目的target文件夹,路径里输入cmd,然后在cmd里输入java –jar JAR包名称.jar即可。退出按ctrl+c
自动配置
Bean/Bean对象
在 Spring 里,Bean 对象指的是由 Spring IoC(Inversion of Control,控制反转)容器管理的对象。IoC 容器负责创建、配置和管理这些 Bean 对象的生命周期。简单来说,当你把一个类的对象交给 Spring 容器后,这个对象就成为了一个 Bean,Spring 会帮你处理它的创建、初始化、销毁等操作。
通俗的讲:Bean对象是一类特殊的对象。Bean对象好似是有门派的修仙弟子,而其它对象是散修。普通对象的创建、初始化、属性赋值、后续怎么管理,基本都是由使用它们的代码自己去操心;而 Bean 对象的 “生老病死”(创建、初始化、属性赋值、使用到最后销毁的整个生命周期)都由门派(Spring 容器)来统一安排和管理
Spring Boot采用约定大于配置的设计思想,将Spring Boot开发过程中可能会遇到的配置信息提前配置好,写在自动配置的JAR包中。项目启动时会自动检测项目类路径下所有的依赖JAR包,将检测到的Bean注册到Spring容器中,并根据检测的依赖进行自动配置。
@Component这个注解,可以让一个类变成bean对象。
@SpringBootApplication注解
1.@SpringBootConfiguration
@SpringBootConfiguration标注当前类是一个配置类,它是一个复合注解。
2.@EnableAutoConfiguration
@EnableAutoConfiguration可以开启自动配置,它也是一个复合注解。
3.@ComponentScan
@ComponentScan注解是一个组件包扫描器,其主要作用是扫描指定包及其子包下所有注解类文件作为Spring容器的组件使用。
GetMapping是接受get请求,PostMapping是接受post请求,RequestMapping是2个都接受。从后端接受数据,是get,将数据送入后端,则是post,
单元测试
单元测试类:单元测试是针对一个独立的工作单元进行正确性验证的测试,对程序开发来说非常重要,通过单元测试不仅能增强程序的健壮性,而且为程序的重构提供了依据。Spring Boot为项目的单元测试提供了很好的支持。 在项目中添加测试依赖启动器后,可以编写相关测试代码对Spring Boot项目中相关功能进行单元测试。根据测试时候是否需要启动Web服务器,可以将单元测试分为Web环境模拟测试和业务组件测试。
测试依赖是这个
单元测试类默认是这样的
@SpringBootTest标记次类是单元测试类。
@Test标记此方法为测试方法
核心配置与注解
Spring Boot极大地简化了Spring应用的开发,尤其是Spring Boot的自动配置功能,该功能使项目即使不进行任何配置,也能顺利运行。当用户想要根据自身需求覆盖Spring Boot的默认配置时,需要使用配置文件修改Spring Boot的默认配置。本章将对Spring Boot的配置进行讲解。
全局配置文件
全局配置文件能够对一些默认配置值进行修改。Spring Boot默认使用的全局配置文件有application.properties和application.yml,Spring Boot启动时会自动读取这两个文件中的配置,如果文件中存在与默认自动配置相同的配置信息,则覆盖默认的配置信息。下面对全局配置文件进行讲解。
SpringBoot的配置文件(全局配置文件):application.properties/application.yml/application.yaml
application.properties文件中可以定义Spring Boot项目的相关属性,属性可采用键值对格式进行设置,表示形式为“Key=Value”,这些相关属性可以是系统属性、环境变量、命令参数等信息,也可以是自定义的属性。
address=beijing server.port=80 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
application.properties文件中的属性支持多种类型,常见的有字面量、数组和集合。
-------------------
.yml文件语法格式
key:(空格)value
server: port: 8081 path: /hello
1.字面量类型属性
如果需要配置的属性为对象的属性,可以通过“对象名.属性名”的方式指定属性的键。对象中可能包含多个属性,在application.properties文件中为对象的属性赋值时,一个属性对应一对键值对。
user.username=lisi user.age=18
------------------
yaml文件中,使用“Key: Value”的形式表示一对键值对,其中Value前面有一个空格,并且该空格不能省略。在配置字面量类型的属性时,直接将字面量作为Value直接写在键值对中即可,且默认情况下字符串是不需要使用单引号或双引号的。
address: beijing age: 13
如果需要配置的属性为对象的属性,配置的方式有缩进式和行内式两种。
# 缩进式 consumer: username: lisi age: 18 # 行内式 consumer: {username: lisi,age: 18}
2.数组类型属性
在application.properties文件中配置数组类型属性时,可以将数组元素的值写在一行内,元素值之间使用逗号(,)间隔,也可以在多行分别根据索引赋值。
# 方式一 user.hobby=swim,travel,cook # 方式二 user.hobby[0]=swim2 user.hobby[1]=travel2 user.hobby[2]=cook2
-----------------
当YAML配置文件中配置的属性为数组类型或单列集合时,也可以使用缩进式写法和行内式写法。
consumer: hobby: - play - read - sleep consumer: hobby: [play,read,sleep] consumer: hobby: play,read,sleep
3.集合类型属性
在application.properties文件中也可以配置集合类型的属性,下面分别演示配置List、Set、Map的集合类型属性。
# 配置List:方式一 user.subject=Chinese,English,Math # 配置List:方式二 user.subject[0]=Chinese user.subject[1]=English user.subject[2]=Math
# 配置Set user.salary=120,230 # 配置Map方式一 user.order.1001=cookie user.order.1002=cake # 配置Map方式二 user.order[1001]=cookie user.order[1002]=cake
-------------------
当YAML配置文件中配置的属性为Map集合时,可以使用缩进式写法和行内式写法。
consumer: order: 1001: cookie 1002: cake consumer: order: {1001: cookie,1002: cake} consumer: order: 1001: cookie,1002: cake
实践
这个url,是访问了我springboot项目的web服务器的端口
其中的HellOcon类,是我自己创建的,里面有个hello方法。
这里是限制了端口号,如果我改成8081,那么上面的url就不行了。
----------------
下面是yml的格式,properties和yml首先是格式不一样,优先级上properties更高。
自定义配置
当自动配置不能满足我们的需求的时候,通常会通过配置类来对自定义Bean进行Bean容器的装配工作(即自定义装配)。添加@Configuration注解的类称之为配置类,配置类主要用于替代原来的配置文件。
当定义一个配置类后,还需要在类中的方法上使用@Bean注解进行组件配置,将方法的返回对象注入到Spring容器中。组件名称默认使用的是方法名,也可以使用@Bean注解的name或value属性自定义组件的名称。引入配置类时,只需让配置类被Spring Boot自动扫描识别即可,该配置类中返回的组件会自动添加到Spring容器中。
举例1:
任务:自动配置不能满足我们的需求,此时需要新增一个Bean对象:person,并对该对象进行基础属性的配置。
要求:设计一个Person类,拥有私有属性:name;age;hobby;map;family;Pet。 Alt+Insert添加各个属性的Getter和Setter方法、toString()方法。 设计一个Pet类拥有私有属性type;name。Alt+Insert添加各个属性的Getter和Setter方法、toString()方法。
public class Person { private String name; private int age; private List<String> hobby; private Map map; private String[] family; private Pet pet; //自行添加各个属性的Getter和Setter方法、toString()方法。 } public class Pet { private String type; private String name; //自行添加各个属性的Getter和Setter方法、toString()方法。 }
这种我们自己创的类要放在com.example.项目名下面,最好再建个包包起来。
1)生成Bean对象person,交给Spring管理:在声明Person类的语句上一行添加注解@Component(注意导包)。
为小明所有的属性进行赋值(使用全局配置文件)。
赋值三步走: 1)生成对象person:Person person = new Person(); 2)使用person调用各个属性的Setter方法进行赋值; 3)测试是否赋值成功。 新的三步走: 1)生成Bean对象person,交给Spring管理; 2)在全局配置文件中为person对象进行属性注入; 3)单元测试类中测试是否赋值成功。 person.name=xiaoming person.age=22 person.family=father,mother person.hobby=rapping,drawing person.map.ICBC=123123 person.map.BC=123456 person.pet.type=dog person.pet.name=wangcai
使用Spring Boot全局配置文件配置属性时,如果配置的属性是Spring Boot内置的属性(如服务端口server.port),那么Spring Boot会自动扫描并读取配置文件中的属性值并覆盖原有默认的属性值。如果配置的属性是用户自定义的属性,可以通过Java代码去读取该配置属性,并且把属性绑定到Bean。在Spring Boot项目中可以通过 @Value和@ConfigurationProperties对配置属性进行绑定。
2)在全局配置文件中为person对象进行属性注入:
将配置文件中的属性名、属性值的键值对注入person对象中:在声明Person类的语句上一行添加注解@ConfigurationProperties(prefix = "person")(注意导包)。
可以理解为,spring容器里面有个Person对象,这个对象是个bean对象,我们将其命名为person,我们就是在全局配置文件中设置属性,相应属性注入person对象。
在application.properties中编写赋值语句
3)单元测试类中测试是否赋值成功。
进行测试: 获取对象person:使用注解@Autowired(测试方法外的任意一行即可)。
测试是否赋值成功:测试方法中打印输出person对象,运行此方法。
结果:
小结(重要)
使用全局配置文件为对象属性进行批量赋值
@Component
@Component是 Spring 框架中的一个核心注解,用于标识一个类为 Spring 管理的组件(Bean),Spring 在启动时会自动扫描并创建该类的实例,并将其纳入 IoC 容器(控制反转容器) 进行管理。
@Component:生成当前类的实例对象存到Spring容器中,此时该对象被称为Bean对象。Bean对象:可以被Spring管理的特殊对象。
1.声明一个类为 Spring Bean
被 @Component 标注的类会被 Spring 自动检测并实例化,无需手动在 XML 或 Java 配置中声明。2.启用自动扫描
需要配合 @ComponentScan 使用,Spring 会扫描指定包及其子包下所有 @Component 及其派生注解(如 @Service, @Repository, @Controller)的类。@ConfigurationProperties
是 Spring Boot 提供的一个注解,用于将外部配置文件(如 application.yml 或 application.properties)中的属性值批量绑定到一个 Java Bean 上,实现类型安全的配置管理。
集中管理配置:将分散的配置属性统一映射到一个 Java 对象中。
类型安全:自动转换配置值到 Java 类型(如 String → Integer、List、Map 等)。
支持校验:结合 @Validated 可对属性值进行校验(如 @NotNull、@Min、@Max)。
@ConfigurationProperties(prefix = "person"):将配置文件中前缀为person的每个属性的值映射到当前类中的变量上。(注:此注解需通过setXxx()方法注入)
@Autowired
@Autowired:主要作用是自动装配 Bean 之间的依赖关系,简单理解就是将Spring容器中的实例拿出来用。
-----------------------------------------------
可以把 Spring 容器想象成一个大型的仓库,里面存放着各种各样的工具(Bean 实例)。@Component 等注解就像是给工具贴上标签,告诉仓库管理员(Spring 容器)哪些工具需要被存放到仓库里。而 @Autowired 则像是一个 “连接器”,当某个工具组合(类)需要用到另一个工具(依赖的 Bean)时,它会自动从仓库中找到对应的工具并连接起来,让工具组合能够正常工作。
-------------------
新增:
1.底层框架 @Value注解:由Spring框架提供。 @ConfigurationProperties注解:由Spring Boot框架提供。 2.功能 @Value注解:只在需要注入属性值的单个属性上进行注入配置。 @ConfigurationProperties注解:主要用于将配置文件中某一类属性整体批量读取并注入到Bean的属性中。 3.为属性设置setter方法 @Value注解:不需要为属性设置setter方法。 @ConfigurationProperties注解:必须为每一个属性设置setter方法。 4.复杂类型属性注入 @Value注解:无法解析,导致注入失败。 @ConfigurationProperties注解:支持任意数据类型的属性注入。 5.松散绑定 @Value注解:不支持松散绑定语法。 @ConfigurationProperties注解:支持松散绑定语法。例如Person类有一个字符串类型的属性firstName,可以绑定配置文件中的如下属性。 person.firstName=james // 标准写法,对应Person类属性名 person.first-name=james // 使用横线-分隔多个单词 person.first_name=james // 使用下划线_分隔多个单词 PERSON.FIRST_NAME=james // 使用大小写格式,推荐常量属性配置
举例2:
完成任务:使用全局配置文件为对象属性进行单个赋值并进行测试。
注意,这个并不会将属性与对象绑定,只是绑定了字段了。person是个前缀,name是自己命名的字段。
举例3:
扩展案例:JSR303数据校验
在Person类中增加一个email属性,此属性的特殊性:email有固定的格式,赋值时最好能进行判断是否符合该格式。JSR303数据校验。
需要新的工具
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
@Validated使用工具完成校验
注意,value不能检验,然后记得给email加set和get
视图技术
Thymeleaf基础入门
HTML如何构建在服务器上
案例1:
目标:浏览器输入http://localhost:8080/index请求打开index.html页面。
任务1:浏览器输入http://localhost:8080/index在前端浏览器页面显示字符串“index”。
任务2:浏览器输入http://localhost:8080/index请求打开index.html页面
注解
@Controller 和 @RestController 都是用于处理HTTP请求的注解,@Controller主要用于返回视图(View),方法通常返回一个视图名称(String),由视图解析器(ViewResolver)解析为具体的页面。如果需要返回JSON/XML等数据,需要在方法上额外添@ResponseBody注解。
@RestController所有方法默认返回数据(JSON/XML等)是@Controller和@ResponseBody的组合注解,所有方法自动添加@ResponseBody,直接返回数据而非视图。
如何将后端数据显示到前端index.html页面?
通过模板引擎修改标签内容
模板引擎
模板:Spring Boot支持HTML、XML、TEXT、JAVASCRIPT、CSS、RAW六种模板。
模板引擎:获取模板,将数据和模板结合并生成输出结果文件(通常是HTML)的工具。
Thymeleaf是Spring Boot官方推荐使用的模板引擎,它创建的模板可维护性非常高,Thymeleaf使开发人员能够在保持设计原型不变的情况下,将程序的数据和逻辑注入到模板文件中,以提高设计团队和开发团队的工作协调性,减少了因理解和实现偏差产生的问题,也使得维护工作更加高效和准确。下面对Thymeleaf简介、Thymeleaf常用属性、标准表达式等基础入门知识进行讲解。
Thymeleaf 在Spring Boot 中的使用
1、 在Spring Boot项目中使用Thymeleaf模板,必须保证引入Thymeleaf依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
Thymeleaf的HTML模板文件中使用的HTML元素语法几乎是标准的HTML语法,Thymeleaf 在标准HTML标签中增加一些格式为“th:xxx”的属性以实现“模板+数据”的展示。
xmlns处是名称空间:可避免编辑器出现HTML验证错误
th:text处是对<p>标签中的文本进行替换,"'里面是参数或者变量'"
案例2:实现任务:创建模板hello.html,由后端提供姓名数据,作为h1标签的内容展示到前端页面。
Model是 Spring MVC 框架中的一个核心接口,用于在控制器(Controller)和视图(View)之间传递数据。它的主要作用是向视图层(如 Thymeleaf、JSP、FreeMarker 等模板引擎)暴露数据,供前端渲染使用。
model可以看作存储键值对
Thymeleaf常用属性
Thymeleaf常用的th属性
单选按钮默认被选中属性为th:checked
Thymeleaf常用表达式
1.变量表达式
Thymeleaf中使用${}包裹内容的表达式被称为变量表达式,使用变量表达式可以访问容器上下文中的变量。
<p th:text="${title}">这是标题</p>
变量表达式中除了可以直接访问变量,还可以访问变量的属性和方法。
<p th:text="${user.name}">这是标题</p> <p th:text="${user.info()}">这是标题</p>
Thymeleaf为变量所在域提供了一些内置对象。 #ctx:上下文对象。 #vars:上下文变量。 #locale:上下文语言环境。 #request:HttpServletRequest对象,仅在Web应用中可用。 #response:HttpServletResponse对象,仅在Web应用中可用。 #session:HttpSession对象,仅在Web应用中可用。 #servletContext:ServletContext对象,仅在Web应用中可用。
内置对象都以符号#开头,通过变量表达式可以访问这些内置对象,例如,通过变量表达式访问HttpSession中的name属性。
<p th:text="${#session.getAttribute('name')}"></p>
在变量表达式中也使用内置对象直接访问内置对象的属性。
<p th:text="${session.name}"></p>
为了便于开发,Thymeleaf中还提供了一些内置的工具对象, 这些工具对象就像是Java内置对象一样, 可以直接访问对应Java内置对象的方法来进行各种操作。 #strings:字符串工具对象, 常用方法有equals()、length()、substring()、replace()等。 #numbers:数字工具对象,常用的方法有formatDecimal()等。 #bools:布尔工具对象,常用的方法有isTrue()和isFalse ()等。 #arrays:数组工具对象, 常用的方法有toArray()、isEmpty()、contains()等。 #lists:List集合工具对象, 常用的方法有toList()、size()、isEmpty()等。 #sets:Set集合工具对象, 常用的方法有toSet()、size()、isEmpty()、contains()等。 #maps:Map 集合工具对象, 常用的方法有size()、isEmpty()、containsKey()、 containsValue()等。 #dates:日期工具对象,常用的方法有format()、year()、month()、hour()等。
通过变量表达式可以使用工具对象,例如,在变量表达式中,使用#strings调用length()方法。
<h1 th:text="${#strings.length('thymeleaf')}"></h1>
2.选择表达式
Thymeleaf中使用*{}包裹内容的表达式被称为选择表达式,选择表达式与变量表达式功能基本一致,只是选择表达式计算的是绑定的对象,而不是整个环境变量映射。标签中通过“th:object”属性绑定对象后,可以在该标签的后代标签中使用选择表达式(*{...})访问该对象中的属性,其中,“*”即代表绑定的对象。
<div th:object="${user}" > <p th:text="*{username}">name</p> </div>
3.消息表达式
Thymeleaf中使用#{}包裹内容的表达式被称为消息表达式,消息表达式可以显示静态文本的内容,通常与th:text属性一起使用。消息表达式显示的内容通常是读取配置文件中信息,在#{}中指定配置文件中的Key,则会在页面中显示配置文件中Key对应的Value。
<p th:text="#{location}"></p>
读取项目Properties配置文件中location属性的值。
读取指定配置文件的内容时,需要先在项目的全局配置文件中通过spring.messages.basename属性进行文件的指定。
4.链接表达式
Thymeleaf中使用@{}包裹内容的表达式被称为链接表达式,表达式中包裹的内容只能写一个绝对/相对URL地址,如果绝对/相对URL地址中包含动态参数,就需要结合变量表达式进行拼接。
以http协议开头的绝对地址。
以/开头的相对地址,Thymeleaf会将开头的斜杠(/)解析为当前工程的上下文路径ContextPath,而浏览器会自动为其添加“http://主机名:端口号”。
不以/开头的相对地址。
<link rel="stylesheet" th:href="@{/css/bootstrap.css}">
5.分段表达式
Thymeleaf中使用~{}包裹内容的表达式被称为分段表达式, 分段表达式用于在模板页面中引用其他的模板片段,该表达式支持以下两种语法结构。 1. ~{templatename::fragmentname} 2. ~{templatename::#id} templatename 模板名 fragmentname 片段名,Thymeleaf 通过 th:fragment 声明定义片段 #id id 选择器
<div th:insert="~{thymeleafDemo::title}"></div> 上述代码中,使用th:insert属性将title片段模板插入到该<div>标签中。 thymeleafDemo为模板名称,Thymeleaf会自动查找 “/resources/templates/”目录下thymeleafDemo模板中声明的名称为title的片段。
SpringBoot整合Thymeleaf
案例3:学生管理系统(简化版)
任务:创建模板hello.html,由后端提供姓名、年龄、性别等数据,作为table标签的一行内容展示到前端页面。结果如下所示:
任务优化:创建模板hello.html,由后端提供姓名、年龄、性别等数据,作为table标签的一行内容展示到前端页面。要求,姓名、年龄、性别以一个person对象的属性的形式给出。增加一个属性:上课状态classStatus,取值为0和1,分别代表有课和没课。前端表格中增加一列展示是否有课。
任务优化:创建模板hello.html,由后端提供姓名、年龄、性别等数据,作为table标签的一行内容展示到前端页面。要求,姓名、年龄、性别以一个person对象的属性的形式给出。增加一个属性:上课状态setClassStatus,取值为0和1,分别代表有课和没课。前端表格中增加一列展示是否有课。创建一个集合存放学生对象,并使用此集合为表格填充数据,要求前端展示表格行数根据后端给出的数据个数动态变化。
小结
Session(会话)
Session(会话)是 Web 开发中的一个重要概念,用于在服务器端存储用户的状态信息,使得 HTTP 无状态协议(Stateless)可以支持有状态的交互(如用户登录、购物车等)。 (1)HTTP 是无状态的
HTTP 协议本身不会记录用户的状态,每次请求都是独立的。例如: 用户登录后,下一次请求服务器仍然不知道是谁在访问。 购物车添加商品后,刷新页面数据会丢失。
(2)Session 的作用
在服务器端存储用户数据(如用户ID、权限、购物车信息)。 通过 Session ID 关联用户(通常存储在 Cookie 或 URL 中)。
Session 的工作原理
当客户端首次访问服务器时,服务器往往会创建一个Session 对象,并且为其生成一个独一无二的Session ID。这个Session ID通常会借助Cookie或者URL 重写的方式返回给客户端。之后客户端每次发起请求时,都会把这个Session ID发送给服务器,这样服务器就能依据这个ID找到对应的Session对象,进而获取与该客户端相关的信息。
1.客户端首次访问服务器时服务器检测到客户端没有携带有效的 Session ID,就会创建一个新的 Session 对象存放用户信息(登录成功后可存放:ID、用户名、权限等信息),并生成一个Session ID标识此Session对象。
2.服务器会把 Session ID 返回给客户端,借助Cookie或者URL 重写的方式。
3.客户端在后续的请求中,会在请求头的 Cookie 字段里携带这个 Session ID。
4.当客户端再次向服务器发送请求时,服务器从请求中提取 Session ID,然后根据这个 ID 找到对应的 Session 对象,从而获取存储在其中的用户信息。
5.Session 失效:Session 通常有一个过期时间,如果在一段时间内客户端没有向服务器发送请求,Session 就会失效。之后客户端再次访问时,服务器会创建一个新的 Session。
api
在Java Servlet规范中,Session通过
javax.servlet.http.HttpSession
接口实现,这是Java Web开发中处理会话的核心API。
request是一个HttpServletRequest对象
// 获取或创建Session HttpSession session = request.getSession(); // 仅获取现有Session HttpSession session= request.getSession(false); if(existingSession != null) { // 操作现有Session }
Session作为域对象的操作
HttpSession是Servlet三大域对象之一(另外两个是HttpServletRequest和ServletContext),作用域为当前会话。作为域对象,它提供了统一的数据操作方法:
// 设置属性 session.setAttribute("user", userObj); // 获取属性 User user = (User)session.getAttribute("user"); // 移除属性 session.removeAttribute("user"); // 获取所有属性名枚举 Enumeration<String> attributeNames = session.getAttributeNames();
Session生命周期控制方法
HttpSession接口提供了管理Session生命周期的关键方法:
// 获取Session ID String sessionId = session.getId(); // 设置最大非活动间隔(秒) session.setMaxInactiveInterval(30*60); // 30分钟 // 立即销毁Session session.invalidate(); // 获取创建时间 long creationTime = session.getCreationTime(); // 获取最后访问时间 long lastAccessedTime = session.getLastAccessedTime(); // 判断是否为新创建的Session boolean isNew = session.isNew();
综合案例:图书管理
任务:
(1)项目使用Spring Boot整合Thymeleaf,项目展示的book.html页面效果全部通过Thymeleaf的模板文件实现。
(2)查询所有图书。访问http://localhost:8080/book/list时,查询所有图书,并以表格的形式展示在页面中。
(3)数据动态展示。图书数据由后端数据集合(List<Book> books)给出,且前端表格行数根据后台数据变化而变化。Book对象应包含属性:id,name,author,press,status。 (4)显示图书状态(可借阅、借阅中、归还中)。图书状态列判断当前图书状态进行显示。 (5)借阅图书。当图书状态为可借阅时,对应的“借阅”按钮为可用状态,图书为其他状态时,对应的“借阅”按钮为不可用状态(提示:使用HTML标签属性的disable)。
(6)选择性显示按钮。当Session中存在用户角色为“ADMIN”时,显示“新增”按钮,否则不显示该按钮。
第一种
list接受一个Request对象,用这个对象创建会话。
第二种
可以在启动的时候,自动接受当前会话,不用手动创建会话。
第三种
html这边可以这样写。
注意,为了方便后期查询调用,books集合我们可以弄个全局的
(7)在搜索框中输入查询条件后单击“查询”按钮,按条件查询图书信息,以查询图书名称中包含“辞”字的图书信息为例。
后端创建一个Book类型对象book,若客户端表单字段名和 Book 类的属性名相匹配时,用户提交表单后,Spring MVC 会自动把表单数据绑定到 Book 对象。即可以调用book相关属性的Getter方法获取到文本框中的内容。
(8)借阅图书。当图书状态为可借阅时,对应的“借阅”按钮为可用状态,并且单击“借阅”按钮时,将当前申请借阅图书的编号异步发送到后台。
这个代码片段定义了一个名为
findBookById
的 JavaScript 函数,它使用 jQuery 的$.get()
方法向服务器发送一个 HTTP GET 请求
- 作用:通过图书 ID 向服务器发起异步请求,获取特定图书的数据。
- 请求路径:
/book/find/[id]
,其中[id]
是动态替换的参数(如/book/find/123
)。- 请求类型:GET 请求(适合查询操作,无副作用)。
注解
Spring Boot数据访问
一般情况下,应用程序中的数据都会使用数据库进行存储和管理,然后在应用程序中对数据库中的数据进行访问操作。Spring Boot在简化项目开发以及实现自动化配置的基础上,对常见的数据层解决方案提供了非常好的支持,用户只需要进行简单的配置,就可以使用数据库访问技术对数据库进行数据访问。
Spring Data概述
Spring Boot默认采用整合Spring Data的方式统一处理数据访问层,Spring Data是Spring提供的开源框架,旨在统一和简化对各种类型数据库的持久化存储。Spring Data为大量的关系型数据库和非关系型数据库提供了数据访问的方案,并且提供了基于Spring的数据访问模型,同时保留了各存储系统的特殊性。
Spring Data为开发者提供了一套统一的API,开发者也可以相同的一套API操作不同存储系统中的数据,保持代码结构的一致性,大大减少开发工作量,提高开发效率。
Spring Data提供了一套统一的Repository接口实现方式,包括基本的增删改查操作、条件查询、排序查询、分页查询等。当数据访问对象需要进行增删改查、排序查询和分页查询等操作时,开发者仅需要按照一定的规范声明接口即可,不需要实现具体的查询方法。Spring Data会根据底层数据存储系统,在运行时自动实现真正的查询方法,执行查询操作,返回结果数据集。
Spring Boot整合Spring Data JPA
Spring Data作为Spring全家桶中重要的一员,在Spring项目全球使用市场份额排名中多次居前位,而在Spring Data子项目的使用份额排名中,Spring Data JPA也一直名列前茅。Spring Boot为Spring Data JPA提供了启动器,使Spring Data JPA在Spring Boot项目中的使用更加便利。
对象关系映射(Object Relational Mapping,ORM)框架在运行时可以参照映射文件的信息,把对象持久化到数据库中,可以解决面向对象与关系数据库存在的互不匹配的现象,常见的ORM框架有Hibernate、OpenJPA等。ORM框架的出现,使开发者从数据库编程中解脱出来,把更多的精力放在业务模型与业务逻辑上,但各ORM框架之间的API差别很大,使用了某种ORM框架的系统会严重受限于该ORM的标准,基于此,SUN公司提出JPA(Java Persistence API,Java持久化API)。
任务:数据查询。使用Spring Boot整合JPA从数据库表中查询一行数据。
在pom.xml文件中添加Spring Data JPA和MySQL依赖启动器。 编写全局配置文件application.yml/application.properties设置配置信息。 编写ORM实体类 编写Repository接口 编写单元测试进行接口方法测试及整合测试
在pom.xml文件中添加Spring Data JPA和MySQL依赖启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
编写全局配置文件application.yml/application.properties设置配置信息。
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=root 当MySQL数据库的root用户未设置密码时(如新安装的本地测试数据库), 可以留空。但需确保MySQL服务配置允许无密码登录
springbootdata是
里的连接名
使用Spring Data JPA需要操作数据库,所以需要在项目中设置一些和数据库连接相关的配置信息。Spring Boot的自动装配提供了数据库连接的一些默认配置,例如数据源。Spring Boot 2.x 版本默认使用HikariCP作为数据源,如果没有显示指定使用其他数据源,项目启动后会自动使用HikariCP数据源获取数据库连接。 引入Spring Data JPA的启动器后,Spring Boot会自动装配对于JPA的默认配置,例如是否打印运行时的SQL语句和参数信息、是否根据实体自动建表等。 本项目选择采用默认的数据源,JPA的配置信息只修改打印运行时的SQL语句,其他都采用默认的配置信息。
编写ORM实体类Book
@Entity 是 Java Persistence API (JPA) 中的一个核心注解, 用于标记一个类为数据库实体类,表示该类与数据库中的一张表(Table)一一对应。 它是 JPA中定义持久化数据的关键注解。 @Table(name="book")是 JPA(Java Persistence API)中的一个注解, 用于自定义实体类与数据库表的映射关系。它通常与 @Entity 一起使用,允许开发者显式指定表名等元数据。 @Id是 JPA(Java Persistence API)中的核心注解,用于标识实体类的主键字段。 它是数据库表与 Java 对象映射(ORM)的基础,所有 @Entity 类必须包含一个 @Id 注解的字段。 @GeneratedValue(strategy = GenerationType.IDENTITY)
编写Repository接口
在 Spring Data JPA 里,CrudRepository<T, ID> 是一个接口, 它提供了基础的 CRUD(创建、读取、更新、删除)操作方法。 这些方法并非需要手动编写实现,而是由 Spring Data JPA 框架自动生成实现。 Spring Data JPA在运行时为 CrudRepository 及其子接口生成具体的实现类。 在启动 Spring 应用时,Spring Data JPA 会扫描继承了 CrudRepository 的接口。
编写单元测试进行接口方法测试及整合测试
Optional是 Java 8 引入的一个容器类,位于java.util包下,用于表示一个值存在或不存在,主要是为了解决空指针异常(NullPointerException)的问题。比如:在传统的 Java 编程中,null值是一个常见的错误源头。当调用一个null对象的方法或者访问其属性时,就会触发NullPointerException。这就要求开发者在使用对象之前手动检查是否为null,使得代码中充斥着大量的null检查逻辑,不仅繁琐,还容易遗漏。Optional类型能够清晰地表达一个方法的返回值可能为空,使调用者在使用时就知道需要处理这种情况。这提高了代码的可读性,让其他开发者更容易理解代码的意图。
Optional.isPresent():若 Optional 包含非 null 值,则返回 true;否则返回 false。 Optional.get():获取Optional对象中所包含的值。 Book b = bookRepository.findById(1).get();
注意,接口不能被实例化,但因为curd接口及其子接口门会被自动生成实现类,所以这里的bookRepository变量接受的对象其实是BookRepository接口的实现类的对象。
扩展
任务:数据查询。使用Spring Boot整合JPA从数据库表中查询一行数据,要求通过作者和状态查询。
这个查询要求转换成sql就是这样
SELECT * FROM books WHERE author = '指定作者' AND status = '指定状态';
更新Repository接口,同时编写数据查询方法。
编写单元测试进行接口方法测试及整合测试。
Spring Data JPA 会依据方法名的命名规则来解析方法名,进而生成对应的 SQL 查询语句。
1.父接口的方法
如果自定义接口继承了JpaRepository接口,则可以直接使用JpaRepository接口提供的方法。
2.根据方法命名规则定义方法
Spring Data中按照框架的规范自定义了Repository接口,除了可以使用接口提供的默认方法外,还可以按特定规则来定义查询方法,只要这些查询方法的方法名遵守特定的规则,不需要提供方法实现体,Spring Data就会自动为这些方法生成查询语句。Spring Data对这种特定的查询方法的定义规范如下。
以find、read、get、query、count开头。
涉及查询条件时,条件的属性使用条件关键字连接,并且条件属性的首字母大写。
Book findByAuthorAndStatus(String author,String status);Book findByAuthorAndStatus(String author,String status);
3.JPQL
使用Spring Data JPA提供的查询方法已经可以满足大部分应用场景的需求,但是有些业务需要更灵活的查询条件,这时就可以使用@Query注解,结合JPQL的方式来完成查询。 JPQL是JPA中定义的一种查询语言,此种语言旨在让开发者忽略数据库表和表中的字段,而关注实体类及实体类中的属性。 JPQL语句的写法和SQL语句的写法十分类似,但是要把查询的表名换成实体类名称,把表中的字段名换成实体类的属性名称。
JPQL支持命名参数和位置参数两种查询参数。
命名参数:在方法的参数列表中,使用@Param注解标注参数的名称,在@Query注解的查询语句中,使用“:参数名称” 匹配参数名称。
位置参数:在@Query注解的查询语句中,使用“?位置编号的数值” 匹配参数,查询语句中参数标注的编号需要和方法的参数列表中参数的顺序依次对应。
//命名参数绑定 @Query("from Book b where b.author=:author and b.name=:name") List<Book> findByCondition1(@Param("author") String author, @Param("name") String name); //位置参数绑定 @Query("from Book b where b.author=?1 and b.name=?2") List<Book> findByCondition2(String author, String name);
JPQL中使用like模糊查询、排序查询、分页查询子句时,其用法与SQL中的用法相同,区别在于JPQL处理的类的实例不同。
//like模糊查询 @Query("from Book b where b.name like %:name%") List<Book> findByCondition3(@Param("name") String name); //排序查询 按照图书的 id 倒序排列结果 @Query("from Book b where b.name like %:name% order by id desc") List<Book> findByCondition4(@Param("name") String name); //分页查询 @Query("from Book b where b.name like %:name%") Page<Book> findByCondition5(Pageable pageable, @Param("name") String name);
任务:将数据进行分页,每页2行数据然后进行分页查询。
1.
//增加三行数据 INSERT INTO book (name, author, press, status) VALUES ( '楚辞', '屈原', '中国文联出版社', '0'); INSERT INTO book (name, author, press, status) VALUES ( '纳兰词', '纳兰性德', '中国文联出版社', '1'); INSERT INTO book (name, author, press, status) VALUES ( '西游记', '吴承恩', '中国文联出版社', '2');
Pageable pageable:方法的第一个参数,Pageable 是 Spring Data 提供的接口,用于封装分页和排序信息。通过传入 Pageable 对象,可以指定查询的页码、每页记录数和排序规则等。
2行一页,页数从0开始,所以显示的是id6和id7的记录。
4.原生SQL
如果出现非常复杂的业务情况,导致JPQL和其他查询都无法实现对应的查询,需要自定义SQL进行查询时,可以在@Query注解中定义该SQL。@Query注解中定义的是原生SQL时,需要在注解使用nativeQuery=true指定执行的查询语句为原生SQL,否则会将其当作JPQL执行
@Query(value="SELECT * FROM book WHERE id = :id",nativeQuery=true) Book findByCondition6(@Param("id") Integer id);
使用@Query注解可以执行JPQL和原生SQL查询,但是@Query注解无法进行DML数据操纵语言,主要语句有INSERT、DELETE和UPDATE操作,如果需要更新数据库中的数据,需要在对应的方法上标注@Modifying注解,以通知Spring Data当前需要进行的是DML操作。需要注意的是JPQL只支持DELETE和UPDATE操作,不支持INSERT操作。
任务:完成图书数据的增删改查。
1.增加一本书:"东游记","吴元泰","中国文联出版公司","1"
save是接口自带的
注意,id我这里是设置了自增,所以我这里添加的id无效,表里还是4
2.将第一本书出版社修改为:"中华书局"
主建已经存在的情况下,默认是修改
3.删掉第一本书的数据。
注解
案例
在前面的基础上,实现让数据库数据显示在前端,并且通过前端的新增按钮实现数据增加
新增了addBooks页面
-----------
增加按钮的链接跳转,发起一个请求,对应请求如下
----------------------------------------------
该页面的新增按钮会发起一个新的请求
重定向
Spring Boot整合MyBatis
MyBatis是半自动化的ORM实现,支持定制化SQL、存储过程和高级映射,其封装性低于 Hibernate,但性能优秀、小巧、简单易学,在国内备受开发人员的喜爱。
任务:使用Spring Boot整合MyBatis,完成数据访问操作。
数据准备:创建数据库、数据表并插入一定的数据。
创建项目,引入相应的启动器:使用Spring Initializr的方式构建项目,选择MySQL和MyBatis依赖,编写实体类。
编写配置文件:在配置文件中进行数据库连接配置以及进行第三方数据源的默认参数覆盖。
对数据进行操作:写增删改查的方法。
测试。
数据准备:
创建数据库springbootdata、数据表t_article、t_comment并插入一定的数据。
CREATE TABLE `t_article` ( `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '文章id', `title` varchar(200) DEFAULT NULL COMMENT '文章标题', `content` longtext COMMENT '文章内容', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `t_article` VALUES ('1', 'Spring Boot基础入门', '从入门到精通讲解...'); INSERT INTO `t_article` VALUES ('2', 'Spring Cloud基础入门', '从入门到精通讲解...'); CREATE TABLE `t_comment` ( `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '评论id', `content` longtext COMMENT '评论内容', `author` varchar(200) DEFAULT NULL COMMENT '评论作者', `a_id` int(20) DEFAULT NULL COMMENT '关联的文章id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO `t_comment` VALUES ('1', '很全、很详细', '狂奔的蜗牛', '1'); INSERT INTO `t_comment` VALUES ('2', '赞一个', 'tom', '1'); INSERT INTO `t_comment` VALUES ('3', '很详细', 'kitty', '1'); INSERT INTO `t_comment` VALUES ('4', '很好,非常详细', '张三', '1'); INSERT INTO `t_comment` VALUES ('5', '很不错', '张杨', '2');
创建项目,引入相应的启动器:
使用Spring Initializr的方式构建项目,选择MySQL和MyBatis依赖,编写实体类。
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
创建项目,引入相应的启动器:
使用Spring Initializr的方式构建项目,选择MySQL和MyBatis依赖,编写实体类Comment和Article,并生成实例存入Spring容器。
编写配置文件:
在配置文件中进行数据库连接配置以及进行第三方数据源的默认参数覆盖。
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=root
因为当MySQL数据库的root用户未设置密码时(如新安装的本地测试数据库),
可以留空。但需确保MySQL服务配置允许无密码登录
对数据进行操作:使用注解方式整合MyBatis
写增删改查的方法——使用注解方式整合MyBatis
创建Mapper接口文件:
@Mapper:
如果在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类。
新建ArticleMapper和CommentMapper
编写访问方法
//查询数据操作: @Select("SELECT * FROM t_comment WHERE id =#{id}") public Comment findById(Integer id); //插入数据操作: @Insert("INSERT INTO t_comment(content,author,a_id) " +"values (#{content},#{author},#{aId})") public int insertComment(Comment comment); //更新数据操作: @Update("UPDATE t_comment SET content=#{content} WHERE id=#{id}") public int updateComment(Comment comment); //删除数据操作: @Delete("DELETE FROM t_comment WHERE id=#{id}") public int deleteComment(Integer id);
编写测试方法进行接口方法测试及整合测试。
@SpringBootTest public class MyBatisTests { @Autowired private CommentMapper commentMapper; @Test public void selectComment() { Comment comment = commentMapper.findById(1); System.out.println(comment); } }
------------
测试结果:
会发现第一张图aId显示null,这是因为
数据库的字段名是a_id,而MyBatis 会严格按名称匹配(需字段名与属性名完全一致),但Comment类里是aId,两者不一致。此时 a_id 无法映射到
aId
,导致查询结果为null
。因此解决方案就是:全局配置开启自动驼峰命名转换,
全局配置文件中配置:mybatis.configuration.map-underscore-to-camel-case=true
如果是jpa,就这样
对数据进行操作:使用注解方式整合MyBatis
创建Mapper接口文件:
@Mapper public interface ArticleMapper { public Article selectArticle(Integer id); public int updateArticle(Article article); }
创建XML映射文件:编写对应的SQL语句。创建XML映射文件ArticleMapper.xml
id和result都是映射,但是id适合主建。
collection是对集合类型的映射,property是集合对象的名字,ofType是集合里存的对象
方法是updateArticle,参数是Article
在全局文件中配置XML映射文件路径以及实体类别名映射路径
#配置MyBatis的xml配置文件路径 mybatis.mapper-locations=classpath:mapper/*.xml #配置XML映射文件中指定的实体类别名路径 mybatis.type-aliases-package=com.example.class10_thymeleaf
calssspath是指resource目录。因为我们的xml里面指定的实体类是Article,所以给指定路径。
编写测试方法进行接口方法测试及整合测试
Spring Boot整合Redis
缓存
缓存是分布式系统中的重要组件,主要解决数据库数据的高并发访问。在实际开发中,尤其是用户访问量较大的网站,用户对高频热点数据的访问非常频繁,为了提高服务器访问性能、减少数据库的压力、提高用户体验,使用缓存显得尤为重要。
Spring Boot 默认缓存体验
完成任务:使用SpringBoot整合JPA对book表做数据访问,要求在控制台输出执行数据库操作时所生成的 SQL 语句。
当把 spring.jpa.show-sql 属性设置为 true 时,Spring Data JPA 会在控制台输出它在执行数据库操作时所生成的 SQL 语句。
全局配置文件中配置: spring.jpa.show-sql=true
编写控制器MyBatisController实现前端发送http://localhost:8080/get/2请求,从数据表book中查询id=2的一行数据显示到浏览器页面,同时控制台输出操作时所生成的 SQL 语句。
@RestController public class MyBatisController { @Autowired private BookRepository bookRepository; @GetMapping("/get/{id}") public Book findById(@PathVariable("id") Integer id){ return bookRepository.findById(id).get(); } }
每次刷新都要访问一次数据库,所以要引入缓存。
缓存搭建
使用@EnableCaching注解开启基于注解的缓存支持(主程序启动类上方)
使用@Cacheable注解对数据操作方法进行缓存管理
cache之后,第一次调用findById后,会创建一个叫comment的缓存空间,将查询结果发给这个缓存空间一份,再返回一份给控制台和前端。第二次调同一个id,会先到缓存空间里找,没找到再去调函数,有就直接返回。
Spring Boot支持的缓存组件有:
(1)Generic (2)JCache (JSR-107) (EhCache 3、Hazelcast、Infinispan等) (3)EhCache 2.x (4)Hazelcast (5)Infinispan (6)Couchbase (7)Redis (8)Caffeine (9)Simple(默认)
主要注解
@EnableCaching注解 @Cacheable注解 @CachePut注解 @CacheEvict注解 @Caching注解 @CacheConfig注解
@EnableCaching是由Spring框架提供的,Spring Boot框架对该注解进行了继承,该注解需要配置在类上(在Spring Boot中,通常配置在项目启动类上),用于开启基于注解的缓存支持。
@Cacheable注解也是由Spring框架提供的,可以作用于类或方法(通常用在数据查询方法上),用于对方法结果进行缓存存储。
@Cacheable注解的执行顺序是,先进行缓存查询,如果为空则进行方法查询,并将结果进行缓存;如果缓存中有数据,不进行方法查询,而是直接使用缓存数据。
@CachePut注解是由Spring框架提供的,可以作用于类或方法(通常用在数据更新方法上),该注解的作用是更新缓存数据。@CachePut注解的执行顺序是,先进行方法调用,然后将方法结果更新到缓存中。@CachePut注解也提供了多个属性,这些属性与@Cacheable注解的属性完全相同。
@CacheEvict注解是由Spring框架提供的,可以作用于类或方法(通常用在数据删除方法上),该注解的作用是删除缓存数据。@CacheEvict注解的默认执行顺序是,先进行方法调用,然后将缓存进行清除。@CacheEvict注解也提供了多个属性,这些属性与@Cacheable注解的属性基本相同,除此之外,还额外提供了两个特殊属性allEntries和beforeInvocation。
(1)allEntries属性
allEntries属性表示是否清除指定缓存空间中的所有缓存数据,默认值为false(即默认只删除指定key对应的缓存数据)。
(2)beforeInvocation属性
beforeInvocation属性表示是否在方法执行之前进行缓存清除,默认值为false(即默认在执行方法后再进行缓存清除)。@CacheConfig注解使用在类上,主要用于统筹管理类中所有使用@Cacheable、@CachePut和@CacheEvict注解标注方法中的公共属性,这些公共属性包括有cacheNames、keyGenerator、cacheManager和cacheResolver。
@CacheConfig注解使用(示例代码):@CacheConfig(cacheNames = "comment") @Service public class CommentService { @Autowired private CommentRepository commentRepository; @Cacheable public Comment findById(int comment_id){ Comment comment = commentRepository.findById(comment_id).get(); return comment; } }
Redis
概念
随着互联网Web 2.0的兴起,关系型数据库在处理超大规模和高并发的Web 2.0网站的数据时存在一些不足,需要采用更适合解决大规模数据集合、多重数据种类的数据库,通常将这种类型的数据库统称为非关系型数据库(No SQL,Not Only SQL)。常见的非关系型数据库有MongoDb、Redis等,其中Redis以超高的性能、完美的文档和简洁易懂的源码受到广大开发人员的喜爱。下面将对Redis的相关知识和Spring Boot整合Redis进行讲解。
-----------------
Redis(Remote Dictionary Server,远程字典服务)是一个基于内存的键值型非关系型数据库,以Key-Value的形式存储数据。Redis中存储键(Key)、值(Value)的方式和Java中的HashMap类似,键和值是映射关系。在同一个库中,Key是唯一不可重复的,每一个Key对应一个Value。键值存储的本质就是使用Key标示Value,当想要检索Value时,必须使用与Value相对应的Key进行查找。
-----------------
Redis与传统的关系型数据库截然不同,Redis没有提供手动创建数据库的语句,Redis启动后会默认创建16个数据库,用0~15进行编号,默认使用编号为0的数据库。
相较于其他的键值存储系统,Redis主要有以下优点。
存取速度快:Redis 基于内存来实现数据存取,相对于磁盘来说,其读写速度要高出好几个数量级,每秒可执行大约110000次的设置操作,或者执行81000次的读取操作。
支持丰富的数据类型:Redis支持开发人员常用的大多数数据类型,例如列表、集合、有序集合和散列等。
操作具有原子性:所有Redis操作都是原子操作,这使得两个客户端并发访问时,Redis服务器能接收更新后的值。
提供多种功能:Redis提供了多种功能特性,可用作非关系型数据库、缓存中间件、消息中间件等。
安装和启动
要想使用非关系型数据库Redis,必须先安装Redis。Redis可以在Windows系统和Linux系统安装,也可以通过Docker镜像来安装,不同安装方式的安装过程也不相同。为了方便操作,此处选择在Windows平台下进行Redis安装。
启动redis-server.exe
出现
即可
Redis自带的客户端工具有时侯使用起来并不是特别方便,读者也可以使用一些图形化Redis客户端管理软件管理Redis。常用的有Redis Desktop Manager。
localhost是我自己写的名字,host是127.0.0.1
Redis使用
项目采用前面默认的。
添加Spring Data Redis 依赖启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
Redis服务连接配置
spring.data.redis.host=127.0.0.1 spring.data.redis.port=6379 spring.data.redis.password=
使用@Cacheable注解定制缓存管理
你使用的 DefaultSerializer 序列化器要求被序列化的对象必须实现 java.io.Serializable 接口,然而 现在实体类并没有实现该接口。
在 Java 里,若要对一个对象进行序列化(也就是把对象转化为字节流,方便存储或者传输),这个对象所属的类必须实现 Serializable 接口。DefaultSerializer 是 Spring 框架默认的序列化器,它遵循这个规则。要是传入的对象没有实现 Serializable 接口,就会抛出这个异常。
序列化(Serialization)是指将 对象 转换为 可存储或传输的格式(如字节流、JSON、XML),以便在需要时能重新恢复为原始对象。反序列化(Deserialization)则是相反的过程,将序列化后的数据重新转换为对象。
将缓存对象实现序列化
基于注解的Redis缓存查询测试
但目前过于简陋,再整合下。加个服务层,写入业务逻辑。(之前类似的项目都是在test里完成的,服务层相比test更正式)
编写业务操作类Service文件CommentService,在该类中编写数据的查询、修改和删除操作
注解
编写Web访问层Controller文件CommentController,使用注入的CommentService实例对象编写对Comment评论数据的查询、修改和删除方法。
我们之前都是直接从控制器对数据访问层进行操作。
而现在,是控制器-》服务层-》数据访问层,也是非常正式的访问流程(浏览器-前端页面-控制器controller-服务层service-数据访问层(jpa、mapper等),数据库(mysql等))
完成任务
浏览器输入http://localhost:8080/get/2显示t_comment表中id=2的数据
浏览器输入http://localhost:8080/update/2/xiaoMing 将t_comment表中id=2行的作者更新成xiaoMing并返回更新后的数据。
cacheput可以更新缓存。
注意,如果是返回commentService.findById(id),会出问题,之前缓存注解放在了数据访问层,但因为有了服务层,缓存注解就应该是在服务层才行。
但问题不止,实际上,这个业务逻辑中调用了多次findbyid的方法,每次都是插入了新的数据在缓存中,并没有我们想的一样更新缓存。
当没有显式指定 key 时,Spring Cache 会根据默认的键生成策略来为缓存项生成对应的键。
Spring Cache 的默认键生成器(SimpleKeyGenerator)根据方法参数生成键,规则如下:
无参数:键为 SimpleKey.EMPTY(字符串表示为 "")。
单个参数:键为参数本身(如 4、"user1")。
多个参数:键为 SimpleKey(params...)(如 SimpleKey[userId, categoryId])。因为我们方法的参数是单个的Comment对象,所以key就是这个Comment,但是我们get的时候调用的findbyid,键默认是id这个integer类型,两者不相等。
这样就可以了。
注解
Spring Cache 的默认键生成器(SimpleKeyGenerator)根据方法参数生成键,规则如下:
无参数:键为 SimpleKey.EMPTY(字符串表示为 "")。
单个参数:键为参数本身(如 4、"user1")。
多个参数:键为 SimpleKey(params...)(如 SimpleKey[userId, categoryId])。-------------------
@CacheEvict注解是由Spring框架提供的,可以作用于类或方法(通常用在数据删除方法上),该注解的作用是删除缓存数据。@CacheEvict注解的默认执行顺序是,先进行方法调用,然后将缓存进行清除。@CacheEvict注解也提供了多个属性,这些属性与@Cacheable注解的属性基本相同,除此之外,还额外提供了两个特殊属性allEntries和beforeInvocation。
(1)allEntries属性
allEntries属性表示是否清除指定缓存空间中的所有缓存数据,默认值为false(即默认只删除指定key对应的缓存数据)。
(2)beforeInvocation属性
beforeInvocation属性表示是否在方法执行之前进行缓存清除,默认值为false(即默认在执行方法后再进行缓存清除)。
浏览器输入http://localhost:8080/delete/2 将t_comment表中id=2的一行删掉
注意,数据访问层这里不需要手动写delete方法,因为会自动生成(参考前面jpa的说明)。
在service层
在控制器
然后就成功了。
但有个问题,因为一些意外,我们的缓存里面存着很多不一致的数据(前面实验的过程中积累的)。我们再弄个请求清空缓存。
基于API的Redis缓存实现
RedisTemplate 是 Spring Data Redis 提供的核心工具类,用于简化与 Redis 数据库的交互操作。它封装了 Redis 客户端(如 Jedis、Lettuce)的底层细节,提供了更高级、类型安全的 API 来操作 Redis 的各种数据结构(如 String、Hash、List、Set、ZSet 等)。
使用Redis API 进行业务数据缓存管理
编写一个进行业务处理的类ApiCommentService,使用@Autowired注解注入Redis API中常用的RedisTemplate;然后在数据查询、修改和删除三个方法中,根据业务需求分别进行数据缓存查询、缓存存储、缓存更新和缓存删除。同时,Comment数据对应缓存管理的key值都手动设置了一个前缀“comment_”,这是针对不同业务数据进行缓存管理设置的唯一key,避免与其他业务缓存数据的key重复。
浏览器输入http://localhost:8080/api/get/3查询缓存中id=3的数据。
浏览器输入http://localhost:8080/api/update/3/xiaoMing 将缓存中id=3行的作者更新成xiaoMing并返回更新后的数据。
自定义RedisTemplate
Redis API 默认序列化机制
基于Redis API的Redis缓存实现是使用RedisTemplate进行数据缓存操作的,这里打开RedisTemplate类,查看源码可知:
使用RedisTemplate进行Redis数据缓存操作时,内部默认使用的是JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接口(例如Serializable);
使用RedisTemplate进行Redis数据缓存操作时,如果自定义了缓存序列化方式defaultSerializer,那么将使用自定义的序列化方式。
---------在项目中引入Redis依赖后,Spring Boot提供的RedisAutoConfiguration自动配置会生效。打开RedisAutoConfiguration类,查看内部源码中关于RedisTemplate的定义方式可知:
在Redis自动配置类中,通过Redis连接工厂RedisConnectionFactory初始化了一个RedisTemplate;该类上方添加了@ConditionalOnMissingBean注解(顾名思义,当某个Bean不存在时生效),用来表明如果开发者自定义了一个名为redisTemplate的Bean,则该默认初始化的RedisTemplate会被覆盖。
如果想要使用自定义序列化方式的RedisTemplate进行数据缓存操作,可以参考上述核心代码创建一个名为redisTemplate的Bean组件,并在该组件中设置对应的序列化方式即可。
自定义RedisTemplate序列化机制
在项目中创建创建一个Redis自定义配置类RedisConfig,通过@Configuration注解定义一个RedisConfig配置类,并使用@Bean注解注入一个默认名称为方法名的redisTemplate组件(注意,该Bean组件名称必须是redisTemplate)。在定义的Bean组件中,自定义了一个RedisTemplate,使用自定义的Jackson2JsonRedisSerializer数据序列化方式;使用redisTemplate.setDefaultSerializer更新序列化方式。在定制序列化方式中,定义了一个ObjectMapper用于进行数据转换设置。
RedisConnectionFactory 是 Spring Data Redis 中用于创建 Redis 连接的核心接口,它封装了与 Redis 服务器建立连接的细节。在使用 RedisTemplate 时,通常需要通过 RedisConnectionFactory 来配置连接信息。
Spring Boot安全管理
实际开发中,开发者为了确保Web应用的安全性,通常需要保护Web应用的用户信息、数据信息等资源不受侵害,例如,对于某些指定的功能,需要先对访问的用户进行身份验证,验证通过后还需要具备相关权限之后才可以操作。下面将对Spring Boot的安全管理进行详细地讲解。
引例
综合案例:图书管理
任务:
(1)访问http://localhost:8080/book/list时,进入图书展示与借阅页面(books.html)。
(2)单击借阅按钮,状态修改成“借阅中”。
(3)访问http://localhost:8080/book/admin/manag时进入图书管理页面(book_manag.html)。
(4)点击编辑按钮实现:跳转到编辑页面:updateBooks.html,页面中获取被编辑行数据。单选按钮默认被选中属性为th:checked。
(5)确认修改:将修改后的内容存到数据库中并返回管理员首页(book_manag.html)。
(6)点击删除按钮实现:将此行数据删除,并返回管理员首页(book_manag.html)。
(7)编写身份认证:要求:不登录账号无法进行操作。
思考:本网站应有几类用户?
任务1,前面有类似的,不展示了。
(2)单击借阅按钮,状态修改成“借阅中”。
重点是这个
重定向不支持某个页面数据修改,重新定向。必须这样强制刷新前端页面。
(3)访问http://localhost:8080/book/admin/manag时进入图书管理页面(book_manag.html)。
(4)点击编辑按钮实现:跳转到编辑页面:updateBooks.html,页面中获取被编辑行数据。单选按钮默认被选中属性为th:checked。
其中window.location.href就是
ps:上面的action暂时用不到
(5)确认修改:将修改后的内容存到数据库中并返回管理员首页(book_manag.html)。
(6)点击删除按钮实现:将此行数据删除,并返回管理员首页(book_manag.html)。
(7)编写身份认证:要求:不登录账号无法进行操作。
看下面
安全框架概述
Java中的安全框架通常是指解决Web应用安全问题的框架,如果开发Web应用时没有使用安全框架,开发者需要自行编写代码增加Web应用安全性。自行实现Web应用的安全性并不容易,需要考虑不同的认证和授权机制、网络关键数据传输加密等多方面的问题,为此Web应用中通常会选择使用一些成熟的安全框架,这些安全框架基本都提供了一套Web应用安全性的完整解决方案,以便提升Web应用的安全性。
Java中常用的安全框架有Spring Security和Shiro,这两个安全框架都提供了强大功能,可以很容易实现Web应用的很多安全防护。下面对这两个安全框架的特点进行讲解。
1.Spring Security
Spring Security是Spring生态系统中重要的一员,是一个基于AOP思想和Servlet过滤器实现的安全框架,它提供了完善的认证机制和方法级的授权功能,是一款非常优秀的权限管理框架。Spring Security伴随着Spring生态系统不断修正、升级,使用Spring Security 减少了为企业系统安全控制编写大量重复代码的工作,在Spring Boot项目中使用Spring Security十分简单。
-----------------------------------
使用Spring Security可以很方便地实现Authentication(认证) 和 Authorization(授权),其中认证是指验证用户身份的过程,授权是指验证用户是否有权限访问特定资源的过程。Spring Security在架构上将认证与授权分离,并提供了扩展点。
Spring Security具有以下的特点。
灵活:Spring Security 提供了一系列可扩展的模块,可以根据具体需求进行选择和配置。
安全:Spring Security 集成了一系列安全措施,包括 XSS ( Cross-Site Scripting ,跨站脚本)攻击防范、CSRF 攻击防范、点击劫持攻击防范等。
易用:Spring Security 提供了一系列快捷配置选项,可以使开发人员轻松地实现认证和授权等功能。
社区支持:Spring Security作为Spring 生态系统的一部分,与Spring无缝整合,并且得到了社区广泛的支持和更新维护。-----------------------------
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关功能抽取出来,实现用户身份认证,授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
Shiro具有如下特点。
易于理解的 Java Security API。
简单的身份认证,支持LDAP,JDBC 等多种数据源。
支持对角色的简单鉴权,也支持细粒度的鉴权。
支持一级缓存,以提升应用程序的性能。
内置的基于 POJO 企业会话管理,适用于Web以及非Web的环境。
不跟任何的框架或者容器捆绑,可以独立运行。
-------------------不管Spring Security还是Shiro,在进行安全管理的过程中都涉及权限管理的两个重要概念:认证和授权。权限管理是指根据系统设置的安全规则或者安全策略,用户可以访问且只能访问自己被授权的资源。
实现权限管理通常需要三个对象,分别为用户、角色、权限,这三个对象的说明如下。
用户:主要包含用户名、密码和当前用户的角色信息,可以实现认证操作。
角色:主要包含角色名称、角色描述和当前角色拥有的权限信息,可以实现授权操作。
权限:权限也可以称为菜单,主要包含当前权限名称、url地址等信息,可以实现动态展示菜单。
Spring Security基础入门
为了更好地使用Spring Boot整合实现Spring Security安全管理功能,体现案例中Authentication(认证)和Authorization(授权)功能的实现,本案例在Spring Boot项目中结合Spring MVC和Thymeleaf实现访问图书管理后台页面。
---------------------
基础认知
WebMvcConfigurer 是 Spring MVC 提供的一个核心接口,用于自定义 Spring Boot 应用的 MVC 配置(如拦截器、视图解析、静态资源等)。
1. 拦截器(Interceptor)用于在请求到达 Controller 之前、Controller 方法执行之后以及视图渲染之后进行拦截处理,常用于:权限验证(登录检查);日志记录;参数预处理;全局异常捕获(结合 @ControllerAdvice)。
2. 视图解析器(ViewResolver)将 Controller 返回的逻辑视图名(如 "home")解析为具体的视图实现(如 JSP、Thymeleaf、FreeMarker 等)。
3. 静态资源处理:处理静态资源(如 CSS、JS、图片),避免被 DispatcherServlet 拦截。默认规则(Spring Boot)静态资源默认放在以下目录:
classpath:/static/
classpath:/public/
classpath:/resources/
访问路径:http://localhost:8080/文件名
4. 格式化器(Formatter)是 Spring MVC 中用于处理数据类型转换和格式化显示的核心组件,主要用于将字符串请求参数转换为目标类型(如日期、数字、自定义对象等),或将对象格式化为字符串用于显示。
WebMvcConfigurer 接口允许开发者通过注册拦截器、视图解析器、静态资源处理、格式化器等来自定义 Spring MVC 的行为。
----------------------
ViewControllerRegistry 是 Spring MVC 中用于快速注册简单 URL 到视图映射的工具类,属于 WebMvcConfigurer 配置体系的一部分。它允许开发者在不编写控制器方法的情况下,直接将 URL 路径映射到视图。
案例续
综合案例:图书管理
任务:编写身份认证:要求:不登录账号无法进行操作;登录成功后按权限进行操作。
不登录用户账号,无法访问http://localhost:8080/book/list,图书展示与借阅页面(books.html)。
不登录管理员账号,无法访问http://localhost:8080/book/admin/manag,图书管理页面(book_manag.html)。
-----------------------------------------------
任务1-1:访问http://localhost:8080/则跳转到后台管理首页。
在项目的class10_thymeleaf目录下创建包config,并在该包下创建配置类WebMvcConfig实现WebMvcConfigurer 。
自定义视图控制器:添加视图路径映射,实现访问项目首页自动映射到后台管理首页。
请求?http://localhost:8080/
响应?book_manag.html
但这样的话,是单纯的页面,没有数据库的数据。
要这样才行。
添加身份认证:引入Security。
在Spring Boot项目中使用Security,必须保证引入Security依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
启动项目,在浏览器访问“http://localhost:8080/”。
查看IDEA控制台信息,信息中包含一些特别的内容。
在默认登录页面中使用Spring Security提供的账号(user),以及生成的随机密码进行登录。
Spring Security结构总览
当初始化Spring Security时,会创建一个类型为org.springframework.security.web.FilterChainProxy,名称为springSecurityFilterChain过滤器,这个过滤器实现了javax.servlet.Filter接口,外部请求系统资源时会经过此过滤器。
Spring Security安全管理的实现主要是由过滤器链中一系列过滤器相互配合完成,下面对过滤器链中主要的几个过滤器及其作用分别进行说明。
SecurityContextPersistenceFilter:是整个拦截过程的入口和出口,也就是第一个和最后一个拦截器。
UsernamePasswordAuthenticationFilter:用于处理来自表单提交的认证,提交的表单必须提供对应的用户名和密码。
FilterSecurityInterceptor:用于保护Web资源,获取所配置资源访问的授权信息。
CsrfFilter:Spring Security会对所有Post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错,起到防止csrf攻击的效果。
ExceptionTranslationFilter:能够捕获来自FilterChain所有的异常,并进行处理。
DefaultLoginPageGeneratingFilter:如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。
Spring Security认证管理
Spring Security认证流程
认证是Spring Security的核心功能之一,Spring Security所提供的认证可以更好地保护系统的隐私数据与资源,只有当用户的身份合法后方可访问该系统的资源。Spring Security提供了默认的认证相关配置,开发者也可以根据自己实际的环境进行自定义身份认证配置。下面对Spring Security的认证流程以及自定义认证进行讲解。
用户认证就是判断一个用户的身份是否合法的过程,用户访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,否则拒绝其访问。
Spring Security的认证流程进行详细介绍。
① 用户提交用户名和密码进行认证请求后,被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter过滤器获取到,将用户名和密码封装到UsernamePasswordAuthenticationToken对象中,该对象为Authentication的实现类。
② 过滤器将封装用户名和密码的Authentication对象提交至AuthenticationManager(认证管理器)进行认证。
③ AuthenticationManager根据当前的认证类型进行认证,认证时会根据提交的用户信息最终返回一个SpringSecurity的UserDetails对象,如果返回的UserDetails对象为空,则说明认证失败,抛出异常。
④ 如果返回的UserDetails对象不为空,则返回UserDetails对象,最后AuthenticationManager 认证管理器返回一个被填充满了信息的Authentication 实例,包括权限信息, 身份信息,细节信息,但密码通常会被移除。
⑤ SecurityContextHolder安全上下文容器存放填充了信息的Authentication,认证成功后通过 SecurityContextHolder.getContext().setAuthentication()方法,将Authentication设置到其中。
Spring Security自定义身份认证
尽管项目启动时,Spring Security会提供了默认的用户信息,可以快速认证和启动,但大多数应用程序都希望使用自定义的用户认证。对于自定义用户认证,Spring Security提供了多种认证方式,常用的有In-Memory Authentication(内存身份认证)、JDBC Authentication(JDBC身份认证)和UserDetailsService(身份详情服务)。下面对Spring Security的这三种自定义身份认证进行详细讲解。
1.内存身份认证
以内存身份认证时,需要在Spring Security的相关组件中进行指定当前认证方式为内存身份认证。Spring Security 5.7.1开始Spring Security将WebSecurityConfigurerAdapter类标注为过时,推荐直接声明配置类,在配置类中直接定义组件的信息。
本文使用Spring Boot 2.7.6,其对应的Spring Security版本为5.7.5。自定义内存身份认证时,可以通过InMemoryUserDetailsManager类实现,InMemoryUserDetailsManager是UserDetailsService的一个实现类,方便在内存中创建一个用户。对此,只需在自定义配置类中创建InMemoryUserDetailsManager实例,在该实例中指定该实例的认证信息,
目的:在内存中创建一个用户。
谁来做这个任务(实例对象?):InMemoryUserDetailsManager类的实例对象可以做到。
只需在自定义配置类中创建InMemoryUserDetailsManager实例,在该实例中指定该实例的认证信息,并存入在Spring容器中即可(Spring承认)。
(1)创建配置类
在config的包,并在该包下创建一个配置类WebSecurityConfig,在该类中创建UserDetailsService类型的InMemoryUserDetailsManager实例对象交由Spring容器管理。
其中:InMemoryUserDetailsManager创建用户的方法为:createUser()。
users.createUser() 是 Spring Security 中 UserDetailsManager 接口提供的方法,用于创建和存储新的用户账户。此方法需要一个UserDetails参数。
UserDetails 是 Spring Security 框架中的一个核心接口,它代表了应用程序中用户的核心信息。这个接口为 Spring Security 的认证和授权过程提供了必要的用户信息,比如用户名、密码、权限等。
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager(); users.createUser(User.withUsername("zhangsan") .password("{noop}123").roles("admin").build());
(2)验证内存身份认证
启动项目后,查看控制台输出的信息,发现没有默认安全管理时随机生成了密码。在浏览器访问项目首页“http://localhost:8080/”。
如果用不是自定义的用户信息登录,会提示用户名或密码错误。
进行自定义用户认证时,需要注意以下几个问题。
提交认证时会对输入的密码使用密码编译器进行加密并与正确的密码进行校验。如果不想要对输入的密码进行加密,需要在密码前对使用{noop}进行标注。
从Spring Security 5开始,自定义用户认证如果没有设置密码编码器,也没有在密码前使用{noop}进行标注,会认证失败。
自定义用户认证时,可以定义用户角色roles,也可以定义用户权限authorities,在进行赋值时,权限通常是在角色值的基础上添加“ROLE_”前缀。
自定义用户认证时,可以为某个用户一次指定多个角色或权限。
JDBC身份认证
JDBC身份认证是通过JDBC连接数据库,基于数据库中已有的用户信息进行身份认证,这样避免了内存身份认证的弊端,可以实现对系统已注册的用户进行身份认证。JdbcUserDetailsManager是Spring Security内置的UserDetailsService的实现类,使用JdbcUserDetailsManager可以通过JDBC将数据库和Spring Security连接起来。下面对JDBC身份认证方式进行讲解。
(1)数据准备
使用之前创建的名为springbootdata的数据库,在该数据库中创建三个表user、priv和user_priv,并预先插入几条测试数据。
(2)配置依赖
打开项目的pom.xml文件,在该文件中添加JDBC的启动器依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
(3)设置配置信息
在项目中创建配置文件 application.properties,在该文件中设置数据库连接的相关
配置信息。
(4)修改配置类
修改文件WebSecurityConfig配置类userDetailsService()方法,将该方法创建的实例对象修改为JdbcUserDetailsManager。
1. 编写SQL语句
String userSQL = "SELECT username,password, valid " +
"FROM user WHERE username = ?";
String authoritySQL = "SELECT u.username,p.authority " +
"FROM user u,priv p,user_priv up " +
"WHERE up.user_id=u.id AND up.priv_id=p.id and u.username =?";
2. 定义JdbcUserDetailsManager实例
JdbcUserDetailsManager users = new JdbcUserDetailsManager();
(5)获取数据库连接,(设置DataSource)
DataSource 是 Java 中用于获取数据库连接的标准接口,它是 JDBC(Java Database Connectivity)的核心组件之一。连接管理:封装数据库连接的创建逻辑,支持连接池、分布式事务等高级特性。配置集中化:将数据库配置(如 URL、用户名、密码)集中管理,便于维护。解耦应用与底层实现:应用程序只需通过接口获取连接,无需关心具体实现(如是否使用连接池)。
实例DataSource :
(6). 把一个 DataSource 对象设置到 users (JdbcUserDetailsManager)对象里,以此让 users 对象具备访问数据库的能力。
users.setDataSource(dataSource);
(7). 告诉 Spring Security 如何根据用户名从数据库中查询用户信息(如密码、权限)。
users.setUsersByUsernameQuery(userSQL);
(8):告诉 Spring Security 如何根据用户名从数据库中查询用户的权限(如角色、权限列表)。
users.setAuthoritiesByUsernameQuery(authoritySQL);
自定义UserDetailsService身份认证
使用InMemoryUserDetailsManager和JdbcUserDetailsManager进行身份认证时,其真正的认证逻辑都在UserDetailsService接口重写的loadUserByUsername()方法中。
对于一个完善的项目来说,通常会实现用户信息查询服务,对此可以自定义一个UserDetailsService实现类,重写该接口的loadUserByUsername()方法,在该方法中查询用户信息,将查询到的用户信息填充到UserDetails对象返回,以实现用户的身份认证。下面通过案例对自定义UserDetailsService进行身份验证的实现进行演示 。
--------------------
(1)创建实体类
在项目目录下创建包entity,在该包下创建用户实体类UserDto和权限实体类Privilege
package com.itheima.chapter07.entity; public class UserDto { private Integer id; //用户编号 private String username; //用户名称 private String password; //密码 private Integer valid; //是否合法 public Integer getId() { return id; } public void setId(Integer id) { this .id = id; } public String getUsername() { return username; } public void setUsername(String username) { this .username = username; } public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } public Integer getValid() { return valid; } public void setValid(Integer valid) { this .valid = valid; } }
package com.itheima.chapter07.entity; public class Privilege { private Integer id; //编号 private String authority; //权限 public Integer getId() { return id; } public void setId(Integer id) { this .id = id; } public String getAuthority() { return authority; } public void setAuthority(String authority) { this .authority = authority; } }
(2)创建用户持久层接口
在项目目录下创建包dao,在该包下创建用户持久层接口,在接口中定义查询用户及角色信息的方法
package com.itheima.chapter07.dao; import com.itheima.chapter07.entity.Privilege; import com.itheima.chapter07.entity.UserDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.ArrayList; import java.util.List; @Repository public class UserDao { @Autowired JdbcTemplate jdbcTemplate; //根据账号查询用户信息 public UserDto getUserByUsername(String username){ String sql = "SELECT * FROM user WHERE username = ?"; //连接数据库查询用户 List<UserDto> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(UserDto.class ),username); if (list !=null && list.size()==1){ return list.get(0); } return null; } //根据用户id查询用户权限 public List<String> findPrivilegesByUserId(Integer userId){ String sql = "SELECT u.username,p.authority " + "FROM user u,priv p,user_priv up " + "WHERE up.user_id=u.id AND up.priv_id=p.id and u.id =?"; List<Privilege> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Privilege.class ), userId); List<String> privileges = new ArrayList<>(); list.forEach(p -> privileges.add(p.getAuthority())); return privileges; } }
(3)封装用户认证信息
在项目目录下创建包service,在该包下创建UserDetailsServiceImpl类,该类实现UserDetailsService接口,并在重写的loadUserByUsername()方法中封装用户认证信息
package com.itheima.chapter07.service; import com.itheima.chapter07.dao.UserDao; import com.itheima.chapter07.entity.UserDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired UserDao userDao; //根据用户名查询用户信息 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //连接数据库根据账号查询用户信息 UserDto userDto = userDao.getUserByUsername(username); if (userDto == null){ //如果用户查不到,返回null,会抛出异常 return null; } //根据用户的id查询用户的权限 List<String> privileges = userDao.findPrivilegesByUserId(userDto.getId()); //将privileges转成数组 String[] privilegeArray = new String[privileges.size()]; privileges.toArray(privilegeArray); UserDetails userDetails = User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(privilegeArray).build(); return userDetails; } }
Spring Security授权管理
授权是Spring Security的核心功能之一,是根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则可正常访问,没有访问的权限时则会被拒绝访问。认证是为了保证用户身份的合法性,而授权则是为了更细粒度地对隐私数据进行划分,授权是在认证通过后发生的,以控制不同的用户访问不同的资源。Spring Security提供了授权方法,开发者通过这些方法进行用户访问控制,下面对Spring Security的授权流程以及自定义授权进行讲解。
Spring Security授权流程
实现授权需要对用户的访问进行拦截校验,校验用户的权限是否可以操作指定的资源。Spring Security使用标准Filter建立了对Web请求的拦截,最终实现对资源的授权访问。
①拦截请求。已认证用户访问受保护的Web资源将被SecurityFilterChain中FilterSecurityInterceptor实例对象拦截。
②获取资源访问策略。FilterSecurityInterceptor实例对象会通过SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource实例对象中获取要访问当前资源所需要的权限,权限封装在Collection实例对象中。 SecurityMetadataSource是读取访问策略的抽象,具体读取的内容,就是开发者配置的访问规则。
③FilterSecurityInterceptor通过AccessDecisionManager进行授权决策,若决策通过,则允许访问资 源,否则将禁止访问。AccessDecisionManager中包含一系列AccessDecisionVoter,可对当前认证过的身份是否有权访问对应的资源进行投票,AccessDecisionManager根据投票结果做出最终决策。
Spring Security自定义授权
根据授权的位置和形式,通常可以将授权的方式分为Web授权和方法授权,这两种授权方式都会调用AccessDecisionManager进行授权决策。下面分别对这两种自定义授权的方式进行讲解。
Web授权
Spring Security的底层实现本质是通过多个Filter形成的过滤器链完成,过滤器链中提供了默认的安全拦截机制,设置安全拦截规则,以控制用户的访问。HttpSecurity是SecurityBuilder接口的实现类,是HTTP安全相关的构建器,Spring Security中可以通过HttpSecurity对象设置安全拦截规则,并通过该对象构建过滤器链。
HttpSecurity可以根据不同的业务场景,对不同的URL采用不同的权限处理策略。当开发者需要配置项目的安全拦截规则时,可以调用HttpSecurity对象对应的方法实现。
通过authorizeRequests()方法可以添加用户请求控制的规则,这些规则通过用户请求控制的相关方法指定。
通过HttpSecurity类的formLogin()方法开启基于表单的用户登录后,可以指定表单认证的相关设置。
案例
Spring Boot项目中使用Spring Security的Web授权方式进行权限管理。
(1)导入登录页面
在项目的resources目录的templates文件夹中导入自定义的登录页面。
(2)编辑配置类
WebMvcConfig配置类中添加loginview的视图映射。
registry.addViewController("/loginview").setViewName("/login.html");
在项目的WebSecurityConfig配置类中使用HttpSecurity对象设置安全拦截规则(哪些页面你需要被保护,哪些不需要),并创建SecurityFilterChain(过滤连)对象交由Spring管理
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
return http.build();
}
HttpSecurity 是 Spring Security 框架中用于配置 HTTP 请求安全策略的核心构建器类(配置规则)。
http.build() 是 Spring Security 配置中的关键步骤,用于将 HttpSecurity 对象构建为一个完整的 SecurityFilterChain(具体执行)。
不需要保护的:登录页面(/loginview)、静态页面("/css/**","/img/**")
http.authorizeRequests().mvcMatchers("/loginview","/css/**","/img/**").permitAll()
实现基于表单的用户认证的核心配置
.and().formLogin()
.and() 的作用是跳出当前配置块,返回到上一级构建器;而 .formLogin() 则是开启基于 HTML 表单的用户认证机制。
将默认的 Spring Security 登录页替换为应用内的自定义页面(如 /loginview)
.loginPage("/loginview")
需要保护的:图书管理页面(/book/admin/manag)
http.authorizeRequests().mvcMatchers("/loginview","/css/**","/img/**").permitAll()
.mvcMatchers("/book/admin/manag").hasRole("ADMIN")
其它请求?全部进行验证!
http.authorizeRequests().mvcMatchers("/loginview","/css/**","/img/**").permitAll()
.mvcMatchers("/book/admin/manag").hasRole("ADMIN")
.anyRequest().authenticated()
设置登录表单提交的目标 URL
.loginProcessingUrl("/doLogin")
.loginProcessingUrl("/doLogin") 的作用是指定处理登录表单提交的 URL,即当用户在登录页面点击提交按钮时,表单数据会被发送到这个 URL 进行处理。提取用户名和密码(默认从 username 和 password 参数获取),并进行认证处理,无需手动编写控制器。
然后我们启动后,访问图书管理系统,只有具有ADMIN角色的用户,才能进入后台管理页面,其他用户没有权限。另外其他请求皆要先进行用户登录,然后默认的登录页面和其样式是不受保护的。
方法授权
Spring Security除了可以在配置类中通过创建过滤器链设置安全拦截规则外,还可以使用@Secured、@RolesAllowed和@PreAuthorize注解控制类中所有方法或者单独某个方法的访问权限,以实现对访问进行授权管理。
使用@Secured和@RolesAllowed注解时,只需在注解中指定访问当前注解标注的类或方法所需要具有的角色,允许多个角色访问时,使用大括号对角色信息进行包裹,角色信息之间使用分号分隔即可。
@RequestMapping("list") @Secured({"ROLE_ADMIN","ROLE_COMMON"}) public String findList() { return "book_list"; } @RequestMapping("admin/manag") @RolesAllowed("ROLE_ADMIN") public String findManagList() { return "book_manag"; }
@PreAuthorize注解会在方法执行前进行权限验证,支持SpEL表达式。
@RequestMapping("list") @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_COMMON')") public String findList() { return "book_list"; } @RequestMapping("admin/manag") @PreAuthorize("hasRole('ROLE_ADMIN')") public String findManagList() { return "book_manag"; }
@Secured、@RolesAllowed和@PreAuthorize注解都可以对方法的访问进行权限控制。
@Secured为Spring Security提供的注解。
@RolesAllowed为基于JSR 250规范的注解。
@PreAuthorize支持SpEL表达式。
------------------
Spring Security默认是禁用方法级别的安全控制注解,要想使用注解进行方法授权,可以使用@EnableGlobalMethodSecurity注解开启基于方法的安全认证机制,该注解可以标注在任意配置类上。
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true, prePostEnabled = true) public class WebSecurityConfig {……}
案例续
(1)开启基于方法的安全认证机制
在项目的WebSecurityConfig配置类中开启基于方法的安全认证机制,并将类中HttpSecurity对象设置的访问“图书管理”拦截规则删除,以确保对资源授权地为方法授权,修改后的代码
(2)方法授权
在BookController类的adminManag()方法上使用注解指定访问该方法所需的角色
用户退出
用户认证通过后,为了避免用户的每次操作都进行认证,可以将用户的信息保存在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于Session方式、基于Token方式等。Spring Security提供会话管理功能,只需要配置即可使用。同时,如果想结束当前会话,可以在自定义退出功能中销毁会话中的用户信息。下面将在Spring Boot项目中基于Spring Security实现会话管理和用户退出。
---------
Spring security默认实现了用户退出的功能,用户退出主要考虑退出后会话如何管理以及跳转到哪个页面。HttpSecurity类提供了logout()方法开启退出登录的支持,默认触发用户退出操作的URL为“/logout”,用户退出时同时也会清除Session等默认用户配置。
用户退出登录的逻辑是由过滤器LogoutFilter执行的,但是项目开发时一般不会选择直接操作LogoutFilter,而是通过LogoutConfigurer对LogoutFilter进行配置,HttpSecurity类logout()方法的返回值就是一个LogoutConfigurer对象,该对象提供了一系列设置用户退出的方法。
books.html
book_manag.html
CSRF(跨站请求伪造):Cross-Site Request Forgery(跨站请求伪造)
本质:攻击者诱导用户在已登录的信任网站上执行非用户本意的操作,利用用户当前的登录状态完成攻击。核心原理:浏览器会自动携带目标网站的 Cookie 等认证信息,攻击者通过伪装成受信任用户的请求,欺骗服务器执行恶意操作。
假设你是社交网站 niceblog.com 的用户,已登录并处于正常会话中(浏览器保存了 niceblog.com 的 Cookie)。此时,你收到一封邮件,里面有一张看似搞笑的图片,实际是攻击者精心构造的恶意链接。攻击者知道 niceblog.com 有一个 “点赞” 接口 POST /post/like,需要参数 postId。
他在自己的网站 evil.com 中隐藏一个自动提交的表单。你以为图片来自可信来源,点击后跳转到 evil.com。此时,浏览器会自动向 niceblog.com 发送请求,携带你的登录 Cookie。niceblog.com 服务器收到请求后,验证 Cookie 有效,认为是你本人操作,执行 “点赞” 攻击者的帖子。
你全程无感知,但已帮攻击者增加了点赞量。
Spring Security 默认启用了 CSRF 保护,CSRF 保护要求状态变更请求(如注销)必须使用 POST 方法,并携带 CSRF 令牌。
我们前面注销链接使用的是简单的 <a> 标签,生成的是 GET 请求。
Spring Security 默认允许 GET 请求访问 /logout,但不会处理会话销毁。
当 CSRF 保护启用时,Spring Security 要求 POST 请求必须包含有效令牌。
.csrf().disable() // 禁用 CSRF 保护