跟随狂神学Java-38,SpringBoot

跟随狂神学Java-38,SpringBoot
joker2yue跟随狂神学Java
作者:joker2yue
链接:https://github.com/Joker2Yue/Joker2Yue-Blog
来源:Github
著作权归原作者所有。商业转载请联系原作者获得授权,非商业转载请注明出处。
第三十八:微服务-SpringBoot
“真正的危险不是计算机开始像人一样思考,而是人开始像计算机一样思考。”
【狂神说Java】SpringBoot最新教程IDEA版通俗易懂_哔哩哔哩_bilibili
历史回顾与学习安排
历史回顾
JavaSE:OOP思想
Mysql:持久化
HTML+CSS+JS+jQuery+框架:视图。做的不好看的原因是框架不熟练,css技能不好
SSM:框架,简化了我们的开发流程,但是随着版本迭代行新特性更新,配置也开始变得复杂
于是,有了SpringBoot,微服务架构
而后面服务越来越多,也就有了SpringCloud
学习安排
 
Spring
什么是Spring
- 一个开源的框架,2003年兴起的一个轻量级Java开发框架。作者:Rod Johnson
- 它是为了解决企业级应用开发的复杂性而创建的,简化开发。
Spring是如何简化Java开发的
为了降低 Java 开发的复杂性, Spring 采用了以下 4 种关键策略:
- 基于 POJO 的轻量级和最小侵入性编程
- 通过IoC、依赖注入(DI)和面向接口实现松耦合
- 基于切面 (AOP) 和惯例进行声明式编程
- 通过切面和模版减少样式代码
什么是SpringBoot
 学过JavaWeb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat,跑出一个HelloWolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;不知道你们有没经历过框架不断的演进,然后自已开发项自所有的技术也再不断的变化、改造,反正我是都经历过了,哈哈。言归止传,什么是SpringBoot呢,就是一个JavaWeb的开发框架,和SpringMVC类似,对比其他JavaWeb框架的好处,官方说是简化开发,约定大于配置,you can”just run”,能迅速的开发web应用,几行代码开发一个HTTP接口。
 所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景衍生一种规范框架,人们只需要进行各种配置而不需要自已去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开友效率,嫌弃原先的各类配置过于林烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。
 是的这就是Java企业级应用->J2EE->Spring->SpringBoot的过程。
 随着Spring不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。SpringBoot正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring、更容易的集成各种常用的中间件、开源软件;
 SpringBoot基于Spring开发,SpringBoot本身并不提供Spring框架的核心特性以及扩用功能,只是用于快速、敏捷地开发新一代基于 Spring框架的应用程序。也就是说,它并不是用来替Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。SpringBoot以约定大于配置的核心思想,默认帮我们进行了很多设置,多数SpringBoot应用只需要很少的Spring配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz等等),SpringBoot应用中这些第三方库几乎可以零配置的开箱即用
 简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的iar包,SpringBoot整合了所有的框架。
 SpringBoot出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,SpringBoot已经当之无愧成为Java领域最热门的技术。
SpringBoot的主要优点
- 为所有的Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码和XML配置的要求
微服务
什么是微服务
 微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构。
单体应用架构
 所谓单体应用架构(all in none)是指,我们门将一入应用的中的所有应用服务者都封装在一入应用中。
 无论是ERP、CRM或是其他什么系统,你都把数据库访问,Web访问,等等各个功能放到一个War包内。
- 这样做的好处是,易于开发和测试;也十分方便部署;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。 
- 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能吧所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。 
微服务架构
 all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部暑到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。
 所谓微服务架构,就是打破之前allinone的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素。所以微服务架构是对功能元索进行复制,而没有对整个应用进行复制。
 这样做的好处是:
- 节省了调用资源
- 每个功能元素的服务都是一个可替换的、可独立升级的软件代码
 
如何构建微服务
 一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过htp相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。
	但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布
式微服务的全套、全程产品:
- 构建一个个功能独立的微服务应用单元,可以使用SpringBoot,可以帮我们快速构建一个应用;
- 大型分布式网络服务的调用,这部分由SpringBoot来完成,实现分布式;
- 在分布式中间,进行流式数据计算、批处理,我们有SpringCloud, data flow。
- Spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案
 
第一个SpringBoot程序
创建项目
你可以在官网Spring Initializr下载配置,解压后导入Idea。或者直接在Idea中新建Spring 项目
 
 
第一行代码
- 勾选模板创建SpringBoot项目,将会自动生成大致如下的目录结构:   
- 直接右上角运行,你可以在 - localhost:8080查看运行效果  
- 这里我们新建一个controller作为测试(HelloController.java) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22- package com.joker_yue.springbootlearn.controller; 
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 /**
 *
 * @author Joker
 * @date 2023/8/6 19:49
 * @version 1.0
 */
 public class HelloController {
 // 接口就是http://localhost:8080/hello
 
 public String hello(){
 // 调用业务,接收前端参数
 return "hello,world";
 }
 } 
项目打包
我们可以将这个项目打包成一个jar包,以方便我们日后部署到其他地方
- 点击Maven-生命周期-package,将会自动开始打包   
- 打包完成后将会输出相应信息   
- 如果报错没有主清单,在pom文件中新增 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28- <build> 
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 <executions>
 <execution>
 <goals>
 <goal>repackage</goal>
 </goals>
 </execution>
 </executions>
 <configuration>
 <mainClass>类路径,比如com.joker_yue.SpringBootLearn</mainClass>
 <excludes>
 <exclude>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 </exclude>
 <exclude>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 </exclude>
 </excludes>
 </configuration>
 </plugin>
 </plugins>
 </build>
- 在target下会生成对应jar包  
- 使用 - java -jar命令运行jar包 
项目配置文件
- 修改端口号 - 你可以在application.properties中输入 - server.port=8081来讲端口号改成8081
- 自定义banner - 你可以在application.properties同级目录下创建banner.txt,以自定义banner:Spring Boot banner在线生成工具,只需要在banner.txt中放入你的banner就行  
原理初探
自动配置
- pom.xml - 在pom.xml中,有一个父依赖,如下: - 1 
 2
 3
 4
 5
 6
 7- <!-- 有一个父项目 --> 
 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.7.14</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>- 你可以通过点击 - artifactId中来找到上一层父依赖- 最顶层的依赖为 - spring-boot-dependencies-版本号.pom,在那里,你可以看到所有的依赖项。这就是你不用手动去导入大量jar包的真相
- 我们在写或者引入一些SpringBoot依赖的时候,不需要指定版本,因为有这些版本仓库 
 
