# springboot-study **Repository Path**: wanzhenxing/springboot-study ## Basic Information - **Project Name**: springboot-study - **Description**: springboot-study... - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-05-09 - **Last Updated**: 2024-01-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: SpringBoot ## README # springboot-study springboot-study... ## 0、SpringBoot 简介 ### 0.1 回顾什么是 Spring ? Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson。 Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。 官方文档:https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/features.html#features.task-execution-and-scheduling ### 0.2 Spring 是如何简化 Java 开发的 为了降低Java开发的复杂性,Spring采用了以下4种关键策略: 1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean; 2、通过IOC,依赖注入(DI)和面向接口实现松耦合; 3、基于切面(AOP)和惯例进行声明式编程; 4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate; ### 0.3 什么是 SpringBoot ? - 学过 javaweb 就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍; - 言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can "just run",能迅速的开发web应用,几行代码开发一个http接口。 - 所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。 - 是的这就是 Java 企业级应用 > J2EE > spring > springboot 的过程。 - 随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件; - Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以**约定大于配置的核心思想**,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。 - 简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。 ### 0.4 Spring Boot 的主要优点 1、为所有 Spring 开发者更快的入门; 2、开箱即用,提供各种默认配置来简化项目配置; 3、内嵌式容器简化 Web 项目; 4、没有冗余代码生成和 XML 配置的要求。 快速去体验开发个接口的感觉吧! ## 1、Hello Spring Boot !(helloworld) ### 1.1 准备工作 我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。 **配置环境**:JDK 17.0.5 / Maven 3.9.0 / SpringBoot 2.7.8 **开发工具**:IntelliJ IDEA 2022 3.1 ### 1.2 创建基础项目说明 Spring 官方提供了非常方便的工具让我们快速构建应用 Spring Initializr:https://start.spring.io **项目创建方式一**:使用Spring Initializr 的 Web页面创建项目 1、打开 https://start.spring.io; 2、填写项目信息; 3、点击”Generate Project“按钮生成项目;下载此项目 4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。 5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。 **项目创建方式二**:使用 IDEA 直接创建项目 1、创建一个新项目; 2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现; 3、填写项目信息; 4、选择初始化的组件(初学勾选 Web 即可); 5、填写项目路径; 6、等待项目构建成功。 **项目结构分析**:通过上面步骤完成了基础项目的创建。就会自动生成以下文件。 1、程序的主启动类; 2、一个 application.properties 配置文件; 3、一个 测试类; 4、一个 pom.xml。 ### 1.3 pom.xml 分析 打开pom.xml,看看Spring Boot项目的依赖: ```java org.springframework.boot spring-boot-starter-parent 2.7.8 com.xing helloworld 0.0.1-SNAPSHOT helloworld helloworld 17 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin ``` ### 1.4 编写一个http接口 1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到 2、在包中新建一个HelloController类 ```java // 原理 自动装配 @RestController public class Helloworld { // 接口: http://localhost:8080/hello @RequestMapping("/hello") public String hello(){ //调用业务,接收前端的参数! return "hello world"; } } ``` 3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号! ![](D:\2023\Java\SpringBoot\springboot-study\img\01.png) 简单几步,就完成了一个web接口的开发,SpringBoot 就是这么简单。所以我们常用它来建立我们的微服务项目! ### 1.5 将项目打成jar包,并在本地运行测试 **将项目打成jar包** 1、点击File → Project Structure → Artifacts → + → Add下的JAR下的 → From modules with dependencies... 2、Create JAR from Modules页面下 → Main Class中添加启动项 → 点击OK 3、Project Structure页面 → √勾选 在项目构建中包含 → Apply → OK 4、点击Build下 → Build Artifacts... → Build Artifact下 → Action下 → Build 5、项目就已经打包到out下 → artifacts下 → *项目名*下 → *项目名*.jar **或者使用maven打包,简单快捷** **jar包 运行测试** C:\Windows\System32>cd /d D:\2023\Java\SpringBoot D:\2023\Java\SpringBoot>java -jar helloworld.jar 如果打包成功,则会在target目录下生成一个out 包,打成了out包下的jar包,就可以在任何地方运行了!OK ### 1.6 **彩蛋** 如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案; 只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。 图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可! ![](D:\2023\Java\SpringBoot\springboot-study\img\02.png) SpringBoot这么简单的东西背后一定有故事,我们之后去进行一波源码分析! ### @学不可以已 helloworld 已完结。 helloworld 模块中 (springboot项目创建+打包测试)。 ## 2、运行原理探究(springboot-01-helloworld) springboot-01-helloworld,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;pom.xml。 ### 2.1 父依赖 其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件! ```xml org.springframework.boot spring-boot-starter-parent 2.7.8 ``` 点进去,发现还有一个父依赖 ```xml org.springframework.boot spring-boot-dependencies 2.7.8 pom spring-boot-dependencies ``` 这里才是真正管理 SpringBoot 应用里面所有依赖版本的地方,SpringBoot的版本控制中心; **以后导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本。** ### 2.2 启动器 **启动器 spring-boot-starter** ```xml org.springframework.boot spring-boot-starter-web ``` **springboot-boot-starter-xxx**:就是 spring-boot 的场景启动器 **spring-boot-starter-web**:帮我们导入了 web 模块正常运行所依赖的组件; SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来,我们要用什么功能就导入什么样的场景启动器即可;我们未来也可以自己自定义 starter; ### 2.3 **主启动类分析** 分析完了 pom.xml 来看看这个启动类 #### 2.3.1 默认的主启动类 ```java // @SpringBootApplication 来标注一个主程序类 说明这是一个Spring Boot应用 @SpringBootApplication public class Springboot01HelloworldApplication { public static void main(String[] args) { // 以为是启动了一个方法,没想到启动了一个服务 SpringApplication.run(Springboot01HelloworldApplication.class, args); System.out.println("\n Run successfully!"); } } ``` 但是**一个简单的启动类并不简单**!我们来分析一下这些注解都干了什么。 #### 2.3.2 @SpringBootApplication **作用**:标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用; **进入这个注解**:可以看到上面还有很多其他注解! ```java @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { // ...... } ``` #### 2.3.3 @ComponentScan 这个注解在Spring中很重要 ,它对应XML配置中的元素。 **作用**:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中。 #### 2.3.4 @SpringBootConfiguration **作用**:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类;继续进去这个注解查看。 ```java // 点进去得到下面的 @Component @Configuration public @interface SpringBootConfiguration {} @Component public @interface Configuration {} ``` 这里的 @Configuration,说明这是一个配置类,配置类就是对应Spring的xml 配置文件; 里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用! 我们回到 SpringBootApplication 注解中继续看。 #### 2.3.5 @EnableAutoConfiguration **@EnableAutoConfiguration:开启自动配置功能** 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;点进注解接续查看: **@AutoConfigurationPackage:自动配置包** ```java @Import({Registrar.class}) public @interface AutoConfigurationPackage { } ``` **@import** :Spring底层注解@import , 给容器中导入一个组件 Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ; 这个分析完了,退到上一步,继续看。 **@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;** **AutoConfigurationImportSelector** :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码: 1、这个类中有一个这样的方法 ```java // 获得候选的配置 protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; } ``` 2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法 ```java public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); //这里它又调用了 loadSpringFactories 方法 return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } ``` 3、我们继续点击查看 loadSpringFactories 方法 ```java private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身 MultiValueMap result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //去获取一个资源 "META-INF/spring.factories" Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //将读取到的资源遍历,封装成为一个Properties while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } } ``` 4、发现一个多次出现的文件:spring.factories,全局搜索它。 #### 2.3.6 spring.factories 我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在。 **WebMvcAutoConfiguration** 我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration 可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下! 所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。 **结论:** 1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值; 2. 将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作; 3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中; 4. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件, 并配置好这些组件 ; 5. 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。 **现在大家应该大概的了解了SpringBoot的运行原理,后面我们还会深化一次!** #### 2.3.7 **SpringApplication** 不简单的方法。 ```java // 最初以为就是运行了一个main方法,没想到却开启了一个服务; @SpringBootApplication public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } } ``` **SpringApplication.run分析** 分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行; #### 2.3.8 SpringApplication **这个类主要做了以下四件事情:** 1、推断应用的类型是普通的项目还是Web项目; 2、查找并加载所有可用初始化器 , 设置到initializers属性中; 3、找出所有的应用程序监听器,设置到listeners属性中; 4、推断并设置main方法的定义类,找到运行的主类。 查看构造器: ```java public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { // ...... this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.setInitializers(this.getSpringFactoriesInstances(); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); } ``` #### 2.3.9 run方法流程分析 ![](D:\2023\Java\SpringBoot\springboot-study\img\03.png) 跟着源码和这幅图就可以一探究竟了! ### @学不可以已 springboot-01-helloworld 已完结。 springboot-01-helloworld 模块中 (IDEA创建springboot项目+测试+启动类分析) 。 ## 3、yaml 语法学习(springboot-02-config) ### 3.1 配置文件 SpringBoot使用一个全局的配置文件, 配置文件名称是固定的 **application.properties:** - 语法结构:key=value **application.yaml:** - 语法结构:key:空格 value **配置文件的作用**:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了; 比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下! ```properties server.port: 8081 ``` ### 3.2 yaml概述 **中文名**:另一种标记语言 **外文名**:YAML Ain't Markup Language **YAML**(/ˈjæməl/,尾音类似*camel*骆驼)是一个可读性高,用来表达数据序列化的格式。 **YAML**是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。 **这种语言以数据作为中心,而不是以标记语言为重点!** 以前的配置文件,大多数都是使用xml来配置; 比如一个简单的端口配置,我们来对比下yaml和xml传统xml配置: ```xml 8081 ``` yaml配置: ```yaml server: port: 8081 ``` ### 3.3 yaml基础语法 **说明**:语法要求严格! 1、空格不能省略; 2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的; 3、属性和值的大小写都是十分敏感的。 **字面量:普通的值 [ 数字,布尔值,字符串 ]** 字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;k: v 。 注意: - “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思; 比如 :name: "kuang \n shen" 输出 :kuang 换行 shen - '' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出 比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen **对象、Map(键值对)** ``` #对象、Map格式k: v1: v2: ``` 在下一行来写对象的属性和值得关系,注意缩进;比如: ``` student: name: qinjiang age: 3 ``` 行内写法 ``` student: {name: qinjiang,age: 3} ``` **数组( List、set )** 用 - 值表示数组中的一个元素,比如: ``` pets: - cat - dog - pig ``` 行内写法 ``` pets: [cat,dog,pig] ``` **修改SpringBoot的默认端口号** 配置文件中添加,端口号的参数,就可以切换端口; ```yaml server.port: 8082 ``` 注入配置文件,yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值! ### 3.4 yaml注入配置文件 **1**、在springboot-02-config项目中的resources目录下新建一个文件 application.yml; **2**、新建一个实体类 **Dog**; ```java package com.xing.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Data @AllArgsConstructor @NoArgsConstructor @Component //注册bean到容器中 public class Dog { // @Value("旺财") private String name; // @Value("3") private String age; //有参无参构造、get、set方法、toString()方法 } ``` **3**、思考,原来是如何给bean注入属性值的!@Value,给狗类测试一下: ```java @Value("旺财") @Value("3") ``` **4**、在SpringBoot的测试类Springboot02ConfigApplicationTests下注入狗输出一下; ```java package com.xing; import com.xing.domain.Dog; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class Springboot02ConfigApplicationTests { @Autowired private Dog dog; @Test void contextLoads() { System.out.println(dog); } } ``` **Test测试结果:Dog(name=旺财, age=3)** 结果成功输出,@Value注入成功。 **5**、我们在编写一个复杂一点的实体类:Person 类 ```java package com.xing.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; import java.util.List; import java.util.Map; @Data @AllArgsConstructor @NoArgsConstructor public class Person { private String name; private Integer age; private Boolean happy; private Date birth; private Map maps; private List lists; private Dog dog; //有参无参构造、get、set方法、toString()方法 } ``` **6**、使用yaml配置的方式进行注入,注意区别和优势,编写一个yaml配置! 编写一个: **application.yml** ```yaml server: port: 8081 person: name: yasuo age: 9 happy: false birth: 2014/01/09 maps: {k1: v1,k2: v2} lists: - code - boy - programming dog: name: 旺财 age: 6 ``` **7**、刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中! ```java package com.xing.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; @Data @AllArgsConstructor @NoArgsConstructor @Component @ConfigurationProperties(prefix = "person") public class Person { private String name; private Integer age; private Boolean happy; private Date birth; private Map maps; private List lists; private Dog dog; //有参无参构造、get、set方法、toString()方法 /** * @ConfigurationProperties作用: * 将配置文件中配置的每一个属性的值,映射到这个组件中; * 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定 * 参数 prefix = "person":将配置文件中的person下面的所有属性——对应 * 只有这个组件是容器中的组件,才能使用容器提供的@configurationProperties功能 * 需要再maven导入:spring-boot-configuration-processor 依赖 */ } ``` **8**、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到这个依赖! ```xml org.springframework.boot spring-boot-configuration-processor true ``` **9**、确认以上配置都OK之后,我们去测试类中测试一下: ```java package com.xing; import com.xing.domain.Dog; import com.xing.domain.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class Springboot02ConfigApplicationTests { @Autowired private Person person; //将person自动注入进来 @Test void contextLoads01() { System.out.println("----------"); System.out.println(person); } } ``` **Test测试结果:Person(name=yasuo, age=9, happy=false, birth=Thu Jan 09 00:00:00 CST 2014, maps={k1=v1, k2=v2}, lists=[code, boy, programming], dog=Dog(name=旺财, age=6))** 结果:所有值全部注入成功! **yaml配置注入到实体类完全OK!** 自由测试: 1、将配置文件的key 值 和 属性的值设置为不一样,则结果输出为null,注入失败; 2、在配置一个person2,然后将 @ConfigurationProperties(prefix = "person2") 指向person2。 ### 3.5 自定义加载指定的配置文件 **@PropertySource** :加载指定的配置文件; **@configurationProperties**:默认从全局配置文件中获取值; **1**、我们去在resources目录下新建一个**xing.properties**文件 编写一个: **xing.properties** ```properties ys:亚索 ``` **2**、然后在我们的代码中指定加载**xing.properties**文件 ```java package com.xing.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; @Data @AllArgsConstructor @NoArgsConstructor @Component //@ConfigurationProperties(prefix = "person") /** * javaConfig 绑定配置文件的值,可以采取这些方式 * 加载指定的配置文件 */ @PropertySource(value = "classpath:xing.properties") public class Person { /** * Spring el 表达式取出配置文件的值 */ @Value("${ys}") private String name; private Integer age; private Boolean happy; private Date birth; private Map maps; private List lists; private Dog dog; //有参无参构造、get、set方法、toString()方法 /** * @ConfigurationProperties作用: * 将配置文件中配置的每一个属性的值,映射到这个组件中; * 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定 * 参数 prefix = "person":将配置文件中的person下面的所有属性——对应 * 只有这个组件是容器中的组件,才能使用容器提供的@configurationProperties功能 * 需要再maven导入:spring-boot-configuration-processor 依赖 */ } ``` 测试类不变,启动测试。 **Test测试结果:Person(name=亚索, age=null, happy=null, birth=null, maps=null, lists=null, dog=null)** **3**、再次输出测试一下:指定配置文件绑定成功! ### 3.6 配置文件占位符 配置文件还可以编写占位符生成随机数 ```yaml person: name: yasuo${random.uuid} # 随机uuid age: ${random.int} # 随机int happy: false birth: 2014/01/09 maps: {k1: v1,k2: v2} hello: 小旺财 lists: - code - boy - programming dog: name: ${person.hello:hello}_旺财 age: 6 ``` 测试类不变,启动测试。使用@ConfigurationProperties **Test测试结果:Person(name=yasuo82408b9e-fb11-4f00-8f75-7e2f49697e9a, age=1660117737, happy=false, birth=Thu Jan 09 00:00:00 CST 2014, maps={k1=v1, k2=v2}, lists=[code, boy, programming], dog=Dog(name=小旺财_旺财, age=6))** ### 3.7 回顾properties配置 上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties ! 【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8; settings-->FileEncodings 中配置;勾选 Transparent native-to-ascii conversion 。 **自由测试步骤:** **1**、新建一个实体类**User** ```java package com.xing.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Data @AllArgsConstructor @NoArgsConstructor @Component //注册bean @PropertySource(value = "classpath:user.properties") public class User { //直接使用@value // @Value("${user.name}") //从配置文件中取值 @Value("${n}") private String name; // @Value("#{9*2}") // #{SPEL} Spring表达式 @Value("${a}") private int age; // @Value("男") // 字面量 @Value("${s}") private String sex; } ``` **2**、编辑配置文件 **user.properties** ```properties n:亚索 a=18 s=男 ``` **3**、我们在User类上使用@Value来进行注入! ```java @Value("${n}") @Value("${a}") @Value("${s}") ``` **4**、Springboot测试 ```java package com.xing; import com.xing.domain.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class UserTest { @Autowired User user; @Test void contextLoads() { System.out.println("---------"); System.out.println(user); } } ``` **Test测试结果:方法一:User(name=WZX, age=18, sex=男) 方法二:User(name=亚索, age=18, sex=男)** 结果正常输出。 ### 3.8 对比小结 @Value这个使用起来并不友好!需要为每个属性单独注解赋值,比较麻烦;功能对比图。![](D:\2023\Java\SpringBoot\springboot-study\img\04.png) **1**、@ConfigurationProperties只需要写一次即可,@Value则需要每个字段都添加。 **2**、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, -后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下。 ```java //在Dog类把name改为firstName,添加配置属性注解, @ConfigurationProperties(prefix = "dog") public class Dog { private String firstName; ``` ```yaml //application.yml 添加松散绑定 dog: first-name: 狗狗 age: 4 ``` 最后测试 测试类不变。 **Test测试结果:Dog(firstName=狗狗, age=4)** **3**、JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性。 **4**、复杂类型封装,yml中可以封装对象,使用value就不支持。 **结论:** 配置yml和配置properties都可以获取到值, 强烈推荐 yml; 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value; 如果说,专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫! ## 4、JSR303数据校验及多环境切换 ### 4.1 测试如何使用 Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式; **1、pom.xml**中引入**spring-boot-starter-validation** jar包; ``` org.springframework.boot spring-boot-starter-validation 2.7.14 ``` **2、Person**类中添加数据校验注解,并使用@Email测试; ```java //添加数据校验注解 @Validated //数据校验 public class Person { @Email(message = "邮箱格式不正确!") private String email; ``` **3、application.yml** 添加email; ```yaml email: 1231333 ``` **4**、测试。 ```java //person 测试 @Autowired private Person person; //将person自动注入进来 @Test void contextLoads01() { System.out.println("----------"); System.out.println(person); } ``` **Test测试结果:[不是一个合法的电子邮件地址]**,使用正确邮箱测试即可成功。 **使用数据校验,可以保证数据的正确性。** ### 4.2 JSR303校验 常用的注解参数 ```java @NotNull(message="名字不能为空") private String userName; @Max(value=120,message="年龄最大不能查过120") private int age; @Email(message="邮箱格式错误") private String email; //空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null, 无法查检长度为0的字符串 @NotBlank 验证字符串不为null、空或包含空格、制表符、换行符、不可见字符等空白字符。 @NotEmpty 验证字符串不为null、空或长度为0。 //Booelan检查 @AssertTrue 验证 Boolean 对象是否为 true @AssertFalse 验证 Boolean 对象是否为 false //长度检查 @Size(min=1, max=10) 验证字符串的长度是否在1到10个字符之间。描述计算机内存、硬盘空间等的大小。 @Length(min=3, max=5) 验证字符串的长度是否在3到5个字符之间。描述音符的长度,或者一段时间的长度。 //日期检查 @Past 验证 Date 和 Calendar 对象是否在当前时间之前 @Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则 .......等等,除此以外,还可以自定义一些数据校验规则。 ``` ### 4.3 多环境配置及配置文件加载位置 官方外部配置文件说明参考文档,考虑的完整位置集可分为以下四个方式: 1. `optional:classpath:/;optional:classpath:/config/` 2. `optional:file:./;optional:file:./config/;optional:file:./config/*/` 3. `optional:classpath:custom-config/` 4. `optional:file:./custom-config/` ![](D:\2023\Java\SpringBoot\springboot-study\img\05.png) 优先级执行顺序:**①④②③**。 优先级1:项目路径下的config文件夹配置文件; 优先级2:项目路径下配置文件; 优先级3:资源路径下的config文件夹配置文件; 优先级4:资源路径下配置文件。 优先级由高到底,高优先级的配置会覆盖低优先级的配置。 **.properties 方法一:** profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境; 我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本; 副配置文件: application-test.properties 代表测试环境配置 application-dev.properties 代表开发环境配置 但是Springboot并不会直接启动这些配置文件,它**默认使用application.properties主配置文件**; 我们需要通过一个配置来选择需要激活的环境: ```properties #主配置文件 application.properties #springboot的多环境配置,可以选择激活哪一个配置文件 spring.profiles.active=dev #在此切换需要使用的环境 ``` **.yaml 方法二:** 和properties配置文件中一样,但是使用yaml去实现不需要创建多个配置文件,更加方便了 ! ```yaml spring: profiles: active: dev #选择要激活那个环境块 --- server: port: 8087 spring: config: activate: on-profile: dev #配置环境的名称 --- server: port: 8089 spring: config: activate: on-profile: prod #配置环境的名称 ``` **注意:如果yaml和properties同时都配置了端口,并且没有激活其他环境,默认会使用properties配置文件的!** ## 5、自动配置原理 ### 5.1 自动配置原理 配置文件到底能写什么?怎么写? SpringBoot官方文档中有大量的配置,我们无法全部记住。 ### 5.2 分析自动配置原理 我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理; ```java package org.springframework.boot.autoconfigure.web.servlet; 在此,包略写...... //表示这是一个自动配置类。在Spring Boot启动时,如果该类存在,则会自动扫描并加载其依赖项。 @AutoConfiguration //表示该类可以接收配置属性,并将其存储到运行时环境中。在Spring Boot启动时,如果该类存在,则会自动扫描并加载其配置属性。 @EnableConfigurationProperties({ServerProperties.class}) //表示该注解只在Web应用程序中生效。如果应用程序不是Web应用程序,则该注解将不会生效。 @ConditionalOnWebApplication(type = Type.SERVLET) //表示该注解只在特定类存在时生效。如果该类不存在,则该注解将不会生效。 @ConditionalOnClass({CharacterEncodingFilter.class}) //表示该注解只在特定属性的值匹配时生效。如果属性的值不匹配,则该注解将不会生效。在本例中, //该注解用于检查“server.servlet.encoding.enabled”属性是否为true。如果该属性的值为true,则该注解将生效,否则将不会生效。 @ConditionalOnProperty(prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true) public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(ServerProperties properties) { this.properties = properties.getServlet().getEncoding(); } //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @Bean //在Spring容器中查找指定名称的Bean是否存在,如果不存在则创建一个新的Bean并注入到容器中。该注解可以用于创建和注册Bean的时候, //也可以用于其他Bean的依赖注入时,如果被依赖的Bean不存在,则自动创建一个新的Bean并注入。 @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); ...... } ``` **一句话总结:根据当前不同的条件判断,决定这个配置类是否生效!** - 一但这个配置类生效;这个配置类就会给容器中添加各种组件; - 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的; - 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着; - 配置文件能配置什么就可以参照某个功能对应的这个属性类 ``` //从配置文件中获取指定的值和bean的属性进行绑定 @ConfigurationProperties(prefix = "spring.http") public class HttpProperties { // .....} ``` 配置文件里面试试前缀,看提示! ```yaml spring.http.encoding. 。。。 ``` **这就是自动装配的原理!** ### 5.3 精髓 1、SpringBoot启动会加载大量的自动配置类 2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中; 3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了) 4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可; **xxxxAutoConfigurartion:自动配置类**;给容器中添加组件。 **xxxxProperties:封装配置文件中相关属性。** ### 5.4 了解@Conditional 了解完自动装配的原理后,来关注一个细节问题,**自动配置类必须在一定的条件下才能生效;** **@Conditional派生注解(Spring注解版原生的@Conditional作用)** 作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效; ![](D:\2023\Java\SpringBoot\springboot-study\img\06.png) **那么多的自动配置类,必须在一定的条件下才能生效;也就是说加载了这么多的配置类,但不是所有的都生效了。** 我们怎么知道哪些自动配置类生效? **我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;** ``` #开启springboot的调试类 debug: true #在 Spring Boot 中,"debug:true" 是一个 application.yaml 文件中的配置项,用于开启 Spring Boot 的调试模式。 #在调试模式下,Spring Boot 会输出一些额外的信息,例如应用程序的运行时信息、线程信息、Bean 的创建和注入过程等,以便开发者进行调试和排查问题。 #这个配置项通常会被放在 application.yaml 文件的开头,以便在整个应用程序运行过程中都开启调试模式。 ``` **Positive matches:(自动配置类启用的:正匹配)** **Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)** **Unconditional classes: (没有条件的类)** 【演示:查看输出的日志】 掌握吸收理解原理,即可以不变应万变! ### @学不可以已 springboot-02-config 已完结。 springboot-02-config 模块中 (yaml注入+JSR303校验+多环境配置+自动配置原理) 。 ## 6、springboot web开发(springboot-03-web) **新建一个springboot-03-web项目,记得勾选spring web。** **1**、新建测试类**HelloController**,验证项目是否正常。 ```java package com.xing.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/hello") public class HelloController { @GetMapping("/h1") public String hello(){ return "hello,world!"; } } ``` **2**、首页测试类**IndexController**,测试首页和图标。 ```java package com.xing.controller; import org.springframework.stereotype.Controller; @Controller public class IndexController { public String index() { return "index";//写一个对应的index.html进行测试 } } ``` ### 6.1 Web开发探究 SpringBoot与Web开发,实战部分;其实SpringBoot的东西用起来非常简单,因为SpringBoot最大的特点就是自动装配。 #### 6.1.1 使用SpringBoot的步骤: 1、创建一个SpringBoot应用,选择需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好; 2、手动在配置文件中配置部分配置项目就可以运行起来; 3、专注编写业务代码,不需要考虑以前那样一大堆的配置了。 要熟悉掌握开发,自动配置的原理一定要搞明白! 比如SpringBoot到底帮我们配置了什么?我们能不能修改?我们能修改哪些配置?我们能不能扩展? - 向容器中自动配置组件 :*** Autoconfiguration - 自动配置类,封装配置文件的内容:***Properties 没事就找找类,看看自动装配原理!我们之后来进行一个单体项目的小项目测试,快速上手开发! #### 6.1.2 静态资源处理 SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面。 可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法; 有一个方法:addResourceHandlers 添加资源处理。 总结:在springboot,可以使用以下方式处理静态资源。 **1、webjars** localhost:8080/webjars/... **2、public,static,/xx,resources** localhost :8080/ **优先级: resources > static(默认) > public** **什么是webjars ?** Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。SpringBoot需要使用Webjars,可以去搜索一下: 网站:https://www.webjars.org 要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!导入完毕,查看webjars目录结构,并访问Jquery.js文件! 访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,访问:http://localhost:8088/webjars/jquery/3.7.0/jquery.js 可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件; 比如我们访问 http://localhost:8088/1.js , 他就会去这些文件夹中寻找对应的静态资源文件; 自定义静态资源路径:也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置; ```yaml spring.resources.static-locations=classpath:/coding/,classpath:/xing/ ``` 一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了! #### 6.1.3首页和图标定制 可以看到一个欢迎页的映射welcomePageHandlerMapping,就是我们的首页,欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。 比如我访问 http://localhost:8088/ ,就会找静态资源文件夹下的 index.html 新建一个 **index.html** ,在我们上面的3个目录中任意一个;然后访问测试:http://localhost:8088/ 看结果! **关于网站图标说明**: 与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。 Spring Boot 2.7.15版本支持**将favicon.ico图标直接放置在`resources`目录下,以替换默认的favicon图标。只需将您的自定义favicon图标命名为 `favicon.ico`**,并将其放置在`src/main/resources`目录下即可。在启动Spring Boot应用程序时,它将自动检测并使用该文件作为默认的favicon图标。如果您想禁用favicon,则可以将`spring.mvc.favicon.path=false` 设置为空字符串。 **1**、关闭SpringBoot默认图标; **2**、自己放一个图标在静态resources资源目录下,放在 public 目录下; **3**、清除浏览器缓存!刷新网页,发现图标已经变成自己的图标! ### 6.2 Thymeleaf模板引擎 前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示及交互等。 jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以他现在默认是不支持jsp的。不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢? **SpringBoot推荐你可以来使用模板引擎:** 模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图: ![](D:\2023\Java\SpringBoot\springboot-study\img\7.png) 模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。 看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。 #### 6.2.1 引入Thymeleaf 怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址: Thymeleaf 官网:https://www.thymeleaf.org Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf Spring官方文档:找到我们对应的版本 https://docs.spring.io/spring-boot/docs/2.7.15/reference/htmlsingle/#upgrading 找到对应的pom依赖:可以适当点进源码看下本来的包! ```xml org.thymeleaf thymeleaf-spring5 org.thymeleaf.extras thymeleaf-extras-java8time ``` Maven会自动下载jar包,可以去看下下载的三个jar包; #### 6.2.2 Thymeleaf分析 前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢? 我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。 我们去找一下Thymeleaf的自动配置类:ThymeleafProperties ```java public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; private Charset encoding; //后面源码忽略... ``` 我们可以在其中看到默认的前缀和后缀! 只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。 使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可! **编写一个测试** **1**、编写一个**TestController** ```java @Controller @RequestMapping("/test") public class TestController { @RequestMapping("/t1") public String test(){ return "test"; } } ``` **2**、编写一个测试页面 test.html 放在 templates 目录下 ```html Title