- 启动器 - 说白了就是SpringBoot的启动场景 - 1 
 2
 3
 4
 5
 6- <!-- starter:启动器 --> 
 <dependency>
 <!-- web依赖,tomcat,dispatcherServlet,serlvet等等 -->
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
- 比如 - spring-boot-starter-web,他就会自动帮我们导入web环境的所有依赖
- SpringBoot会将所有的功能场景都变成一个个的启动器 
- 我们要使用什么功能,就需要找到对应的启动器就行了 
 
主程序
- @SpringBootApplication- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19- package com.joker_yue.springbootlearn; 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 // SpringBoot程序入口,不能动也不能删除
 // 本身就是Spring的一个组件
 // @SpringBootApplication 标注这个类是一个SpringBoot的应用
 public class SpringBootLearnApplication {
 public static void main(String[] args) {
 // 讲SpringBoot应用启动
 SpringApplication.run(SpringBootLearnApplication.class, args);
 }
 }
- 注解 - 1 
 2
 3
 4
 5
 6
 7
 8
 9- SpringBoot的配置 
 Spring配置类
 说明这是一个Spring的组件
 
 自动配置
 自动配置包
 自动配置-包注册
 自动配置导入选择
 - 自动配置的包  
 
原理图

自动装配结论
SpringBoot的所有自动配置都在启动类中被扫描并加载,位置在spring.factories中。
但是这些配置不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了。有了启动器,我们自动装配就会生效,然后就配置成功了
- SpringBoot在启动的时候,从类路径下/META-INFO/spring.factories中获取指定的值
- 将这些自动配置的类导入容器,自动配置类就会生效,帮我们进行自动配置
- 以前我们需要手动配置的东西,现在SpringBoot帮我们做了
- 整个JavaEE,解决方案和自动配置,都在spring-boot-autoconfigure-2.7.14.jar这个包下
- 他会将所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件
- 有了自动配置类,免去了我们手动编写配置文件的工作!
一旦这个配置类生效,这个配置类就会给容器中添加各种组件,这些组件的属性从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的
- SpringBoot启动时会加载大量的自动配置类
- 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类中
- 我们再来看这个自动配置类中到底配置了哪些组件(只要我们要用的组件存在其中,我们就不需要再手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可
| 1 | xxxAutoConfiguration,自动装配类,给容器中添加组件 | 
SpringApplication、Run
还记得程序的主入口吗?
| 1 | package com.joker_yue.springbootlearn; | 
- SpringApplication.run方法分析 - SpringApplication的实例化
- run方法的执行
 
- SpringApplication - 这个类主要做了以下四件事情 - 推断应用的类型是普通的项目还是web项目
- 查找并加载所有可用初始化器,设置到initializers属性中
- 找出所有的应用程序监听器,设置到listener属性中
- 推断并设置main方法的定义类,找到运行的主类
 - 查看构造器 

SpringBoot配置
文件说明
官方的配置文件为application.properties或application.yaml
- properties - 语法结构:key=value
 
- 语法结构:
- yaml - 语法结构: - key: value
- 注意:中间有个空格 
- 你甚至可以用它来写对象,它真的很像json - 1 
 2
 3
 4
 5
 6
 7- # 对象 
 student:
 name: joker
 age: 19
 # 行内对象
 student: {name: joker, age: 19}
- 你还可以将它写数组 - 1 
 2
 3
 4
 5
 6
 7
 8- # 数组 
 pets:
 - cat
 - dog
 
 
 # 行内数组
 pets: [cat, dog, pig]
 
yaml给对象赋值
- 传统的赋值方式 - Dog.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50- package com.joker_yue.springbootlearn.pojo; 
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/7 14:34
 */
 public class Dog {
 
 private String name;
 
 private Integer age;
 public Dog() {
 }
 public Dog(String name, Integer age) {
 this.name = name;
 this.age = age;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public Integer getAge() {
 return age;
 }
 public void setAge(Integer age) {
 this.age = age;
 }
 
 public String toString() {
 return "Dog{" +
 "name='" + name + '\'' +
 ", age=" + age +
 '}';
 }
 }
- 测试 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21- package com.joker_yue.springbootlearn; 
 import com.joker_yue.springbootlearn.pojo.Dog;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 // 单元测试
 class SpringBootLearnApplicationTests {
 
 private Dog dog;
 
 void contextLoads() {
 System.out.println(dog);
 }
 }
 
- 现在我们使用application.yaml来进行装配 - application.yaml - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- person: 
 name: Joker
 age: 18
 happy: true
 birth: 2023/08/07
 maps: {k1: v1, k2: v2}
 lists:
 - code
 - music
 - girl
 dog:
 name: 旺财
 age: 3
- person.java - @ConfigurationProperties注解的作用是将配置文件(- application.yaml)中的属性值绑定到Java类的对应属性上。在Spring Boot中,我们可以使用这个注解来方便地获取配置文件中的属性值,并将其映射到Java类中的属性上。- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107- package com.joker_yue.springbootlearn.pojo; 
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/7 14:37
 */
 //
 public class Person {
 private String name;
 private Integer age;
 private boolean happy;
 private Date birth;
 private Map<String, Object> maps;
 private List<Object> lists;
 private Dog dog;
 public Person() {
 }
 public Person(String name, Integer age, boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
 this.name = name;
 this.age = age;
 this.happy = happy;
 this.birth = birth;
 this.maps = maps;
 this.lists = lists;
 this.dog = dog;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public Integer getAge() {
 return age;
 }
 public void setAge(Integer age) {
 this.age = age;
 }
 public boolean isHappy() {
 return happy;
 }
 public void setHappy(boolean happy) {
 this.happy = happy;
 }
 public Date getBirth() {
 return birth;
 }
 public void setBirth(Date birth) {
 this.birth = birth;
 }
 public Map<String, Object> getMaps() {
 return maps;
 }
 public void setMaps(Map<String, Object> maps) {
 this.maps = maps;
 }
 public List<Object> getLists() {
 return lists;
 }
 public void setLists(List<Object> lists) {
 this.lists = lists;
 }
 public Dog getDog() {
 return dog;
 }
 public void setDog(Dog dog) {
 this.dog = dog;
 }
 
 public String toString() {
 return "Person{" +
 "name='" + name + '\'' +
 ", age=" + age +
 ", happy=" + happy +
 ", birth=" + birth +
 ", maps=" + maps +
 ", lists=" + lists +
 ", dog=" + dog +
 '}';
 }
 }
- 运行结果  
 
当然,使用peoperties赋值也是可以的,但是会比较复杂
- person.properties - 1 - name=joker_yue 
- person.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 //注册bean
 public class Person {
 
 private String name;
 ......
 }
- 测试  
在yaml配置文件中还可以编写占位符生成随机数
| 1 | person: | 
- 运行结果 
松散绑定
例如Person类中有个lastName,yaml文件中的是lastname、last-name或者last_name,这些都能绑定到,就这么简单
JSR303数据校验
你需要依赖:
| 1 | <dependency> | 
举个例子,有些数据校验比如说手机号、邮箱等,格式不对将无法通过校验。在SpringBoot中我们可以使用 @Validated注解来进行数据校验,像这样
| 1 | 
 | 
常见参数
| 1 | 
 | 
多环境配置以及配置文件位置
- 配置文件位置 - 你可以将 - application.yaml配置文件放在以下位置(按照读取顺序优先级排列)- file:./config/  
- file:./  
- classpath:/config/  
- classpath:/ 
 - ==同个路径下,yml>yaml>properties,可以在spring-boot-starter-partent里找到。== 
- 多环境配置 - 你可以将文件以 - application-[filename].yaml的方式命名,这样不同的配置文件将会对应不同的环境,就像这样 
- 你也可以通过yaml来一次性配置多个环境 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16- # SpringBoot 的多环境配置,可以选择激活哪一个配置 
 server:
 port: 8081
 spring:
 profiles:
 active: dev
 server:
 port: 8082
 spring:
 profiles: dev
 server:
 port: 8083
 spring:
 profiles: test 
 
参考:
SpringBoot Web开发
静态资源导入
- webjars - 你可以在maven中导入对应的依赖来获取对应的静态资源  - 访问方式如下   
- 固定位置支持 - SpringBoot支持以下位置:(按照优先级排序) - classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/
 - 你在 - localhost/8080下输入- xxx.js,只要上述4个位置中有,就能访问得到
 
- 自定义位置 - 不建议使用,因为你需要将上述4个地址手动加入配置,否则4个地址将会失效 - 1 
 2
 3
 4
 5
 6
 7
 8- spring: 
 mvc:
 # URL响应地址(Springboot默认为/**)
 static-path-pattern: /SystemData/**
 web:
 resources:
 # 静态文件地址,保留官方内容后,进行追加
 static-locations: classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources,file:SystemData- 参考:Springboot多种方法处理静态资源:设置并访问静态资源目录_springboot 添加静态目录_Mintimate的博客-CSDN博客 
 
首页定制
默认目录:
- 你可以将首页放在resources目录下或者其下任意一个文件夹中  
Controller跳转
- 在templates目录下的所有页面,只能通过Controller来跳转,需要模板引擎Thymeleaf的支持,见下一章
修改默认图标
- 你可以在 - application.yaml中写入- 1 
 2
 3
 4
 5- # 关闭默认图标 
 spring:
 mvc:
 favicon:
 enabled: false
- 然后再将图标 - favicon.ico放入- public文件夹中即可
模板引擎-Thymeleaf
你需要导入如下依赖
| 1 | <!-- Thymeleaf --> | 
- 你只需要使用Thymeleaf,将html放在templetes下,写好对应Controller就行 - IndexController.java - 1 
 2
 3
 4
 5
 6
 7
 8
 public class IndexController {
 
 public String index(Model model){
 model.addAttribute("msg","Hello Spring Boot!");
 return "test";
 }
 }
- test.html - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 <!-- 导入 xmlns:th="http://www.thymeleaf.org" 约束 -->
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
 <meta charset="UTF-8" >
 <title>Title</title>
 </head>
 <body>
 <!--所有的html元素都可以被thymeleaf接管。th:元素名-->
 <h1 th:text="${msg}"></h1>
 </body>
 </html>
 
Thymeleaf语法
- 简单表达式:- 变量表达式:${...}
- 选择变量表达式:*{...}
- 消息表达式:#{...}
- 链接网址表达式:@{...}
- 片段表达式:~{...}
 
- 变量表达式:
- 文字- 文本文字:、,…'one text'``'Another one!'
- 数字文字:、、、,…0``34``3.0``12.3
- 布尔文字: ,true``false
- 空文本:null
- 文字标记:、、,…one``sometext``main
 
- 文本文字:、,…
- 文本操作:- 字符串连接:+
- 文字替换:|The name is ${name}|
 
- 字符串连接:
- 算术运算:- 二元运算符:、、、、+``-``*``/``%
- 减号(一元运算符):-
 
- 二元运算符:、、、、
- 布尔运算:- 二元运算符: ,and``or
- 布尔取反(一元运算符): ,!``not
 
- 二元运算符: ,
- 比较和平等:- 比较器: , , , (, , , ,>``<``>=``<=``gt``lt``ge``le)
- 相等运算符:, (,==``!=``eq``ne)
 
- 比较器: , , , (, , , ,
- 条件运算符:- 如果-那么:(if) ? (then)
- 如果-那么-否则:(if) ? (then) : (else)
- 违约:(value) ?: (defaultvalue)
 
- 如果-那么:
- 特殊令牌:- 非运营:_
 
- 非运营:
| 次序 | 特征 | 属性 | 
|---|---|---|
| 1 | 片段包含 | th:insertth:replace | 
| 2 | 片段迭代 | th:each | 
| 3 | 条件评估 | th:ifth:unlessth:switchth:case | 
| 4 | 局部变量定义 | th:objectth:with | 
| 5 | 常规属性修改 | th:attrth:attrprependth:attrappend | 
| 6 | 特定属性修改 | th:valueth:hrefth:src... | 
| 7 | 文本(标签正文修改) | th:textth:utext | 
| 8 | 片段规格 | th:fragment | 
| 9 | 片段去除 | th:remove | 
MVC配置原理
- 扩展MVC配置(了解即可) - 你可以在类上加入@Configuration,并将此类实现WebMvcConfiguration接口以扩展MVC配置 
- 可重写的方法如下图   
- 自定义视图解析器 - 你可以 - implements ViewResolver,然后重写- resolveViewName方法,再注册bean就可以装配进SpringBoot- 1 
 2
 3
 4
 public ViewResolver myViewResolver() {
 return new MyViewResolver();
 }
 
- 扩展SpringMVC - 视图跳转 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23- package com.joker_yue.springbootlearn.config; 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 13:52
 */
 // 如果我们要扩展MVC,官方建议我们这样使用!
 // 这玩意导入了一个类,DelegatingWebMvcConfiguration:这个类会从容器中获取所有的webmvcconfig。如果你加了这个注解,你需要全部重写SpringMVC的配置
 public class MyMVCConfig implements WebMvcConfigurer {
 // 视图跳转
 
 public void addViewControllers(ViewControllerRegistry registry) {
 registry.addViewController("/joker").setViewName("test");
 }
 }
- 在SpringBoot中,有非常多的xxxConfiguration帮助我们进行扩展配置,只要看见了这个东西,我们就需要注意了! 
 
员工管理系统
目录结构
将资源文件放入后,目录结构如图:
 
首页定制
- 视图跳转 - Controller实现视图解析:Index.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17- package com.joker_yue.controller; 
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 16:42
 */
 public class IndexController {
 
 public String index() {
 return "index";
 }
 }
- 自定义MVC实现视图跳转:MyMvcController.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23- package com.joker_yue.config; 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 16:45
 */
 // 如果我们要扩展MVC,官方建议我们这样使用!
 public class MyMvcController implements WebMvcConfigurer {
 // 视图跳转
 
 public void addViewControllers(ViewControllerRegistry registry) {
 registry.addViewController("/").setViewName("index");
 registry.addViewController("/index.html").setViewName("index");
 }
 }
 
- 修改资源匹配地址 - 首页的所有静态资源都需要使用Thymeleaf接管 - 你可以将资源目录上加上 - @{},就像这样- 1 - @{/css/bootstrap.min.css} - 这样的话,所有默认位置的地址都能匹配到 
页面国际化
- 通用配置 - 在 - resources下新建文件夹- i18n
- 在 - i18n下新建几个文件,如下:  
- 你可以点击下方【资源包】来一次性配置多个properties文件,需要 Resource Bundle Editor 插件  
- 编辑器效果如图   
- 写好后如图   
- 在配置文件中定义国际化文件的位置 - 1 
 2
 3
 4- # 国际化配置 
 spring:
 messages:
 basename: i18n.login
- 在index.html中的切换语言按钮上定义 - 1 
 2- <a class="btn btn-sm" th:href="@{/index.html(l='zh_CH')}">中文</a> 
 <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
 
- 自定义 - 说到底,国际化的实现还是通过解析前端发送过来的参数做到的。所以我们可以自定义解析规则。 
- 自定义组件:MyLocaleResolver.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37- package com.joker_yue.config; 
 import org.springframework.util.StringUtils;
 import org.springframework.web.servlet.LocaleResolver;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.util.Locale;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 19:45
 */
 public class MyLocaleResolver implements LocaleResolver {
 // 解析请求
 
 public Locale resolveLocale(HttpServletRequest request) {
 // 获取请求中的语言参数
 String language = request.getParameter("l");
 System.out.println("Debug==>"+language);
 Locale locale = Locale.getDefault(); // 如果没有就使用默认的
 // 如果请求的链接携带了国际化的参数
 if(!StringUtils.isEmpty(language)){
 // 比如传过来的是zh_CN
 String[] split = language.split("_"); // 将会分解为ch和CN
 // 国家 地区
 locale = new Locale(split[0],split[1]);
 }
 return locale;
 }
 
 public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
 }
 }
- 然后再去自定义的MVC中配置下就可以了,配置到Spring容器中。 - 1 
 2
 3
 4
 5- // 自定义国际化组件 
 public LocaleResolver localeResolver(){
 return new MyLocaleResolver();
 }
 
判断用户登录
- 编写业务:LoginController.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29- package com.joker_yue.controller; 
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 20:04
 */
 public class LoginController {
 
 public String login( String username, String password, Model model) {
 // 具体的业务
 if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
 // 登录成功
 return "dashboard";
 } else {
 // 告诉用户登陆失败
 model.addAttribute("msg", "用户名或密码错误");
 return "index";
 }
 }
 }
- 判断后端返回消息是否为空 - 1 
 2- <!-- 登录消息回显,判断返回消息是否为空,为空,不显示代码 --> 
 <p style="color: red" th:text="${msg}" th:if="${!#strings.isEmpty(msg)}"></p>
- 由于地址栏不好看我们得改下   - LoginController.java:重定向路径 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28- package com.joker_yue.controller; 
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 20:04
 */
 public class LoginController {
 
 public String login( String username, String password, Model model) {
 // 具体的业务
 if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
 // 重定向路径
 return "redirect:/main.html";
 } else {
 // 告诉用户登陆失败
 model.addAttribute("msg", "用户名或密码错误");
 return "index";
 }
 }
 }
- MyMvcController:修改视图解析器 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33- package com.joker_yue.config; 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.LocaleResolver;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 16:45
 */
 // 如果我们要扩展MVC,官方建议我们这样使用!
 public class MyMvcController implements WebMvcConfigurer {
 // 视图跳转
 
 public void addViewControllers(ViewControllerRegistry registry) {
 registry.addViewController("/").setViewName("index");
 registry.addViewController("/index.html").setViewName("index");
 // 如果是main,映射到dashboard
 registry.addViewController("/main.html").setViewName("dashboard");
 }
 // 自定义国际化组件
 
 public LocaleResolver localeResolver(){
 return new MyLocaleResolver();
 }
 }
- 现在地址栏好看了  
 
用户权限拦截
- 自定义过滤器:LoginHandlerInterceptor.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25- package com.joker_yue.config; 
 import org.springframework.web.servlet.HandlerInterceptor;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/8 20:42
 */
 public class LoginHandlerInterceptor implements HandlerInterceptor {
 
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 // 登录成功后,应该有用户的Session
 Object loginUser = request.getSession().getAttribute("loginUser");
 if (loginUser == null) {
 request.setAttribute("msg", "没有权限,请先登录");
 request.getRequestDispatcher("/index.html").forward(request, response);
 return false;
 }
 return true;
 }
 }