Test

``` **3**、启动项目请求测试:http://localhost:8088/test/t1 #### 6.2.3 Thymeleaf 语法学习 要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下; Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!下载Thymeleaf的官方文档学习! **我们做个最简单的练习 :我们需要查出一些数据,在页面中展示** ![](D:\2023\Java\SpringBoot\springboot-study\img\07.png) **1**、修改测试请求,增加数据传输。 ```java @Controller @RequestMapping("/test") public class TestController { @RequestMapping("/t1") public String test(Model model){ //存入数据 model.addAttribute("msg","Hello thymeleaf!"); //classpath:/templates/test.html return "test"; } } ``` **2**、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。 可以去官方文档的#3中看一下命名空间拿来过来: ```html xmlns:th="http://www.thymeleaf.org" ``` **3**、我们去编写下前端页面 ```html Title

Test

``` 4、启动测试!http://localhost:8088/test/t1 **OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!** **1、我们可以使用任意的 th:attr 来替换Html中原生属性的值!** ![img](D:\2023\Java\SpringBoot\springboot-study\img\08.png) **2、我们能写哪些表达式呢?** ```java Simple expressions:(表达式语法) Variable Expressions: ${...}:获取变量值;OGNL; 1)、获取对象的属性、调用方法 2)、使用内置的基本对象:#18 #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. 3)、内置的一些工具对象:       #execInfo : information about the template being processed.       #uris : methods for escaping parts of URLs/URIs       #conversions : methods for executing the configured conversion service (if any).       #dates : methods for java.util.Date objects: formatting, component extraction, etc.       #calendars : analogous to #dates , but for java.util.Calendar objects.       #numbers : methods for formatting numeric objects.       #strings : methods for String objects: contains, startsWith, prepending/appending, etc.       #objects : methods for objects in general.       #bools : methods for boolean evaluation.       #arrays : methods for arrays.       #lists : methods for lists.       #sets : methods for sets.       #maps : methods for maps.       #aggregates : methods for creating aggregates on arrays or collections. ================================================================================== Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样; Message Expressions: #{...}:获取国际化内容 Link URL Expressions: @{...}:定义URL; Fragment Expressions: ~{...}:片段引用表达式 Literals(字面量) Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations:(文本操作) String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations:(数学运算) Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations:(布尔运算) Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality:(比较运算) Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators:条件运算(三元运算符) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: No-Operation: _ ``` **练习测试:** **1**、我们编写一个Controller,放一些数据; ```java @RequestMapping("/t2") public String test2(Map map){ //存入数据 map.put("users", Arrays.asList("Fizz","Zed","Yasuo")); //classpath:/templates/test.html return "test"; } ``` **2**、测试页面取出数据; ```html Title