- 将过滤器添加到解析器中:MyMvcController.java - 1 
 2
 3
 4
 5
 6
 7
 public void addInterceptors(InterceptorRegistry registry) {
 registry.addInterceptor(new LoginHandlerInterceptor())
 .addPathPatterns("/**")
 .excludePathPatterns("/index.html", "/", "/user/login"
 ,"/css/**", "/js/**", "/img/**");
 }
展示员工列表
- 提取公共页面- th:fragment="sidebar"
- <div th:insert="~{common/commons::topbar}"></div>
- 如果要传递参数,可以直接使用()传参,判断接收即可
 
- 列表循环展示
添加员工
- 按钮提交
- 跳转添加页面
- 添加员工成功
- 返回首页
404
你只需要在templates下建一个error文件夹,然后将html放进去就行
 
如何快速搭建一个Web应用
- 前端搞定:页面长什么样子,以做数据库设计
- 设计数据库
- 前端让它们能够自动运行,独立化工程
- 数据接口如何对接:JSON,对象 all in one
- 前后端联调测试!
你需要:
- 一套自己熟悉的后台模板:工作必要。比如X-Admin
- 前端框架:至少自己能够通过前端框架,组合出来一个网站页面
- 让这个网站能够独立运行
整合
整合JDBC使用
| 1 | package com.joker_yue.controller; | 
整合Druid数据源
- pom.xml - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- <!-- Druid --> 
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid</artifactId>
 <version>1.2.8</version>
 </dependency>
 <!-- log4j -->
 <dependency>
 <groupId>log4j</groupId>
 <artifactId>log4j</artifactId>
 <version>1.2.17</version>
 </dependency>
- application.yaml - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30- # JDBC链接源 
 spring:
 datasource:
 url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
 username: root
 password: root
 driver-class-name: com.mysql.cj.jdbc.Driver
 type: com.alibaba.druid.pool.DruidDataSource
 #SpringBoot默认是不注入这些的,需要自己绑定
 #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.Properity
 #则导入log4j 依赖就行
 filters: stat,wall,log4j
 maxPoolPreparedStatementPerConnectionSize: 20
 useGlobalDataSourceStat: true
 connectionoProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
- DruidConfig.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51- package com.joker_yue.config; 
 import com.alibaba.druid.pool.DruidDataSource;
 import com.alibaba.druid.support.http.StatViewServlet;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 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.HashMap;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/11 13:02
 */
 public class DruidConfig {
 // 绑定Spring数据源
 
 
 public DataSource druidDataSource() {
 return new DruidDataSource();
 }
 // 后台监控:相当于web.xml
 // 因为SpringBoot内置了Servlet,没有web.xml,替代方法:ServletRegistrationBean
 
 public ServletRegistrationBean statViewServlet() {
 ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
 // 后台需要有人登录,账号密码配置
 HashMap<String, String> initParams = new HashMap<>();
 // 增加配置:用户名和密码。这两条的key不能乱写,是固定的
 initParams.put("loginUsername", "admin"); // 登录的key是固定的
 initParams.put("loginPassword", "123456"); // 密码的key也是固定的
 // 增加配置:允许谁能访问
 initParams.put("allow", "127.0.0.1"); // 为空,所有人都可以访问
 // 增加配置:禁止谁能访问
 // initParams.put("deny","244.178.44.111");
 bean.setInitParameters(initParams);
 return bean;
 }
 }
- 你可以访问 - http://localhost:8080/druid来进入监控页面
- 配置过滤器 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16- // Filter 
 public FilterRegistrationBean webStartFilter(){
 FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
 bean.setFilter(new WebStatFilter());
 // 可以设置请求
 HashMap<String, String> initParams = new HashMap<>();
 // 这些东西不进行统计
 initParams.put("exclusions", "*.js,*.css,/druid/*");
 bean.setInitParameters(initParams);
 return bean;
 }
整合Mybatis
- pom.xml:整合包 - 1 
 2
 3
 4
 5
 6- <!-- mybatis starter --> 
 <dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.1.3</version>
 </dependency>
- applicatin.properties:配置文件 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- spring.datasource.username=root 
 spring.datasource.password=root
 spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 # 整合Mybatis
 # Mybatis别名
 mybatis.type-aliases-package=com.joker_yue.pojo
 mybatis.mapper-locations=classpath:mapper/*.xml
- 导入Mapper,下方两种任选其一 - 在Mapper类中使用注解标明本类为Mapper类(Dao层) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- package com.joker_yue.mapper; 
 import com.joker_yue.pojo.User;
 import org.apache.ibatis.annotations.Mapper;
 import org.springframework.stereotype.Repository;
 import java.util.List;
 // 加上此注解,说明本类为一个Mybatis的一个Mapper类:Dao
 // 将数据访问层(DAO层)的类标识为Spring Bean
 public interface UserMapper {
 List<User> queryUserList();
 }
- 在需要使用Mapper的类中(Service层) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22- package com.joker_yue; 
 import org.junit.jupiter.api.Test;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import javax.sql.DataSource;
 class SpringBootMybatisApplicationTests {
 
 DataSource dataSource;
 
 void contextLoads() {
 System.out.println(dataSource.getClass());
 }
 }
 
- UserMapper.xml - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 <mapper namespace="com.joker_yue.mapper.UserMapper">
 <!-- <cache/> -->
 <select id="queryUserList" resultType="User">
 select * from user
 </select>
 <select id="selectUserById" resultType="User">
 select * from user where id = #{id}
 </select>
 <insert id="addUser" parameterType="User">
 insert into user (name, pwd) values (#{name}, #{pwd})
 </insert>
 <update id="updateUser" parameterType="User">
 update user set name = #{name}, pwd = #{pwd} where id = #{id}
 </update>
 <delete id="deleteUser" parameterType="int">
 delete from user where id = #{id}
 </delete>
 </mapper>
- UserController.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29- package com.joker_yue.controller; 
 import com.joker_yue.mapper.UserMapper;
 import com.joker_yue.pojo.User;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 import java.util.List;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/11 14:31
 */
 public class UserController {
 
 private UserMapper userMapper;
 
 public List<User> queryUserList(){
 List<User> userList = userMapper.queryUserList();
 for (User user : userList) {
 System.out.println(user);
 }
 return userList;
 }
 }
整合SpringSecurity
 Spring Security是针对 Spring 项目的安全框架,也是 Spring Boot 底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
 在web开发中,安全第一位。现在我们会的也就是过滤器,拦截器
 它是功能性需求:不是,这是非功能性需求
 做网站:安全应该在什么时候考虑:设计之初
 记住几个类:
- WebSecurityConfigurerAdapter: 自定义 Security 策略
- AuthenticationManagerBuilder: 自定义认证策略
- @EnableWebSecurity: 开启 WebSecurity 模式, @Enablexxxx 开启某个功能
市面上比较知名的一些安全架构:
- Shiro
- SpringSecurity
- 它们很像,除了类不一样,名字不一样,功能几乎都是类似的
- 功能:认证,授权(认证authentication,授权authorization)
代码:
- SecurityConfig.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66- package com.joker_yue.config; 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 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;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/11 16:05
 */
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 // 链式编程
 // 授权规则
 
 public void configure(HttpSecurity http) throws Exception {
 // 首页所有人都可以访问,功能页只对有权限的人开放
 http.authorizeRequests()
 .antMatchers("/").permitAll()
 .antMatchers("/level1/**").hasRole("vip1")
 .antMatchers("/level2/**").hasRole("vip2")
 .antMatchers("/level3/**").hasRole("vip3");
 // 没有权限会自动跳转到登录页面。需要注册请求"/login",如果登录错误git,就会跳转到"login?error"
 http.formLogin().loginPage("/toLogin");
 /*
 * 你可以将登录请求设置为如下,同时还能自定义接收的参数
 * http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("pwd");
 */
 // 开启了注销功能
 // 注销后指定跳转到首页
 http.logout().logoutSuccessUrl("/");
 // 关闭跨站攻击
 http.csrf().disable();
 // 开启记住我功能 Cookie的实现。自动清除Cookie:14天
 // 自定义接收的参数
 http.rememberMe().rememberMeParameter("remember");
 }
 // 认证规则
 /*
 * 密码编码:PasswordEncoder
 * 在Spring Security 5.0+中,新增了很多的加密方式。
 * 如果你不选择一种加密方式,那么明文密码将会不安全,会报500错误
 * */
 
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 // 这些数据应该从数据库中读。我们现在是在内存中虚拟了角色
 auth.inMemoryAuthentication()
 .passwordEncoder(new BCryptPasswordEncoder()) // 添加密码加密
 .withUser("joker").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
 .and()
 .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3")
 .and()
 .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
 }
 }
- login.html - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 <div style="text-align: center">
 <h1 class="header">登录</h1>
 </div>
 
 <div class="ui placeholder segment">
 <div class="ui column very relaxed stackable grid">
 <div class="column">
 <div class="ui form">
 <!--如果要使用toLogin,则http.formLogin().loginPage("/toLogin"); 配置一致。同时<form th:action="@{/toLogin}" method="post">
 如果非要使用login,则http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login"); 同时<form th:action="@{/login}" method="post">。 /toLogin是地址栏显示的请求地址,实际内部请求地址为/login
 -->
 <form th:action="@{/toLogin}" method="post">
 <div class="field">
 <label>工号</label>
 <div class="ui left icon input">
 <input type="text" placeholder="请输入工号..." name="username">
 <i class="user icon"></i>
 </div>
 </div>
 <div class="field">
 <label>密码</label>
 <div class="ui left icon input">
 <input type="password" placeholder="请输入密码..." name="password">
 <i class="lock icon"></i>
 </div>
 </div>
 <div class="field">
 <input type="checkbox" name="remember">记住我
 </div>
 <input type="submit" class="ui blue submit button"/>
 </form>
 </div>
 </div>
 </div>
 </div>
- index.html - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91- <!--主容器--> 
 <div class="ui container">
 <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
 <div class="ui secondary menu">
 <a class="item" th:href="@{/index}">首页</a>
 <!--登录注销-->
 <div class="right menu">
 <!--如果没有登录-->
 <div sec:authorize="!isAuthenticated()">
 <a class="item" th:href="@{/toLogin}">
 <i class="address card icon"></i> 登录
 </a>
 </div>
 <div sec:authorize="isAuthenticated()">
 <!--如果登录:用户名,注销-->
 <!--注销-->
 <a class="item">
 <!-- 获得用户名和用户权限 -->
 用户名:<span sec:authentication="name"></span>
 角色:<span sec:authentication="principal.authorities"></span><!--SpringSecurity5获取角色的方式-->
 </a>
 </div>
 <div sec:authorize="isAuthenticated()">
 <!--如果登录:用户名,注销-->
 <!--注销-->
 <a class="item" th:href="@{/logout}">
 <i class="sign-out icon"></i> 注销
 </a>
 </div>
 </div>
 </div>
 </div>
 <div class="ui segment" style="text-align: center">
 <h3>Spring Security Study by 秦疆</h3>
 </div>
 <div>
 <br>
 <div class="ui three column stackable grid">
 <!--菜单根据用户角色动态实现-->
 <div class="column" sec:authorize="hasRole('vip1')">
 <div class="ui raised segment">
 <div class="ui">
 <div class="content">
 <h5 class="content">Level 1</h5>
 <hr>
 <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
 <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
 <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
 </div>
 </div>
 </div>
 </div>
 <div class="column">
 <div class="ui raised segment" sec:authorize="hasRole('vip2')">
 <div class="ui">
 <div class="content">
 <h5 class="content">Level 2</h5>
 <hr>
 <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
 <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
 <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
 </div>
 </div>
 </div>
 </div>
 <div class="column">
 <div class="ui raised segment" sec:authorize="hasRole('vip3')">
 <div class="ui">
 <div class="content">
 <h5 class="content">Level 3</h5>
 <hr>
 <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
 <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
 <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
 </div>
 </div>
 </div>
 </div>
 </div>
 </div>
 </div>
Swagger
了解前后端分离
前后端分离时代:
- 后端:后端控制层,服务层,数据访问层 (由后端团队开发)
- 前端:前端控制层,视图层(由前端团队开发)。- 伪造后端数据。 json 已经存在了,可以不需要后端,前端工程依旧能够跑起来,做一个演示的效果
 
- 前后端如何交互:API
- 前后端相互独立,松耦合
- 前后端甚至可以部署在不同的服务器上
产生一个问题:
- 前后端集成联调,前端人员和后端人员无法做到”即时协商,尽早解决”,最终导致问题集中爆发
解决方案:
- 指定schema【计划的提纲】,实时更新最新的API,降低集成风险 
- 早些年:指定word计划文档 
- 前后端分离: - 前端测试后端接口:Postman
- 后端提供接口,需要实时更新最新的消息及改动
 
了解Swagger的作用和概念
- 号称世界上最流行的API框架
- RestFul API,文档在线生成自动生成工具。API文档与代码同步更新
- 直接运行,可以在线测试API接口
- 支持多种语言:Java,PHP等
官网:API Documentation & Design Tools for Teams | Swagger
在项目中使用Swagger
SpringBoot教程(十六) | SpringBoot集成swagger(全网最全)_一缕82年的清风的博客-CSDN博客
- 集成Swagger - 导入依赖 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- <!-- Swagger --> 
 <dependency>
 <groupId>io.springfox</groupId>
 <artifactId>springfox-swagger2</artifactId>
 <version> 2.9.2</version>
 </dependency>
 <!-- SwaggerUI -->
 <dependency>
 <groupId>io.springfox</groupId>
 <artifactId>springfox-swagger-ui</artifactId>
 <version>2.9.2</version>
 </dependency>
- 编写Hello工程 
- 编写SwaggerConfig.java开启Swagger - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14- package com.joker_yue.config; 
 import org.springframework.context.annotation.Configuration;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/12 15:15
 */
 // 开启Swagger2
 public class SwaggerConfig {
 }
- 测试运行 - 建议降级SpringBoot为2.5.6  
 