Test1

Test2

[[${users}]]

``` **3**、启动项目测试!http://localhost:8088/test/t2 **看完语法,有很多样式,即使现在学习,长时间也会忘记,根据官方文档来查询,熟练使用官方文档最重要!** ### 6.3 MVC自动配置原理 #### 6.3.1 官网文档 在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。 只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档! 地址 :https://docs.spring.io/spring-framework/docs/5.3.29/reference/html/web.html#spring-web ```java Spring MVC Auto-configuration // Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。 Spring Boot provides auto-configuration for Spring MVC that works well with most applications. // 自动配置在Spring默认设置的基础上添加了以下功能: The auto-configuration adds the following features on top of Spring’s defaults: // 包含视图解析器 Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans. // 支持静态资源文件夹的路径,以及webjars Support for serving static resources, including support for WebJars // 自动注册了Converter: // 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型 // Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】 Automatic registration of Converter, GenericConverter, and Formatter beans. // HttpMessageConverters // SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释; Support for HttpMessageConverters (covered later in this document). // 定义错误代码生成规则的 Automatic registration of MessageCodesResolver (covered later in this document). // 首页定制 Static index.html support. // 图标定制 Custom Favicon support (covered later in this document). // 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中! Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document). /* 如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供 RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例, 则可以声明WebMVCregistrationAdapter实例来提供此类组件。 */ If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components. // 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。 If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc. ``` 我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢? #### 6.3.2 视图解析器 自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器; 即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。 我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法! 我们可以点进这类看看!找到对应的解析视图的代码; ```java @Nullable //注解说明 @Nullable 即参数可为null public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); List requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest()); if (requestedMediaTypes != null) { // 获取候选的视图对象 List candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes); // 选择一个最适合的视图对象,然后把这个对象返回 View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != null) { return bestView; } } ...... ``` 我们继续点进去看,他是怎么获得候选的视图的呢? getCandidateViews中看到他是把所有的视图解析器拿来,进行while循环,挨个解析! ```java Iterator var5 = this.viewResolvers.iterator(); ``` 所以得出结论:**ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的** 我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的! ```java protected void initServletContext(ServletContext servletContext) { // 这里它是从beanFactory工具中获取容器中的所有视图解析器 // ViewRescolver.class 把所有的视图解析器来组合的 Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values(); ViewResolver viewResolver; if (this.viewResolvers == null) { this.viewResolvers = new ArrayList(matchingBeans.size()); Iterator var3 = matchingBeans.iterator(); while(var3.hasNext()) { viewResolver = (ViewResolver)var3.next(); if (this != viewResolver) { this.viewResolvers.add(viewResolver); } } ...... ``` 既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢? 可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来; **我们去实现一下** **1**、我们在我们的主程序中去写一个视图解析器来试试; ```java //应为类型要求为WebMvcConfigurer,所以我们实现其接口 //如果。你想diy一些定制化的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配! //可以使用自定义类扩展SpringMVC的功能 jar包spring-webmvc下DispatcherServlet类 //protected void doDispatch 打断点,访问http://localhost:8088/测试 @Configuration //标注为配置类 //@EnableWebMvc 不要标注此注解 否则此类全面接管MVC的全部配置 public class MyMvcConfig implements WebMvcConfigurer { //ViewResolver实现了视图解析器接口的类,我们就可以把它看做视图解析器 @Bean //放到bean中 public ViewResolver myViewResolver(){ //自动视图解析器交给spring管理 return new MyViewResolver(); } //自定义了一个自己的视图解析器 写一个静态内部类,视图解析器就需要实现ViewResolver接口 public static class MyViewResolver implements ViewResolver { @Override public View resolveViewName(String viewName, Locale locale) throws Exception { return null; } } } ``` **2**、怎么看我们自己写的视图解析器有没有起作用呢? 我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中 **3**、我们启动我们的项目,然后随便访问http://localhost:8088 页面,进Debug看this信息; 找到this → 找到视图解析器,可以看到我们自己定义的就在里面; 所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了! #### 6.3.3 转换器和格式化器 **找到格式化转换器**:WebMvcAutoConfiguration → FormattingConversionService → getFormat → getDateFormat ```java @Bean public FormattingConversionService mvcConversionService() { WebMvcProperties.Format format = this.mvcProperties.getFormat(); // 拿到配置文件中的格式化规则 WebConversionService conversionService = new WebConversionService((new DateTimeFormatters()).dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); this.addFormatters(conversionService); return conversionService; } ...... ``` **点击去**:getDateFormat ```java public Format getFormat() { return this.format; } private String date; # 自定义的配置日期格式化 # spring.mvc.format.date=dd/MM/yyyy ``` 可以看到在我们的Properties文件中,我们可以进行自动配置它! 如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:自己多研究探讨即可! #### 6.3.4 修改SpringBoot的默认配置 这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论:SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来! **扩展使用SpringMVC** 官方文档如下: If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components. 我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;自己写一个;新建一个包叫config,写一个类MyMvcConfig。 ```java //应为类型要求为WebMvcConfigurer,所以我们实现其接口 //可以使用自定义类扩展MVC的功能 @Configuration //@EnableWebMvc 不要标注此注解 否则此类全面接管MVC的全部配置 public class MyMvcConfig1 implements WebMvcConfigurer { //视图跳转 @Override public void addViewControllers(ViewControllerRegistry registry) { //浏览器发送/test,就会跳转到test页面 registry.addViewController("/tt").setViewName("test"); } } ``` 我们去浏览器访问一下:http://localhost:8088/tt **确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!** 我们可以去分析一下原理: **1**、WebMvcAutoConfiguration 是SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter **2**、这个类上有一个注解,在做其他自动配置时会导入:@Import({EnableWebMvcConfiguration.class}), **3**、点进EnableWebMvcConfiguration这个类看一下它继承了一个父类:DelegatingWebMvcConfiguration 这个父类中有这样一段代码: ```java public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); public DelegatingWebMvcConfiguration() { } // 从容器中获取所有的webmvcConfigure @Autowired( required = false ) public void setConfigurers(List configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } ...... ``` **4**、我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个 ```java protected void addViewControllers(ViewControllerRegistry registry) { this.configurers.addViewControllers(registry); } ``` **5**、我们点进去看一下。 ```java public void addViewControllers(ViewControllerRegistry registry) { Iterator var2 = this.delegates.iterator(); while(var2.hasNext()) { // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的 WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next(); delegate.addViewControllers(registry); } } ...... ``` 所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用。 #### 6.3.5 全面接管SpringMVC 官方文档:If you want to take complete control of Spring MVCyou can add your own @Configuration annotated with @EnableWebMvc. 全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置! 只需在我们的配置类中要加一个@EnableWebMvc。 我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下; 不加注解之前,可以正常访问首页:http://localhost:8088/ 给配置类加上注解:@EnableWebMvc:访问首页错误http://localhost:8088/ **Whitelabel Error Page** 我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子。 **当然,我们开发中,不推荐使用全面接管SpringMVC** 思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码: **1**、这里发现它是导入了一个类,可以继续进去看 ```java @Import({DelegatingWebMvcConfiguration.class}) //点进EnableWebMvcConfiguration //再进DelegatingWebMvcConfiguration ``` **2**、它继承了一个父类 WebMvcConfigurationSupport ```java public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { ...... } ``` **3**、回顾一下Webmvc自动配置类 ```java @AutoConfiguration(after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) //这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效 @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) public class WebMvcAutoConfiguration { ...... } ``` 总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了; 而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能! **在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意** **各个测试地址:** ```java System.out.println("http://localhost:8088/hello/h1"); System.out.println("http://localhost:8088/webjars/jquery/3.7.0/jquery.js"); System.out.println("http://localhost:8088/1.js"); System.out.println("http://localhost:8088"); System.out.println("http://localhost:8088/test/t1"); System.out.println("http://localhost:8088/test/t2"); System.out.println("http://localhost:8088/tt"); ``` ### 6.4 员工管理系统 **1**、首页配置: - 注意点,所有页面的静态资源都需要使用thymeleaf接管; - url:@{} **2**、页面国际化: - 我们需要配置i18n文件 - 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleReso1ver - 记得将自己写的组件配置到spring容器 @Bean - #{} **3**、登录 + 拦截器 **4**、员工列表展示 ①提取公共页面 a、th:fragment="sidebar" b、th:rep1ace="~{commons/commons : :topbar}" c、如果要传递参数,可以直接使用()传参,接收判断即可! ②列表循环展示 **5**、添加员工 ①按钮提交 ②跳转到添加页面 ③添加员工成功 ④返回首页 **6**、CRUD搞定 **7**、404、注销登录 **8**、所有功能完成! ### @学不可以已 springboot-03-web 已完结。 springboot-03-web 模块中(创建springboot项目+整合Thymeleaf+员工管理系统) 。 ## 7、springboot整合JDBC(springboot-04-data) **SpringData简介** 对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。 Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。 Sping Data 官网:https://spring.io/projects/spring-data 数据库相关的启动器 :可以参考官方文档: https://docs.spring.io/spring-boot/docs/2.7.15/reference/html/using.html#using.build-systems.starters 整合JDBC ### 7.1 创建测试项目测试数据源 **1**、新建一个项目测试:springboot-04-data ; 引入相应的模块!基础模块;勾选 **Spring Web;JDBC API; MYSQL Driver** 依赖。 **2**、项目建好之后,发现自动帮我们导入了如下的启动器; ```xml org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java 8.0.33 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web ``` **3**、编写yaml配置文件连接数据库; ```yaml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # ?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/db_mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8 username: wan password: 101101 server: port: 8091 ``` **4**、配置完这些东西后,就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;测试类测试一下。 ```java @SpringBootTest class Springboot04DataApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { //查看一下默认的数据源 System.out.println(dataSource.getClass()); //获取数据库连接 Connection connection = dataSource.getConnection(); System.out.println(connection);//关闭连接 } } ``` 结果:可以看到他默认给我们配置的数据源为 : **class com.zaxxer.hikari.HikariDataSource** 我们并没有手动配置。 我们来全局搜索一下,找到数据源的所有自动配置都在 :**DataSourceAutoConfiguration**文件: ```java @Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class}) protected static class PooledDataSourceConfiguration { protected PooledDataSourceConfiguration() { } } ``` 这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.7.15默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源; **HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;** **可以使用 spring.datasource.type 指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。** 关于数据源我们并不做介绍,有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate ### 7.2 JDBCTemplate **1**、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库; **2**、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。 **3**、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。 **4**、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用 **5**、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类 **JdbcTemplate主要提供以下几类方法:** - execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句; - update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句; - query方法及queryForXXX方法:用于执行查询相关语句; - call方法:用于执行存储过程、函数相关语句。 ### 7.3 测试CURD **1**、编写一个**JdbcController** ,注入 jdbcTemplate,编写测试方法进行访问测试; ```java package com.xing.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @RestController @RequestMapping("/jdbc") public class JdbcController { /** * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作 * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接 */ @Autowired JdbcTemplate jdbcTemplate; //查询user表中所有数据 //List 中的1个 Map 对应数据库的 1行数据 //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值 @GetMapping("/list") public List> userList(){ String sql = "select * from user"; List> list = jdbcTemplate.queryForList(sql); System.out.println(list); return list; } //新增user @PostMapping("/add") //浏览器测试使用RequestMapping public String addUser(){ String sql = "INSERT INTO user (id, name, pwd) VALUES (23,'小张','123678')"; jdbcTemplate.update(sql); System.out.println("添加成功!"); String sql1 = "select * from user"; List> list = jdbcTemplate.queryForList(sql1); System.out.println(list); return "addOk"; } //更新user @PutMapping("/update/{id}") //浏览器测试使用RequestMapping public String updateUser(@PathVariable("id") int id){ String sql = "update user set name=?,pwd=? where id="+id; //数据 Object[] objects = new Object[2];//参数个数 objects[0] = "小周"; objects[1] = "369852"; jdbcTemplate.update(sql,objects); System.out.println("更新成功!"); return "updateOk"; } //删除user @DeleteMapping("/delete/{id}") //浏览器测试使用RequestMapping public String deleteUser(@PathVariable("id") int id){ String sql = "delete from user where id=?"; System.out.println("删除成功!"); return "deleteOk"; } } // DROP TABLE IF EXISTS `user`; // CREATE TABLE `user` ( // `id` int NOT NULL AUTO_INCREMENT, // `name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, // `pwd` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, // PRIMARY KEY (`id`) USING BTREE // ) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC; // INSERT INTO `user` VALUES(1,'小龙','123546'),(2,'aaa','bbb'),(3,'小明','3466'),(4,'小王','888'),(6,'Has','123321'), // (7,'小王','12'),(9,'小Q','58779'),(10,'小W','5123'),(13,'小丽','12345'),(14,'小W','5123'),(15,'小W','5123'), // (16,'小丽','12345'),(20,'小龙','123654'),(21,'小龙','123654'),(23,'小张','123678'); ``` **2**、测试请求,结果正常。到此,CURD的基本操作,使用 JDBC 就搞定了。 ## 8、springboot整合Druid ### 8.1 Druid简介 Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。 Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。 Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。 Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。 Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。 **Github地址**:https://github.com/alibaba/druid **com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:** ![](D:\2023\Java\SpringBoot\springboot-study\img\09.png) ![](D:\2023\Java\SpringBoot\springboot-study\img\10.png) ### 8.2 配置数据源 **1**、添加上 Druid 数据源依赖; ```xml com.alibaba druid 1.2.18 ``` **2**、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源; ```yaml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # ?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/db_mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8 username: wan password: 101101 type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源 server: port: 8091 ``` **3**、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换; ```java package com.xing; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; @SpringBootTest class Springboot04DataApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { //查看一下默认的数据源 System.out.println(dataSource.getClass()); //获取数据库连接 Connection connection = dataSource.getConnection(); System.out.println(connection);//关闭连接 } } ``` **4**、切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码; ```yaml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # ?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/db_mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8 username: wan password: 101101 type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源 #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 server: port: 8091 ``` **5**、导入Log4j 的依赖; ```xml log4j log4j 1.2.17 ``` **6**、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性; ```java package com.xing.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @Configuration public class DruidConfig { // 将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建 // 绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效 // 作用就是将 全局配置文件中前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中 @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDateSource(){ return new DruidDataSource(); } //后台监控 // 配置 Druid 监控管理后台的Servlet; // 内置 Servlet 容器时没有web.xml文件,所以使用SpringBoot的注册Servlet方式 @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到 Map initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); //后台管理界面的登录账号 initParams.put("loginPassword", "123456"); //后台管理界面的登录密码 //后台允许谁可以访问 //initParams.put("allow", "localhost"):表示只有本机可以访问 //initParams.put("allow", ""):为空或者为null时,表示允许所有访问 initParams.put("allow", ""); //deny:Druid 后台拒绝谁访问 initParams.put("wzx", "192.168.3.186"); //表示禁止此ip访问 //设置初始化参数 bean.setInitParameters(initParams); return bean; } //配置 Druid 监控 之 web 监控的 filter // WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计 @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计 Map initParams = new HashMap<>(); //这些东西不进行统计 initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*"); bean.setInitParameters(initParams); //"/*" 表示过滤所有请求 bean.setUrlPatterns(Arrays.asList("/*")); return bean; } } ``` **7**、去测试类中测试一下;看是否成功! ```java @SpringBootTest class Springboot04DataApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { //查看一下默认的数据源 System.out.println(dataSource.getClass()); //获取数据库连接 Connection connection = dataSource.getConnection(); System.out.println(connection);//关闭连接 DruidDataSource druidDataSource = (DruidDataSource) dataSource; System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive()); System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize()); connection.close(); } } ``` **输出结果 :** com.alibaba.druid.pool.DruidDataSource com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@260d48f5 druidDataSource 数据源最大连接数:21 druidDataSource 数据源初始化连接数:6 可见配置参数已经生效! ### 8.3 配置Druid数据源监控 Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。 所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理; ```java //后台监控 // 配置 Druid 监控管理后台的Servlet; // 内置 Servlet 容器时没有web.xml文件,所以使用SpringBoot的注册Servlet方式 @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到 Map initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); //后台管理界面的登录账号 initParams.put("loginPassword", "123456"); //后台管理界面的登录密码 //后台允许谁可以访问 //initParams.put("allow", "localhost"):表示只有本机可以访问 //initParams.put("allow", ""):为空或者为null时,表示允许所有访问 initParams.put("allow", ""); //deny:Druid 后台拒绝谁访问 //initParams.put("wzx", "192.168.3.186"); //表示禁止此ip访问 //设置初始化参数 bean.setInitParameters(initParams); return bean; } ``` 配置完毕后,我们可以选择访问 :http://localhost:8080/druid 使用账号密码登录。进入可以查看 **Stat Index 查看JSON API** **配置 Druid web 监控 filter 过滤器** ```java //配置 Druid 监控 之 web 监控的 filter // WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计 @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计 Map initParams = new HashMap<>(); //这些东西不进行统计 initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*"); bean.setInitParameters(initParams); //"/*" 表示过滤所有请求 bean.setUrlPatterns(Arrays.asList("/*")); return bean; } ``` 平时在工作中,按需求进行配置即可,主要用作监控! ### @学不可以已 springboot-04-data 已完结。 springboot-04-data 模块中 (IDEA创建springboot项目+整合jdbcCURD+整合druidDruid配置) 。 ## 9、springboot整合MyBatis(springboot-05-mybatis) **整合MyBatis** 官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter ### 9.1 整合测试 **1**、创建项目springboot-05-mybatis; **2**、导入相关依赖; ```xml org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-web com.mysql mysql-connector-j runtime org.springframework.boot spring-boot-starter-test test org.mybatis.spring.boot mybatis-spring-boot-starter 2.3.1 com.alibaba druid 1.2.18 org.projectlombok lombok org.springframework.boot spring-boot-maven-plugin src/main/java **/*.xml true ``` **3**、配置数据库连接信息 ```properties server.port=8092 spring.datasource.username=wan spring.datasource.password=101101 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8 #整合mybatis #如果没有aliases UserMapper.xml中parameterType="User"会报错 mybatis.type-aliases-package=com.xing.domain mybatis.mapper-locations=classpath:mapper/*.xml ``` **4**、测试数据源和连接; ```java @SpringBootTest class Springboot05MybatisApplicationTests { @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { System.out.println(dataSource.getClass()); System.out.println(dataSource.getConnection()); } } ``` **5**、创建 User.java实体类,导入 Lombok; ```java @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; private String pwd; } ``` **6**、创建mapper目录以及对应的 Mapper 接口; ```java package com.xing.mapper; import com.xing.domain.User; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; import java.util.List; @Mapper //@Mapper : 表示本类是一个 MyBatis 的 Mapper @Repository public interface UserMapper { // 获取所有用户信息 List queryUserList(); // 通过id获得用户 User queryUserById(int id); int addUser(User user); int updateUser(User user); int deleteUserById(int id); } ``` **7**、对应的Mapper映射文件; ```xml insert into user (id,name,pwd) values (#{id},#{name},#{pwd}); update user set name = #{name},pwd = #{pwd} where id = #{id}; delete from user where id = #{id}; ``` **8**、编写**UserController** 进行测试! ```java package com.xing.controller; import com.xing.domain.User; import com.xing.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/User") public class UserController { @Autowired UserMapper userMapper; //查询全部用户 @GetMapping("/queryList") public List queryUser(){ return userMapper.queryUserList(); } //根据用户id查询用户 @GetMapping("/queryUserById/{id}") public User queryUserById(@PathVariable("id") int id){ return userMapper.queryUserById(id); } //添加用户数据 @PostMapping("/addUser") public int addUser(@RequestBody User user){ return userMapper.addUser(user); } //修改用户数据 @PutMapping("/updateUser") public int updateUser(@RequestBody User user){ return userMapper.updateUser(user); } //删除用户数据 @DeleteMapping("/deleteUserById/{id}") public int deleteUserById(@PathVariable("id") int id){ return userMapper.deleteUserById(id); } } ``` **启动项目访问进行测试**!http://localhost:8092/User/... 根据具体方法地址修改url进行测试。 **测试结果完成,搞定收工!** ### @学不可以已 springboot-05-mybatis 已完结。 springboot-05-mybatis 模块中 (IDEA创建springboot项目+整合mybatis+CRUD测试) 。 ## 10、springboot整合SpringSecurity(springboot-06-security) ### 10.1 安全简介 在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。 市面上存在比较有名的:**Shiro,Spring Security !** 这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢? 首先我们看下它的官网介绍:Spring Security官网地址 Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。 Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求 从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。 怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。 Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。 ### 10.2 实验环境搭建 **1**、新建一个初始的springboot项目添加web模块,thymeleaf模块; **2**、导入静态资源; ```java |views |level1 1.html 2.html 3.html |level2 1.html 2.html 3.html |level3 1.html 2.html 3.html login.html index.html ``` **3**、RouterController跳转; ```java @Controller public class RouterController { @RequestMapping({"/","/index"}) public String index() { return "index"; } @RequestMapping("/toLogin") public String toLogin() { return "views/login"; } @RequestMapping("/level1/{id}") public String level1(@PathVariable("id") int id) { return "views/level1/" + id; } @RequestMapping("/level2/{id}") public String level2(@PathVariable("id") int id) { return "views/level2/" + id; } @RequestMapping("/level3/{id}") public String level3(@PathVariable("id") int id) { return "views/level3/" + id; } } ``` **4**、测试 http://localhost:8080 实验环境是否OK! ### 10.3 整合 SpringSecurity Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理! 记住几个类: - WebSecurityConfigurerAdapter:自定义Security策略 - AuthenticationManagerBuilder:自定义认证策略 - @EnableWebSecurity:开启WebSecurity模式 Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。 **“认证”(Authentication)** 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。 身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。 **“授权” (Authorization)** 授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。 这个概念是通用的,而不是只在Spring Security 中存在。 ### 10.4 认证和授权 目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能。 **1**、引入 Spring Security 模块; ```xml org.springframework.boot spring-boot-starter-security ``` **2**、编写 Spring Security 配置类; 参考官网:https://spring.io/projects/spring-security 查看我们自己项目中的版本,找到对应的帮助文档: https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4 **3**、编写基础配置类; ```java package com.kuang.config; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity //开启WebSecurity模式 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { } } ``` **4**、定制请求的授权规则; ``` @EnableWebSecurity //开启WebSecurity模式 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 定制请求的授权规则 // 首页所有人可以访问 http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); } ``` **5**、测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以; **6**、在configure()方法中加入以下配置,开启自动配置的登录功能; ```java http.formLogin();//内部登录测试 ``` **7**、测试一下:http://localhost:8080 发现,没有权限的时候,会跳转到登录的页面; **8**、查看刚才登录页的注释信息; 我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法 ```java // 认证通过的用户 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // 设置用户名密码 auth.inMemoryAuthentication().passwordEncoder() .withUser("xing").password("123456").roles("vip2","vip3") .and() .withUser("root").password("123456").roles("vip1") .and() .withUser("admin").password("123456").roles("vip1","vip2","vip3"); } ``` **9**、测试,我们可以使用这些账号登录进行测试!发现会报错;There is no PasswordEncoder mapped for the id “null” **10**、原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码; ```java // 认证通过的用户 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // 设置用户名密码 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("xing").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1") .and() .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3"); } ``` **11**、测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定。 ### 10.5 权限控制和注销 **1**、开启自动配置的注销的功能; ```java //定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //.... //开启自动配置的注销的功能 logout注销请求 http.logout(); } ``` **2**、我们在前端,增加一个注销的按钮,index.html 导航栏中; ```html
注销 ``` **3**、测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面; **4**、但是,想让他注销成功后,依旧可以跳转到首页,该怎么处理呢; ```java // 定制退出登录 注销成功来到首页 http.logout().logoutSuccessUrl("/"); ``` **5**、测试,注销完毕后,发现跳转到首页OK; **6**、我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如xing这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢? 我们需要结合thymeleaf中的一些功能sec:authorize="isAuthenticated()":是否认证登录!来显示不同的页面; Maven依赖: ```xml org.thymeleaf.extras thymeleaf-extras-springsecurity5 ``` **7**、修改我们的 前端页面; **①**、导入命名空间 **②**、`xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">` **③**、修改导航栏,增加认证判断 **④**、index.html ```html