- 配置Swagger - 你可以配置Swagger页面上的信息:SwaggerConfig.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42- package com.joker_yue.config; 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import springfox.documentation.service.ApiInfo;
 import springfox.documentation.service.Contact;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spring.web.plugins.Docket;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 import java.util.ArrayList;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/12 15:15
 */
 // 开启Swagger2
 public class SwaggerConfig {
 // 配置Swagger Docket的Bean实例
 
 public Docket docket(){
 return new Docket(DocumentationType.SWAGGER_2)
 .apiInfo(apiInfo()); // ApiInfo可以配置swagger-ui.html上的显示信息
 }
 private ApiInfo apiInfo(){
 // 作者信息
 Contact joker = new Contact("Joker", "www.github.com/Joker2Yue", "Joker2Yue@outlook.com");
 return new ApiInfo(
 "Joker自定义的Swagger",
 "Joker_Never_Plays_Jokes.",
 "1.0",
 "www.github.com/Joker2Yue",
 joker,
 "Apache 2.0",
 "http://www.apache.org/licenses/LICENSE-2.0",
 new ArrayList());
 }
 } 
- 配置扫描接口 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 public Docket docket() {
 return new Docket(DocumentationType.SWAGGER_2)
 .apiInfo(apiInfo()) // ApiInfo可以配置swagger-ui.html上的显示信息
 .enable(true) // 是否启用Swagger
 .select() // 扫描接口
 // RequestHandlerSelectors:配置要扫描接口的方式
 // basePackage:配置要扫描的包;
 // any():配置扫描全部的包;
 // none:都不扫描;
 // withClassAnnotation:扫描类上的注解
 // withMethodAnnotation:扫描方法上的注解
 // .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) //只会扫描类上有RestController的类,给它生成接口
 .apis(RequestHandlerSelectors.basePackage("com.joker_yue.controller"))
 // .paths()过滤什么路径
 .paths(PathSelectors.ant("/joker/**"))
 .build()
 ;
 }
- 配置是否启动Swagger - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26- // 配置Swagger Docket的Bean实例 
 public Docket docket(Environment environment) {
 // 获取环境,以动态设置Swagger的开启和关闭
 Profiles profiles = Profiles.of("dev","test");
 // 通过environment.acceptsProfiles()方法判断是否处在自己设定的环境内
 boolean flag = environment.acceptsProfiles(profiles);
 return new Docket(DocumentationType.SWAGGER_2)
 .apiInfo(apiInfo()) // ApiInfo可以配置swagger-ui.html上的显示信息
 .groupName("呜呜呜")
 .enable(flag) // 是否启用Swagger
 .select() // 扫描接口
 // RequestHandlerSelectors:配置要扫描接口的方式
 // basePackage:配置要扫描的包;
 // any():配置扫描全部的包;
 // none:都不扫描;
 // withClassAnnotation:扫描类上的注解
 // withMethodAnnotation:扫描方法上的注解
 // .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) //只会扫描类上有RestController的类,给它生成接口
 .apis(RequestHandlerSelectors.basePackage("com.joker_yue.controller"))
 // .paths()过滤什么路径
 .paths(PathSelectors.ant("/joker/**"))
 .build()
 ;
 }
- 你可以自定义组。在swagger页面中查看效果 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 public Docket docket1(){
 return new Docket(DocumentationType.SWAGGER_2).groupName("A");
 }
 public Docket docket2(){
 return new Docket(DocumentationType.SWAGGER_2).groupName("B");
 }
 public Docket docket3(){
 return new Docket(DocumentationType.SWAGGER_2).groupName("C");
 }  
- 你可以在Controller中定义测试 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31- package com.joker_yue.controller; 
 import com.joker_yue.pojo.User;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import org.springframework.web.bind.annotation.*;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/12 15:11
 */
 public class HelloController {
 
 public String hello() {
 return "hello";
 }
 // 只要我们的接口中,返回值中存在实体类,它就会被扫描到Swagger中
 
 public User user(){
 return new User();
 }
 
 
 public String userhello( String username){
 return "hello"+username;
 }
 } 
 
总结
- 可以通过Swagger给一些比较难理解的属性或者接口增加注释信息
- 接口文档实时更新
- 可以在线测试
任务
异步任务
- 平常我们处理多线程任务,需要自己手动编写。但是Spring给我们提供了注解,我们拿过来就可以开启异步任务 
- 它其实就是多线程封装的 
- 异步执行方法有可能会非常消耗cpu的资源,所以大的项目建议使用Mq异步实现 - 假装有一个业务:AsyncService.java - 在这里我们使用注解**== - @Async==**表明这是一个异步方法- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23- package com.joker_yue.service; 
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/12 21:05
 */
 public class AsyncService {
 // 告诉Spring,这是一个异步的方法。
 public void hello(){
 try {
 Thread.sleep(3000);
 } catch (InterruptedException e) {
 throw new RuntimeException(e);
 }
 System.out.println("数据正在处理...");
 }
 }
- 来一个Controller:AsyncController.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23- package com.joker_yue.controller; 
 import com.joker_yue.service.AsyncService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/12 21:06
 */
 public class AsyncController {
 
 AsyncService asyncService;
 
 public String hello() {
 asyncService.hello(); // 停止3s,转圈
 return "OK";
 }
 }
- 在程序主入口使用注解**== - @EnableAsync==**开启异步- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- package com.joker_yue; 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.scheduling.annotation.EnableAsync;
 //开启异步功能
 public class SpringBootTaskApplication {
 public static void main(String[] args) {
 SpringApplication.run(SpringBootTaskApplication.class, args);
 }
 }
 
- 异步处理失效的处理+整合线程池 - 如果异步注解写当前自己类,有可能aop会失效,无法拦截注解,最终导致异步注解失效,需要经过代理类调用接口;所以需要将异步的代码单独抽取成一个类调用接口。
- 如果没有整合线程池,频繁的创建线程会非常的浪费资源
 - 将方法抽取到一个单独的类 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25- import lombok.extern.slf4j.Slf4j; 
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 /**
 * @ClassName MemberServiceAsync
 * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
 public class MemberServiceAsync {
 
 public String smsAsync() {
 log.info(">02<");
 try {
 log.info(">正在发送短信..<");
 Thread.sleep(3000);
 } catch (Exception e) {
 }
 log.info(">03<");
 return "短信发送完成!";
 }
 }
- 在需要调用此方法的类中代理 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 public class MemberService {
 
 private MemberServiceAsync memberServiceAsync;
 
 
 public String addMember(){
 log.info(">01<");
 memberServiceAsync.sms();
 log.info(">04<");
 return "用户注册成功"
 }
 }
- 配置线程池ThreadPoolConfig.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.task.TaskExecutor;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import java.util.concurrent.ThreadPoolExecutor;
 public class ThreadPoolConfig {
 /**
 * 每秒需要多少个线程处理?
 * tasks/(1/taskcost)
 */
 private int corePoolSize = 3;
 /**
 * 线程池维护线程的最大数量
 * (max(tasks)- queueCapacity)/(1/taskcost)
 */
 private int maxPoolSize = 3;
 /**
 * 缓存队列
 * (coreSizePool/taskcost)*responsetime
 */
 private int queueCapacity = 10;
 /**
 * 允许的空闲时间
 * 默认为60
 */
 private int keepAlive = 100;
 
 public TaskExecutor taskExecutor() {
 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 // 设置核心线程数
 executor.setCorePoolSize(corePoolSize);
 // 设置最大线程数
 executor.setMaxPoolSize(maxPoolSize);
 // 设置队列容量
 executor.setQueueCapacity(queueCapacity);
 // 设置允许的空闲时间(秒)
 //executor.setKeepAliveSeconds(keepAlive);
 // 设置默认线程名称
 executor.setThreadNamePrefix("thread-");
 // 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
 // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 // 等待所有任务结束后再关闭线程池
 executor.setWaitForTasksToCompleteOnShutdown(true);
 return executor;
 }
 }
- 在需要进入线程池的任务中指定线程池的名称 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 /**
 * @ClassName MemberServiceAsync
 * @Author 蚂蚁课堂余胜军 QQ644064779 www.mayikt.com
 * @Version V1.0
 **/
 public class MemberServiceAsync {
 
 public String smsAsync() {
 log.info(">02<");
 try {
 log.info(">正在发送短信..<");
 Thread.sleep(3000);
 } catch (Exception e) {
 }
 log.info(">03<");
 return "短信发送完成!";
 }
 }
 
邮件任务
- 导入依赖 - 1 
 2
 3
 4
 5
 6- <!-- Mail --> 
 <dependency>
 <groupId>org.springframework.boot.</groupId>
 <artifactId>spring-boot-starter-mail</artifactId>
 <version>2.7.14</version>
 </dependency>
- application.properties - 1 
 2
 3
 4
 5
 6- spring.mail.username=joker_yue@qq.com 
 spring.mail.password=别偷看密码吼吼
 spring.mail.host=smtp.qq.com
 spring.mail.port=465
 # 开启加密验证
 spring.mail.properties.mail.smtp.ssl.enable=true
- Test.java - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51- package com.joker_yue; 
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.mail.SimpleMailMessage;
 import org.springframework.mail.javamail.JavaMailSenderImpl;
 import org.springframework.mail.javamail.MimeMessageHelper;
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeMessage;
 class SpringBootTaskApplicationTests {
 
 JavaMailSenderImpl mailSender;
 
 void contextLoads() {
 // 一个简单的邮件
 SimpleMailMessage simpleMessage = new SimpleMailMessage();
 simpleMessage.setSubject("测试邮件");
 simpleMessage.setText("测试邮件内容");
 simpleMessage.setFrom("joker_yue@qq.com");
 simpleMessage.setTo("joker_yue@qq.com");
 mailSender.send(simpleMessage);
 }
 
 void contextLoads2() throws MessagingException {
 // 一个复杂的邮件
 MimeMessage mimeMailMessage = mailSender.createMimeMessage();
 // 组装
 MimeMessageHelper helper = new MimeMessageHelper(mimeMailMessage, true, "UTF-8");
 // 设置主题
 helper.setSubject("测试邮件");
 // 设置文本,后面一个参数为是否解析为html
 helper.setText("<p style='color:red;'>测试邮件内容</p>", true);
 // 附件
 // helper.addAttachment("1.jpg",new File("1.jpg"));
 // 接收者
 helper.setTo("joker_yue@qq.com");
 // 发送人
 helper.setFrom("joker_yue@qq.com");
 mailSender.send(mimeMailMessage);
 }
 }
定时任务
需要用到:
| 1 | TaskScheduler 任务调度 | 
| 1 | // 开启定时功能 | 
代码:
- ScheduleService.java - 使用 - @Scheduling注解- 星期一到星期天之间的任何时间的第0秒就会执行 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20- package com.joker_yue.service; 
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 /**
 * @author Joker
 * @version 1.0
 * @date 2023/8/13 14:40
 */
 public class ScheduledService {
 //在一个指定的时间执行
 //需要cron表达式
 // 分别为秒 分 时 日 月 星期
 // 表示星期一到星期天之间的任何时间的第0秒就会执行
 public void hello(){
 System.out.println("hello被执行");
 }
 }
- 主入口,加上 - @EnableScheduling注解- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17- package com.joker_yue; 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
 // 开启异步功能
 // 开启定时功能
 public class SpringBootTaskApplication {
 public static void main(String[] args) {
 SpringApplication.run(SpringBootTaskApplication.class, args);
 }
 }
Cron表达式:cron表达式语法规则及常见示例_cron 规则_Kuo-Teng的博客-CSDN博客
全局捕获异常
注解说明
@ExceptionHandler 表示拦截异常
- @ControllerAdvice 是 controller 的一个辅助类,最常用的就是作为全局异常处理的切面类
- @ControllerAdvice 可以指定扫描范围
- @ControllerAdvice 约定了几种可行的返回值,如果是直接返回 model 类的话,需要使用 @ResponseBody 进行 json 转换- 返回 String,表示跳到某个 view
- 返回 modelAndView
- 返回 model + @ResponseBody
 
代码
| 1 | 
 | 
结果
 
分布式
什么是分布式系统
”分布式系统(Distribute System)是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统“
 
什么是RPC
 RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,它允许一个计算机程序调用另一个地址空间(通常是不同的机器)的子程序或函数,就像是本地调用一样,而不需要开发人员显式地处理远程通信的细节。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
 也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数
 更多信息:RPC是什么,看完你就知道了 - 知乎 (zhihu.com)
Dubbo
官方文档:Apache Dubbo
 Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
 在云原生时代,Dubbo 相继衍生出了 Dubbo3、Proxyless Mesh 等架构与解决方案,在易用性、超大规模微服务实践、云原生基础设施适配、安全性等几大方向上进行了全面升级。
 首先有一店里要有汉堡包(register),然后消费者点餐(subscribe),最后消费者取餐(invoke)
 
- Zookeeper:注册中心
- Dubbo-admin:监控后台,查看我们注册了哪些服务,哪些服务被消费了
- Dubbo:jar包
