SpringBoot

目录

  • SpringBoot
  • 进入微服务阶段
  • 微服务架构
  • 第一个SpringBoot程序
    • 环境配置
    • 创建基础项目说明
      • 项目创建方式一
      • 项目创建方式二(推荐)
      • 项目结构分析
      • 彩蛋
  • 原理初探
    • pom.xml
    • 启动器
    • 主程序
    • 结论
    • run
    • SpringApplication
    • run方法流程分析
  • SpringBoot配置
    • 配置文件
    • yaml
    • yaml基础语法
    • 注入配置文件
    • yaml注入配置文件
    • 加载指定的配置文件
    • 配置文件占位符
    • 对比小结
    • JSR303检验
    • 多环境切换
      • properties多配置文件
      • yaml的多文档块(现在已经不推荐了)
    • 配置文件加载位置
    • 自动配置原理
      • 分析自动配置原理
      • 精髓
    • 了解:@Conditional
  • SpringBootWeb开发
    • 静态资源
      • 自定义静态资源路径
  • Thymeleaf模板引擎
    • 引入Thymeleaf
    • Thymeleaf分析
    • Thymeleaf 语法学习
  • MVC自动配置原理
    • 官网阅读
    • 转换器和格式化器
    • 修改SpringBoot的默认配置
    • 全面接管SpringMVC
  • 页面国际化
    • 准备工作
    • 配置文件编写
    • 配置文件生效探究
    • 配置页面国际化值
    • 配置国际化解析
  • 聊一聊怎么写一个网页
  • 整合JDBC
    • SpringData简介
    • 创建测试项目测试数据源
    • JDBCTemplate
    • 测试
  • 集成Druid
    • Druid简介
    • 配置数据源
  • 整合Mybatis
    • 整合测试
  • SpringSecurity(安全)
    • 简介
    • 实验环境搭建
    • 权限控制和注销
    • 记住我功能
    • 定制登录页
  • shiro(安全)
    • shiro整合spring
    • shiro整合mybatis
    • shiro整合thymeleaf
  • Swagger
    • SpringBoot集成Swagger
    • 配置Swagger
    • 配置扫描接口
    • 配置Swagger开关
    • 配置API分组
    • 实体配置
    • 常用注解
    • 拓展:其他皮肤
  • 异步任务
  • 定时任务
    • 时间
    • 特殊字符
    • 测试步骤:
  • 邮件任务
  • 分布式 Dubbo+Zooker
    • 什么是分布式系统?
    • Dubbo文档
    • 单一应用架构
    • 垂直应用架构
    • 分布式服务架构
    • 流动计算架构
    • RPC
    • 测试环境搭建
      • Dubbo
      • Dubbo环境搭建
      • Window下安装zookeeper
      • window下安装dubbo-admin(可以不要)
    • SpringBoot + Dubbo + zookeeper
      • 框架搭建
      • 服务提供者(provider-server)
      • 服务消费者(consumer-server)
      • 启动测试
      • 过程总结

SpringBoot

进入微服务阶段

  • javase:OOP

  • MySQL:持久化

  • html+css+js+jquery+框架:视图,框架不熟练,css不好

  • javaweb:独立开发MVC三层架构的网站:原始

  • ssm:框架:简化了我们的开发流程,配置也开始较为复杂;

  • 在此之前项目打包都是war包,程序在Tomcat中运行

  • spring再简化:springBoot-jar包,内嵌Tomcat;微服务架构!

  • 服务越来越多:springCloud

微服务架构

微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须建成一系列小服务组合,可以通过http方式进行通信。

所谓微服务加购,就是打破之前all in one的架构方式,把每个功能元素独立出来,把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些可以整合多个功能元素,所以微服务架构是对功能元素进行赋值,而没有对整个应用进行复制,这样做的好处是:

  • 节省了调用资源
  • 每个功能元素的服务都是一个可替换的,可独立升级的软件代码

程序核心:高内聚(在划分模块时,要把功能关系紧密的放到一个模块中)
低耦合(模块之间的联系越少越好,接口越简单越好)

论文地址:https://martinfowler.com/articles/microservices.html

中文版论文地址:https://www.cnblogs.com/liuning8023/p/4493156.html

SpringBoot_第1张图片

  • 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用
  • 大型分布式网络服务的调用,这部分springcloud来完成,实现分布式
  • 在分布式中间,进行流式数据计算,批处理,我们有spring cloud data flow
  • spring为我们想清楚了整个开始构建应用到大型分布式应用全流程方案

SpringBoot_第2张图片

第一个SpringBoot程序

环境配置

环境准备:

  • jdk1.8
  • maven-3.8.4
  • SpringBoot 最新版

开发工具:

  • IDEA

创建基础项目说明

Spring官方提供了非常方便的工具让我们快速构建应用,IDEA也集成了这个网站

Spring Initializr:https://start.spring.io

项目创建方式一

使用Spring Initializr 的 Web页面创建项目

  1. 打开 https://start.spring.io
  2. 填写项目信息
  3. 点击”Generate“按钮生成项目;下载此项目
  4. 解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
  5. 如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

SpringBoot_第3张图片

项目创建方式二(推荐)

使用 IDEA 直接创建项目

  1. 创建一个新项目
  2. 选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
  3. 填写项目信息
  4. 选择初始化的组件(初学勾选 Web 即可)
  5. 填写项目路径
  6. 等待项目构建成功

SpringBoot_第4张图片


SpringBoot_第5张图片

项目结构分析

通过上面步骤完成了基础项目的创建。就会自动生成以下文件。

1、程序的主启动类(程序的主入口)

2、一个 application.properties 配置文件(SpringBoot的核心配置文件)

3、一个 测试类

4、一个 pom.xml

SpringBoot_第6张图片

pom.xml文件分析:

打开pom.xml,看看Spring Boot项目的依赖:


<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.7.4version>
    <relativePath/> 
parent>

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
dependencies>

<build>
    <plugins>
        
        <plugin>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-maven-pluginartifactId>
            <version>2.7.4version>
        plugin>
    plugins>
build>

编写一个http接口

  1. 在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到
  2. 在包中新建一个HelloController类
@RestController
public class HelloController {
    //接口:http://localhost:8080/hello
    @RequestMapping("/hello")
    public String hello(){
        //调用业务,接收前端的参数!
        return "hello,world";
    }
}
  1. 编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

SpringBoot_第7张图片


SpringBoot_第8张图片

更改端口号:在application资源文件中配置

 server.port = xxxx

彩蛋

图像彩蛋

  • 在resources目录下新建banner.txt
  • 把图像拷贝进去
  • 重启

原理初探

pom.xml

  • spring-boot-dependencies:核心在父工程中
  • 在写或者引入springboot依赖的时候,不需要指定版本,就是因为有这些版本仓库

启动器

1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starterartifactId>
  <version>2.7.4version>
dependency>
  • 启动器:说白了就是springboot的启动场景
  • 比如:spring-boot-starter-web 他就会自动导入web环境所有的依赖
  • springboot会将所有的功能场景,都变成一个个的启动器
  • 我们要使用什么功能,就只需要找到对应的启动器就可以了starter

主程序

//程序的主入口
//@SpringBootApplication 标注这个类是springboot的应用  启动类下的所有资源被导入
@SpringBootApplication
public class Springboot01HelloworldApplication {
    //将springboot应用启动
    public static void main(String[] args) {
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}
  • 注解:@SpringBootApplication

  • 1. @SpringBootConfiguration:springboot的配置

    • 1.1 @Configuration:spring的配置类
      • 1.1.1 @Component:说明这也是spring组件
  • 2.@EnableAutoConfiguration:自动导入配置

    • 2.1 @AutoConfigurationPackage:自动配置包
      • 2.2 @Import(AutoConfigurationPackages.Registrar.class) :自动配置 “ 注册包 ”
    • 2.2 @Import(AutoConfigurationImportSelector.class):自动配置导入选择器

获取所有的配置

AutoConfigurationImportSelector这个类里面
//获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

获取候选的配置

//获取候选的配置
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		//Assert  断言
		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;
	}

META-INF/spring.factories:自动配置的核心文件

SpringBoot_第9张图片


SpringBoot_第10张图片

结论

SpringBoot_第11张图片

SpringBoot所有的自动配置,都在启动类中被扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的starter,就有对应的启动器了,有了启动器,我们的自动装配就会生效,然后就配置成功了

  1. SpringBoot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值
  2. 将这些自动配置的类导入容器,自动配置类就会生效,帮我们进行自动配置
  3. 以前需要自动配置的东西,现在不需要了
  4. 整合javaEE,解决方案和自动配置的东西都在Spring-boot-autoconfigure下
  5. 它会把所有需要导入的组件,以类名的方式返回这些组件,这些组件就会被添加到容器
  6. 容器中也会存在非常多的XXXAutoConfigure的文件(@Bean),就是这个类给容器导入了这个场景所需要的所有组件并自动配置
  7. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

run

最初以为就是运行了一个main方法,没想到却开启了一个服务;

@SpringBootApplication
public class Springboot01HelloworldApplication {
    //该方法返回一个configurableApplicationContext对象
    // 参数一:应用入口的类   参数类:命令行参数
    public static void main(String[] args) {
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }
}

SpringApplication.run分析

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;

SpringApplication

这个类主要做了以下四件事情:

  • 1、推断应用的类型是普通的项目还是Web项目
  • 2、查找并加载所有可用初始化器 , 设置到 initializers 属性中
  • 3、找出所有的应用程序监听器,设置到 listeners 属性中
  • 4、推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

run方法流程分析

SpringBoot_第12张图片

关于SpringBoot,谈谈你的理解:

  • 自动装配:如何加载
  • run方法:如何启动
    • 1、判断是是普通项目还是web项目
      • 普通项目:运行后直接关闭
      • web项目:运行后一直运行
    • 2、推断出并设置main方法的定义类,找到运行的主类
      • 不知道主类没有办法加载
    • 3、方法内设置了一些全局监听器,主要是获取上下文,并处理bean容器
    • 4、查找并加载所有的可用初始化器,设置到setInitializers

全面接管SpringMVC的配置!

SpringBoot配置

配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties
    • 语法结构 :key=value
  • application.yaml 或 application.yml
    • 语法结构 :key:空格 value

**配置文件的作用 **:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

比如可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!

yaml

YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)

  • YAML A Markup Language:是一个标记语言
  • YAML isnot Markup Language:不是一个标记语言

这种语言以数据作为中心,而不是以标记语言为重点!

标记语言

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

yaml配置:

server:
  port: 8080

xml配置:

<server>
  <port>8080port>
server>

yaml基础语法

  • properties只能保存键值对!
# k=v键值对
# 普通的key-value
name: xiaoming
#相当于name=xiaoming

# 存对象
student:
  name: xiaoming
  age: 12
#相当于
#student.name = xiaoming
#student.age = 12
  
# 行内写法
student1: {name: xiaoming,age: 13}

#数组
pets:
  - cat
  - dog
  - pyg

pets1: [cat,dog]

说明:语法要求严格!

1、空格不能省略

2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

3、属性和值的大小写都是十分敏感的。

字面量:普通的值 [ 数字,布尔值,字符串 ]

字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;

k: v

注意:

  • " " 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;

    比如 :name: “xiao \n ming” 输出 :xiao 换行 ming

  • ’ ’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出

    比如 :name: ‘xiao \n ming’ 输出 :xiao \n ming

对象、Map(键值对)

#对象、Map格式
k:
 v1:    
 v2:
 
maps: {k1: v1,k2: v2}

在下一行来写对象的属性和值得关系,注意缩进;比如:

student:
	name: xiaoming
    age: 3

行内写法

student: {name: xiaoming,age: 3}

数组( List、set )

用 - 值表示数组中的一个元素,比如:

pets: 
 - cat 
 - dog 
 - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就可以切换端口;

server:  
 port: 8082

可以注入到我们的配置类当中

注入配置文件

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

SpringBoot_第13张图片


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-configuration-processorartifactId>
    <optional>trueoptional>
dependency>

yaml注入配置文件

1、在springboot项目中的resources目录下新建一个文件 application.yaml

2、编写一个实体类 Dog;

  • 导入lombok
<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
dependency>
//添加到spring组件中
@Component

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
    private String name;
    private Integer age;
}

3、编写一个person类

/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = "person":将配置文件中的person下面的所有属性——对应

只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能
*/

@Component
//绑定yaml的值 prefix = "person"
@ConfigurationProperties(prefix = "person")
@Data
@AllArgsConstructor
@NoArgsConstructor
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;
}

4、 在yaml中写入对象

person:
  name: 小明
  age: 3
  happy: false
  birth: 2022/10/12
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: 旺财
    age: 3

5、 在测试程序中测试

@SpringBootTest
class Springboot02ConfigApplicationTests {

    //自动装配
    @Autowired
    private Person person;

    @Test
    void contextLoads() {
        System.out.println(person);
    }

}

加载指定的配置文件

**@PropertySource **:加载指定的配置文件;

@configurationProperties:默认从全局配置文件中获取值;

1、在resources目录下新建一个person.properties文件

name=xiaoming

2、然后在代码中指定加载person.properties文件

// javaconfig绑定我么配置文件的值,可以采取这些方式!
// 加载指定的配置文件
@PropertySource(value = "classpath:person.properties")

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    //SPEL表达式取出配置文件的值
    @Value("${name}")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

配置文件占位符

配置文件还可以编写占位符生成随机

Person:
  name: xiaoming${random.uuid} # 随机uuid
  age: ${random.int}  # 随机int
  happy: false
  birth: 2022/10/12
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: ${person.hello:other}_旺财 #如果hello没有那么就默认值hello+旺财
    age: 3

对比小结

@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;

功能对比图:

SpringBoot_第14张图片

1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

4、复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

配置yml和配置properties都可以获取到值 , 强烈推荐 yml;

如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;

如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

JSR303检验

spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-validationartifactId>
dependency>
@Component
//绑定yaml的值 prefix = "person"
@ConfigurationProperties(prefix = "person")

@Validated //数据校验

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    //(message = "邮箱格式不对")可写可不写
    @Email(message = "邮箱格式不对")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

在这里插入图片描述

使用数据校验,可以保证数据的正确性;

空检查 
@Null 验证对象是否为null 
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串 
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. 
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查 
@AssertTrue 验证 Boolean 对象是否为 true 
@AssertFalse 验证 Boolean 对象是否为 false

长度检查 
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 
@Length(min=, max=) Validates that the annotated string is between min and max included.

日期检查 
@Past 验证 DateCalendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期 
@Future 验证 DateCalendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期 
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。

数值检查 
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integernull 
@Min 验证 NumberString 对象是否大等于指定的值 
@Max 验证 NumberString 对象是否小等于指定的值 
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度 
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度 
@Digits 验证 NumberString 的构成是否合法 
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。 
@Range(min=, max=) 被指定的元素必须在合适的范围内 
@Range(min=10000,max=50000,message=”range.bean.wage”) 
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证) 
@CreditCardNumber信用卡验证 
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。 
@ScriptAssert(lang= ,script=, alias=) 
@URL(protocol=,host=, port=,regexp=, flags=)]()]()

SpringBoot_第15张图片

多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

properties多配置文件

在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

需要通过一个配置来选择需要激活的环境:

需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

yaml的多文档块(现在已经不推荐了)

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !

server:
  port: 8081

# 选则哪个端口访问
Spring:
  profiles:
    active: dev


---
server:
  port: 8082
Spring:
  profiles: dev #命名

---
server:
  port: 8083
Spring:
  profiles: test #命名

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!

配置文件加载位置

外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

springboot启动会扫描以下位置的application.properties或者application.yml文件作为Springboot的默认配置文件

优先级1:项目路径下的config文件夹配置文件优先级
优先级2:项目路径下配置文件优先级
优先级3:资源路径下的config文件夹配置文件优先级
优先级4:资源路径下配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置;

SpringBoot会从这四个位置全部

加载主配置文件;互补配置;

在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;

#配置项目的访问路径
server.servlet.context-path=/yingxu

官网链接:官方外部配置文件说明参考文档

自动配置原理

配置文件到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,无法全部记住

官方文档

SpringBoot_第16张图片

分析自动配置原理

HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 

//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;

  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;

  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

去配置文件里面试试前缀,看提示!

SpringBoot_第17张图片

这就是自动装配的原理

精髓

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

xxxxAutoConfigurartion:自动配置类;给容器中添加组件

xxxxProperties:封装配置文件中相关属性;

了解:@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

SpringBoot_第18张图片

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

【演示:查看输出的日志】

SpringBootWeb开发

springboot到底帮我们配置了什么?我们能不能修改?能修改哪些东西?能不能扩展?

  • xxxAutoconfiguration:向容器中自动配置组件
  • xxxxProperties:自动配置类,装配配置文件中自定义的一些内容

解决问题:

  • 导入静态资源
  • 首页
  • jsp,模板引擎Thymeleaf
  • 装配扩展SpringMVC
  • 增删改查
  • 拦截器
  • 国际化

静态资源

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}

什么是webjars:WebJars是将web前端资源(js,css等)打成jar包文件,然后借助Maven工具,以jar包形式对web前端资源进行统一依赖管理,保证这些Web资源版本唯一性。WebJars的jar包部署在Maven中央仓库上。

webjars官网

要使用jQuery,只要要引入jQuery对应版本的pom依赖即可!

<dependency>
    <groupId>org.webjars.npmgroupId>
    <artifactId>jqueryartifactId>
    <version>3.6.1version>
dependency>

SpringBoot_第19张图片

  1. 拿到静态资源的第一种方式
http://localhost:8080/webjars/jquery/3.6.1/dist/jquery.js
  1. 使用resources/static或者resources/resources或者resources/public来访问静态资源

在这些目录下创建1.js

http://localhost:8080/1.js

优先级:resources>static(默认)>puiblic

SpringBoot_第20张图片

很少使用webjars

自定义静态资源路径

也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;

spring.resources.static-locations=classpath:/coding/,classpath:/yingxu/

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!

Thymeleaf模板引擎

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。

jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的

那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?

SpringBoot推荐你可以来使用模板引擎:

模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:

SpringBoot_第21张图片

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。

引入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.4/reference/htmlsingle/#using.build-systems.starters

找到对应的pom依赖:可以适当点进源码看下本来的包!


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

Maven会自动下载jar包,我们可以去看下下载的东西;

在这里插入图片描述

Thymeleaf分析

前面呢,已经引入了Thymeleaf,那这个要怎么使用呢?

首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,进行使用。

去找一下Thymeleaf的自动配置类:ThymeleafProperties

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
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

@Controller
public class TestController {
    @GetMapping ("/test")
    public String test(Model model){
        //classpath:/templates/test.html
        return "test";
    }
}

2、编写一个测试页面 test.html 放在 templates 目录下

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>测试页面h1>

body>
html>

3、启动项目请求测试

Thymeleaf 语法学习

要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;

Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!去下载Thymeleaf的官方文档!

做个最简单的练习 :我们需要查出一些数据,在页面中展示

1、修改测试请求,增加数据传输;

@Controller
public class TestController {
    @RequestMapping ("/test")
    public String test(Model model){
        //存入数据
        model.addAttribute("msg","hello,springboot");
        return "test";
    }
}

2、要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。

可以去官方文档的#3中看一下命名空间拿来过来:

xmlns:th="http://www.thymeleaf.org"

3、编写下前端页面

DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>


<div th:text="${msg}">div>
body>
html>

4、启动测试!

SpringBoot_第22张图片

OK,入门搞定,来认真研习一下Thymeleaf的使用语法!

1、可以使用任意的 th:attr 来替换Html中原生属性的值!

SpringBoot_第23张图片

2、能写哪些表达式呢?

#4

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,放一些数据

@Controller
public class IndexController {
    @GetMapping ("/test")
    public String test(Model model){
        //存入数据
        model.addAttribute("msg","

hello,springboot

"
); model.addAttribute("users", Arrays.asList("xiaoming","zhangsan")); return "test"; } }

2、测试页面取出数据

DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>


<div th:text="${msg}">div>
<div th:utext="${msg}">div>
<br/>


<h3 th:each="user:${users}" th:text="${user}">h3>
    


body>
html>

3、启动项目测试!

http://localhost:8080/test

MVC自动配置原理

官网阅读

在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。

只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!

地址 :官网

转换器和格式化器

在Properties文件中,可以进行自动配置它!

如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:

# 自定义的配置日期格式化!
spring.mvc.format.date=dd/MM/yyyy

spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss

其余的就不一一举例了!

修改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;

//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/yingxu , 就会跳转到test页面;
        registry.addViewController("/yingxu").setViewName("test");
    }
}

确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!

可以去分析一下原理:

1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration

这个父类中有这样一段代码:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    
  // 从容器中获取所有的webmvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
}

4、可以在这个类中去寻找一个刚才设置的viewController当做参考,发现它调用了一个

protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
}

5、点进去看一下

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自己的配置类,我们自己的配置类当然也会被调用;

@EnableWebMvc 就是导入了一个 DelegatingWebMvcConfiguration 类,从容其中获取所有的webmvcconfig

在SpringBoot中由非常多的xxxConfiguration,会帮助我们进行扩展,只要看见了这个东西我们就要注意了

全面接管SpringMVC

官方文档:

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个@EnableWebMvc。

我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;

不加注解之前,访问首页:

SpringBoot_第24张图片


给配置类加上注解:@EnableWebMvc

SpringBoot_第25张图片

发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;

当然,在开发中,不推荐使用全面接管SpringMVC

思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:

1、这里发现它是导入了一个类,我们可以继续进去看

@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

2、它继承了一个父类 WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  // ......
}

3、回顾一下Webmvc自动配置类

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
}

总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;

而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!

页面国际化

有的时候,网站会去涉及中英文甚至多语言的切换,这时候就需要学习国际化了!

SpingBoot-Web静态资源 密码:web

准备工作

先在IDEA中统一设置properties的编码问题!

SpringBoot_第26张图片

编写国际化配置文件,抽取页面需要显示的国际化页面消息。我们可以去登录页面查看一下,哪些内容我们需要编写国际化的配置!

配置文件编写

1、我们在resources资源文件下新建一个i18n目录,存放国际化配置文件

2、建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作;文件夹变了

SpringBoot_第27张图片

3、我们可以在这上面去新建一个文件;

SpringBoot_第28张图片

弹出如下页面:我们再添加一个英文的;

SpringBoot_第29张图片

这样就快捷多了!

SpringBoot_第30张图片

4、接下来,我们就来编写配置,我们可以看到idea下面有另外一个视图;

没有Resource Bundle在idea的Plugins中下载一个 Resource Bundle Editor就有了

在这里插入图片描述

这个视图我们点击 + 号就可以直接添加属性了;我们新建一个login.tip,可以看到边上有三个文件框可以输入

SpringBoot_第31张图片

我们添加一下首页的内容!

SpringBoot_第32张图片

然后依次添加其他页面内容即可!

SpringBoot_第33张图片

然后去查看我们的配置文件;

login.properties :默认

login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名

英文:

login.btn=Sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username

中文:

login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名

OK,配置文件步骤搞定!

配置文件生效探究

我们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration

里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;

// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    if (StringUtils.hasText(properties.getBasename())) {
        // 设置国际化文件的基础名(去掉语言国家代码的)
        messageSource.setBasenames(
            StringUtils.commaDelimitedListToStringArray(
                                       StringUtils.trimAllWhitespace(properties.getBasename())));
    }
    if (properties.getEncoding() != null) {
        messageSource.setDefaultEncoding(properties.getEncoding().name());
    }
    messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
    Duration cacheDuration = properties.getCacheDuration();
    if (cacheDuration != null) {
        messageSource.setCacheMillis(cacheDuration.toMillis());
    }
    messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
    messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
    return messageSource;
}

我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;

spring.messages.basename=i18n.login

配置页面国际化值

去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。我们去页面测试下:

IDEA还有提示,非常智能的!

SpringBoot_第34张图片


SpringBoot_第35张图片

但是我们想要更好!可以根据按钮自动切换中文英文!

配置国际化解析

在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!

我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置:

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
    // 容器中没有就自己配,有的话就用用户配置的
    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    }
    // 接收头国际化分解
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    return localeResolver;
}

AcceptHeaderLocaleResolver 这个类中有一个方法

public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = this.getDefaultLocale();
    // 默认的就是根据请求头带来的区域信息获取Locale进行国际化
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    } else {
        Locale requestLocale = request.getLocale();
        List<Locale> supportedLocales = this.getSupportedLocales();
        if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
            Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            } else {
                return defaultLocale != null ? defaultLocale : requestLocale;
            }
        } else {
            return requestLocale;
        }
    }
}

那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的Locale生效!

我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!

修改一下前端页面的跳转连接:


<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>

我们去写一个处理的组件类!

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;

//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {

        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
        //如果请求链接不为空
        if (!StringUtils.isEmpty(language)){
            //分割请求参数
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean;

@Bean
public LocaleResolver localeResolver(){
    return new MyLocaleResolver();
}

我们重启项目,来访问一下,发现点击按钮可以实现成功切换!

# 1.配置首页  : 
 1.1  注意点,所有页面的静态资源都需要使用thymeleaf接管; 
 1.2  @{}

# 2.页面的国际化
#确保在setting中  fileEncoding 都是utf-8
注意点: 2.1 我们需要配置i18n文件
    2.2 我们如果需要在项目中进行按钮自动切换,就需要自定义一个组件
MyLocaleResolver
    2.3 记得将自己写的组件配置到spring容器中  @bean
    2.4 #{}
    
# 3.登录+拦截器

# 4.CRUD 员工列表展示

	4.1.提取公共页面  th:fragment  或者  th:replace 
		4.1.1  th:fragment="sidebar"
		4.1.2  
4.1.3 如果要使用参数,可以使用()传参 4.2 列表循环展示 list页面中 # 5.添加员工 1.按钮提交 2.跳转到添加页面 3.添加员工成功 4.返回首页 日期:默认`dd/MM/yyyy`. 修改 spring.mvc.date-format= # 6.CRUD搞定 # 7. 404

聊一聊怎么写一个网页

  • 前端必须使用推荐模板:别人写好的,我们拿来改成自己需要的
  • 框架:组件:需要自己手动组合拼接 layui,elementui
    • 栅格系统
    • 导航栏
    • 侧边栏
  1. 前端搞定:页面长什么样子:数据

  2. 设计数据库

  3. 前端让他能够自动运行,独立化工程

  4. 数据接口如何对接,json,对象all in one

  5. 前后端联调测试

有一套自己熟悉的后台模板:工作必要! 后台框架:xadmin

前端界面:至少自己能够通过前端框架,组合出来一个网站页面

让这个网站能够独立运行!

整合JDBC

SpringData简介

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

Sping Data 官网:Sping Data 官网

数据库相关的启动器 :可以参考官方文档:

官方文档

创建测试项目测试数据源

1、新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块

SpringBoot_第36张图片

2、项目建好之后,发现自动帮我们导入了如下的启动器:


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jdbcartifactId>
dependency>

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <scope>runtimescope>
dependency>

3、编写yaml配置文件连接数据库;

spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下

@SpringBootTest
class Springboot04DataApplicationTests {

    //DI注入数据源
    @Autowired
    @Resource
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        //查看一下默认的数据源  com.zaxxer.hikari.HikariDataSource
        System.out.println(dataSource.getClass());

        //获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        //关闭
        connection.close();
    }

}

结果:可以看到默认配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置

全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件:

@Import(
    {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}

这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;

可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。

关于数据源我们并不做介绍,有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate

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方法:用于执行存储过程、函数相关语句。

测试

编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;

@RestController
public class JDBCController {
    @Autowired
    @Resource
    JdbcTemplate jdbcTemplate;

    //查询数据库的所有信息
    //没有实体类,数据库中的东西,怎么获取? Map
    @GetMapping("/userList")
    public List<Map<String,Object>> userList(){
        String sql="select * from user";
        List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
        return list_maps;
    }

    //新增一个用户
    @GetMapping("/addUser")
    public String addUser(){
        //插入语句,注意时间问题
        String sql = "insert into mybatis.user(id,name,pwd) values (6,'李四','123')";
        jdbcTemplate.update(sql);
        //查询
        return "addUser-Ok";
    }

    //修改用户信息
    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入语句
        String sql = "update mybatis.user set name=?,pwd=? where id="+id;
        //数据
        Object[] objects = new Object[2];
        objects[0] = "李四2";
        objects[1] = "111";
        jdbcTemplate.update(sql,objects);
        //查询
        return "updateUser-Ok";
    }

    //删除用户
    @GetMapping("/deleteUser/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入语句
        String sql = "delete from mybatis.user where id=?";
        jdbcTemplate.update(sql,id);
        //查询
        return "deleteUser-Ok";
    }
}

测试请求

http://localhost:8080/userList
http://localhost:8080/addUser
http://localhost:8080/updateUser/6
http://localhost:8080/deleteUser/6

结果正常;

到此,CURD的基本操作,使用 JDBC 就搞定了。

集成Druid

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 基本配置参数如下:

配置 缺省值 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。
如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this).
另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错
url 连接数据库的url,不同数据库不一样。例如:
mysql : jdbc:mysql://10.20.153.104:3306/druid2
oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:详细
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
validationQueryTimeout 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrow false 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 1分钟(1.0.14) 有两个含义:
1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接
2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis 30分钟(1.0.14) 连接保持空闲而不被驱逐的最长时间
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

配置数据源

1、添加上 Druid 数据源依赖。

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>1.2.13version>
dependency>

2、去配置Druid,配置自定义的数据源

spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 自定义数据源
    type: com.alibaba.druid.pool.DruidDataSource

3、接下来就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码

spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 自定义数据源
    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

    # 底层开启功能,stat(sql监控),wall(防火墙)
    filters: stat,wall,log4j2
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

4、因为Druid中包含log4j所以需要导入依赖

  • 要用log4j2先去掉spring自带的日志依赖spring-boot-starter-logging

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    
    
    <exclusions>
        <exclusion>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-loggingartifactId>
        exclusion>
    exclusions>

dependency>
  • 再配置log4j2


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-log4j2artifactId>
dependency>

5、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性

  • config文件夹下
@Configuration
public class DruidConfig {
    @Bean
    //绑定到application.yaml文件中生效
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDateSource(){
        return new DruidDataSource();
    }
}

6、Druid具有看监控功能,可以方便用户在web端看见后台的操作,配置后台监控和过滤器

  • druid与username平级
#配置监控统计拦截的filters,stat:监控统计、log4j2:日志记录、wall:防御sql注入
druid:
  # 配置后台监控
  stat-view-servlet: # 配置监控页功能
    enabled: true
    login-username: admin
    login-password: 123456
    resetEnable: false
    #allow: 192.168.188.165 #ip白名单,以逗号分割传递多个值;相对应的deny为ip黑名单,不过貌似没啥用

  # filter后台过滤
  web-stat-filter: # 监控web
    enabled: true
    urlPattern: /*
    exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


  filter:
    stat: # 对上面filters里面的stat的详细配置
      slow-sql-millis: 1000
      logSlowSql: true
      enabled: true
    wall:
      enabled: true
      config:
        drop-table-allow: false

完整:

spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 自定义数据源
    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

    # 底层开启功能,stat(sql监控),wall(防火墙)
    filters: stat,wall,log4j2
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500


    #配置监控统计拦截的filters,stat:监控统计、log4j2:日志记录、wall:防御sql注入
    druid:
      # 配置后台监控
      stat-view-servlet: # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: 123456
        resetEnable: false
        #allow: 192.168.188.165 #ip白名单,以逗号分割传递多个值;相对应的deny为ip黑名单,不过貌似没啥用

      # filter后台过滤
      web-stat-filter: # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


      filter:
        stat: # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false
  • 在项目的yaml文件中,添加filters配置监控统计拦截(注意位置,刚开始我把它的位置写在了最后的位置,但是貌似并没有加载进去。放在前面就莫名奇妙的好了,很玄学!!!)

SpringBoot_第37张图片

  • 注意filters的配置位置!

然后在浏览器输入你的项目地址加端口号+/druid就可以查看了例如:http://localhost:8080/druid/

整合Mybatis

官方文档:官方文档

Maven仓库地址:mybatis-spring-boot-starter

整合测试

1、导入 MyBatis 所需要的依赖


<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.2.2version>
dependency>

2、配置数据库连接信息

  • application.properties
  • 数据库表再mybatis那篇文章中
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

3、导入lombok


<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
dependency>

4、Maven资源过滤设置


<build>
    <resources>
        <resource>
            <directory>src/main/resourcesdirectory>
            <includes>
                <include>**/*.propertiesinclude>
                <include>**/*.xmlinclude>
            includes>
            <filtering>falsefiltering>
        resource>
        <resource>
            <directory>src/main/javadirectory>
            <includes>
                <include>**/*.propertiesinclude>
                <include>**/*.xmlinclude>
            includes>
            <filtering>falsefiltering>
        resource>
    resources>
build>

5、在pojo文件夹下编辑User实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

6、在mapper文件夹下编辑UserMapper

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】
  • 这三个注解就代表这三层
//这个实体类既需要具有增删改查的功能又需要注册到spring中托管所有需要Repository和mapper
@Mapper
//这个注解代表了这是一个mybatis的mapper接口
@Repository //Dao层
public interface UserMapper {
    //查询所有
    List<User> queryUserList();
    //通过id查询
    User queryUserById(int id);
    //增加
    int addUser(User user);
    //修改
    int updateUser(User user);
    //删除
    int deleteUser(int id);
}

7、在resources资源目录下创建mybatis文件夹

  • 在mybatis文件夹下创建mapper文件夹
  • 在mapper文件夹下创建UserMapper.xml

在这里插入图片描述


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yingxu.mapper.UserMapper">
    
    <select id="queryUserList" resultType="user">
        select * from mybatis.user
    select>
    
    <select id="queryUserById" resultType="user">
        select * from mybatis.user where id=#{id}
    select>
    
    <insert id="addUser" parameterType="user">
        insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd})
    insert>
    
    <update id="updateUser" parameterType="user">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
    update>
    
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id}
    delete>
mapper>

8、在application.properties配置文件中配置

# 整合Mybatis
# 让spring识别到mapper文件
mybatis.type-aliases-package=com.yingxu.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

完整:

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 整合Mybatis
# 让spring识别到mapper文件
mybatis.type-aliases-package=com.yingxu.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

9、Service层(没有业务可以不需要,直接跳到步骤11

  • 在service文件夹下创建UserService
public interface UserService {
    //查询所有
    List<User> queryUserList();
    //通过id查询
    User queryUserById(int id);
    //增加
    int addUser(User user);
    //修改
    int updateUser(User user);
    //删除
    int deleteUser(int id);
}

10、Service层实现类

  • 在service文件夹下创建UserServiceImpl
@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    //查询所有
    @Override
    public List<User> queryUserList() {
        return userMapper.queryUserList();
    }
    //通过id查询
    @Override
    public User queryUserById(int id) {
        return userMapper.queryUserById(id);
    }
    //增加
    @Override
    public int addUser(User user) {
        return userMapper.addUser(user);
    }
    //修改
    @Override
    public int updateUser(User user) {
        return userMapper.updateUser(user);
    }
    //删除
    @Override
    public int deleteUser(int id) {
        return userMapper.deleteUser(id);
    }
}

11、编写controller

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    //查询所有
    @RequestMapping("/userList")
    public List<User> queryUserList(){
        List<User> userList = userService.queryUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        return userList;
    }
    //通过id查询
    @RequestMapping("/queryUser/{id}")
    public User queryUserById(@PathVariable("id")int id){
        User user = userService.queryUserById(id);
        return user;
    }
    //增加
    @RequestMapping("/addUser")
    public String addUser(){
        int add = userService.addUser(new User(7, "七七", "123"));
        if (add>0){
            return "addUser-ok";
        }
        return "addUser-no";
    }
    //修改
    @RequestMapping("/updateUser")
    public String updateUser(){
        int update = userService.updateUser(new User(7, "七七2", "123456"));
        if (update>0){
            return "updateUser-ok";
        }
        return "updateUser-no";
    }
    //删除
    @RequestMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id")int id){
        int delete = userService.deleteUser(id);
        if (delete>0){
            return "deleteUser-ok";
        }
        return "deleteUser-no";
    }
}

12、测试

http://localhost:8080/userList
http://localhost:8080/queryUser/1
http://localhost:8080/addUser
http://localhost:8080/updateUser
http://localhost:8080/deleteUser/7

SpringSecurity(安全)

官方文档

shiro、SpringSecurity:很像,除了类不一样

简介

  • SpringSecurity是Springboot底层安全模块默认的技术选型,它可以实现强大的Web安全机制,只需要少数的spring-boot----spring-security依赖,进行少量的配置,就可以实现

  • SpringBoot中的SpringSecurity依赖:

  • 功能权限、访问权限、菜单权限…,我们使用过滤器,拦截器需要写大量的原生代码,这样很不方便

  • 所以在网址设计之初,就应该考虑到权限验证的安全问题,其中Shiro、SpringSecurity使用很多

实验环境搭建

1、创建项目

SpringBoot_第38张图片

创建完后pom.xml中有以下两个依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

2、将静态项目导入

spring-security静态项目 密码:web

  • 结构如下

SpringBoot_第39张图片

3、application.properties中设置

spring.thymeleaf.cache=false

4、配置Controller层

  • controller文件夹下创建startController
@Controller
public class startController {
    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }
    @RequestMapping("/toLogin")
    public String login(){
        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;
    }
}

5、config文件夹下创建SecurityConfig

  • 如果要使用JDBC就想看下面文档

  • [JDBC数据库编写文档](JDBC Authentication :: Spring Security)

  • @EnableWebSecurity:开启WebSecurity模式

  • 两个单词:en是认证,or是权限

    • 认证方式:Authentication
    • 权限:Authorization
//认证和授权
@EnableWebSecurity
public class SecurityConfig{
    //认证
    @Bean
    public UserDetailsService users(){
        //密码加密
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        //设置用户
        User.UserBuilder users = User.builder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        manager.createUser(users.username("yingxu").password(encoder.encode("123456")).roles("vip1").build());
        manager.createUser(users.username("admin").password(encoder.encode("123456")).roles("vip1","vip2").build());
        manager.createUser(users.username("root").password(encoder.encode("123456")).roles("vip1","vip2","vip3").build());
        return manager;
    }

    //授权
    @Bean
    public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能也特定人访问
        //请求授权的规则
        http
                .authorizeHttpRequests(authorize -> authorize
                        // 请求放开
                        .antMatchers("/**").permitAll()
                        .antMatchers("/level1/**").hasRole("vip1")
                        .antMatchers("/level2/**").hasRole("vip2")
                        .antMatchers("/level3/**").hasRole("vip3")
                        // 其他地址的访问均需验证权限
                        .anyRequest().authenticated()
                )
                //没有权限默认为登陆页
                .formLogin(withDefaults());
        return http.build();
    }
}

权限控制和注销

旧版本与新版本对比

1、开启自动配置的注销的功能

  • .logout().logoutSuccessUrl("/");
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
    //首页所有人可以访问,功能也特定人访问
    //请求授权的规则
    http
            .authorizeHttpRequests(authorize -> authorize
                    // 请求放开
                    .antMatchers("/**").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3")
                    // 其他地址的访问均需验证权限
                    .anyRequest().authenticated()
            )
            //没有权限默认为登陆页
            .formLogin(withDefaults())
            //注销,开启注销功能,跳转到首页
            .logout().logoutSuccessUrl("/");
    return http.build();
}

2、查看pom依赖


<dependency>
    <groupId>org.thymeleaf.extrasgroupId>
    <artifactId>thymeleaf-extras-springsecurity5artifactId>
dependency>

3、html页面引用

  • 引用后有提示
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

4、修改登录注销

<div sec:authorize="!isAuthenticated()"> 未登录显示
<div sec:authorize="isAuthenticated()"> 以登陆显示
  • index.html

<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="principal.username">span>
            角色:<span sec:authentication="principal.authorities">span>
        a>
    div>
    <div sec:authorize="isAuthenticated()">
        
        <a class="item" th:href="@{/logout}">
            <i class="sign-out icon">i> 注销
        a>
    div>
div>
  • 位置如图

SpringBoot_第40张图片

5、根据不同的权限显示不同的模块

  • index.html

<div class="column" sec:authorize="hasRole('vip1')">
    <div class="ui raised segment">
        有vip1权限时显示..........
    div>
div>
<div class="column" sec:authorize="hasRole('vip2')">
    <div class="ui raised segment">
        有vip2权限时显示..........
    div>
div>
<div class="column" sec:authorize="hasRole('vip3')">
    <div class="ui raised segment">
        有vip3权限时显示..........
    div>
div>

6、关闭csrf功能(登录不了的添加,一般不添加)

//防止网站工具
.csrf().disable()//关闭csrf功能,登录失败肯存在的原因~
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
    //首页所有人可以访问,功能也特定人访问
    //请求授权的规则
    http
            .authorizeHttpRequests(authorize -> authorize
                    // 请求放开
                    .antMatchers("/**").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3")
                    // 其他地址的访问均需验证权限
                    .anyRequest().authenticated()
            )
            //没有权限默认为登陆页
            .formLogin(withDefaults())

            //防止网站工具
            .csrf().disable()//关闭csrf功能,登录失败肯存在的原因~

            //注销,开启注销功能,跳转到首页
            .logout().logoutSuccessUrl("/");
    return http.build();
}

记住我功能

1、开启记住我功能

//开启记住我功能  cookie
http.rememberMe();
//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
    //首页所有人可以访问,功能也特定人访问
    //请求授权的规则
    http
            .authorizeHttpRequests(authorize -> authorize
                    // 请求放开
                    .antMatchers("/**").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3")
                    // 其他地址的访问均需验证权限
                    .anyRequest().authenticated()
            )
            //没有权限默认为登陆页
            .formLogin(withDefaults())
            //注销,开启注销功能,跳转到首页
            .logout().logoutSuccessUrl("/");
    //开启记住我功能  cookie
    http.rememberMe();
    return http.build();
}

定制登录页

登录文档

1、修改login.html页面

<form th:action="@{/toLogin}" method="post">
<div class="field">
    <input type="checkbox" name="remember"/>记住我
div>
  • 前端也需要指向自己定义的 login请求

SpringBoot_第41张图片

2、定制登录页

//定制登录页
.formLogin(form -> form
        .loginPage("/toLogin")
        .usernameParameter("username")
        .passwordParameter("password")
        .permitAll()
)
  • 三个要对应

SpringBoot_第42张图片

  • 当不对应时(需要使用到loginProcessingUrl()

SpringBoot_第43张图片

3、自定义记住我

//开启记住我功能  cookie
//rememberMeParameter()自定义接收前端的参数
http.rememberMe().rememberMeParameter("remember");

4、需要关闭csrf功能(不然注销报错)

.csrf().disable()//关闭csrf功能

完整:

//授权
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
    //首页所有人可以访问,功能也特定人访问
    //请求授权的规则
    http
            .authorizeHttpRequests(authorize -> authorize
                    // 请求放开
                    .antMatchers("/**").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3")
                    // 其他地址的访问均需验证权限
                    .anyRequest().authenticated()
            )
            //没有权限默认为登陆页
            //.formLogin(withDefaults())
            //定制登录页
            .formLogin(form -> form
                    .loginPage("/toLogin")
                    .loginProcessingUrl("/login")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .permitAll()
            )

            //防止网站工具
            .csrf().disable()//关闭csrf功能,登录失败肯存在的原因~

            //注销,开启注销功能,跳转到首页
            .logout().logoutSuccessUrl("/index");
    //开启记住我功能  cookie
    //rememberMeParameter()自定义接收前端的参数
    http.rememberMe().rememberMeParameter("remember");
    return http.build();
}

shiro(安全)

shiro官网:shiro官网

核心三大对象:用户Subject, 管理用户SecurityManager, 连接数据Realms

  • Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。

  • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

  • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

shiro整合spring

整合shiro:

  • Subject:用户
  • SecurityManager:管理所有用户
  • Realm:连接数据

1、 创建项目

SpringBoot_第44张图片

2、导入依赖

  • 使用shiro-spring-boot-web-starter也可以

<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-springartifactId>
    <version>1.8.0version>
dependency>

3、编写静态页面

  • 在resources/templates目录下
    • 创建user文件夹
      • add.html页面
      • update.html页面
    • index.html页面
    • login.html页面

index.html页面:

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr/>
<a th:href="@{/user/add}">adda> | <a th:href="@{/user/update}">updatea>
body>
html>

add.html页面:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>addh1>
body>
html>

update.html页面:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>updateh1>
body>
html>

login.html页面:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>登陆h1>
<hr>
<p th:text="${msg}" style="color: red">p>
<form th:action="@{/login}">
    <p>用户名:<input type="text" name ="username">p>
    <p>密码:<input type="text" name ="password">p>
    <p><input type="submit">p>
body>
html>

4、编写控制层

  • 在controller文件夹下创建MyController类
@Controller
public class MyController {
    @RequestMapping({"/","/index"})
    public String index(Model model){
        model.addAttribute("msg","hello,shiro");
        return "index";
    }
    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }
    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }
    @RequestMapping("/login")
    public String login(String username, String password, Model model){
        //获得当前的用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户的登陆数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);//执行登陆方法
            //但会登陆成功的界面
            return "index";
        } catch (UnknownAccountException uae) {
            model.addAttribute("msg","用户名异常");
            return "login";
        } catch (IncorrectCredentialsException ice) {
            model.addAttribute("msg","密码不存在");
            return "login";
        }
    }
}

5、编写配置类

  • 在config文件夹下创建UserRealm类
//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了==>授权");
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了==>认证");

        //用户名和 密码
        String name="root";
        String password="123456";

        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        if (!userToken.getUsername().equals(name)){
            return null;//抛出异常
        }

        //密码认证,shiro做~
        return new SimpleAuthenticationInfo("",password,"");
    }
}
  • 在config文件夹下创建ShiroConfig类
@Configuration
public class ShiroConfig {
    //ShiroFilterFactoryBean:第3步
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //关联DefaultWebSecurityManager
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
         /*
            anon:无需认证就可以访问
            authc:必须认证了才能访问
            user: 必须拥有记住我才能访问
            perms:拥有对某个资源的权限
            role:拥有某个角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();

        //登录认证
        /*
        filterMap.put("/user/add","anon");
        filterMap.put("/user/update","authc");
         */
        //设置权限 *:通配符
        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);
        //登陆拦截,设置登录请求
        bean.setLoginUrl("/toLogin");

        return bean;
    }
    //DefaultWebSecurityManager:第2步
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    //创建realm 对象,需要自定义类:第1步
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

6、运行

shiro整合mybatis

1、导入依赖


<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>1.2.13version>
dependency>


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>

    
    <exclusions>
        <exclusion>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-loggingartifactId>
        exclusion>
    exclusions>

dependency>


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-log4j2artifactId>
dependency>
 
<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.2.2version>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
dependency>


<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-springartifactId>
    <version>1.9.0version>
dependency>

2、创建application.yaml配置文件

spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就ok了 serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 自定义数据源
    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

    # 底层开启功能,stat(sql监控),wall(防火墙)
    filters: stat,wall,log4j2
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

3、创建pojo层

  • 在pojo文件夹下创建User类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

4、在mapper文件夹下编辑UserMapper

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】
  • 这三个注解就代表这三层

mapper–>UserMapper:

@Mapper
@Repository //Dao层
public interface UserMapper {
    User queryUserByName(String name);
}

5、在resources资源目录下创建mapper文件夹

  • 在mapper文件夹下创建UserMapper.xml

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yingxu.mapper.UserMapper">
    
    <select id="queryUserByName" resultType="user">
        select * from mybatis.user where name=#{name}
    select>
mapper>

6、在application.properties配置文件中配置

# 让spring识别到mapper文件
mybatis.type-aliases-package=com.yingxu.pojo
mybatis.mapper-locations=classpath:mapper/*.xml

7、Service层

  • 在service文件夹下创建UserService
public interface UserService {
    User queryUserByName(String name);
}

8、Service层实现类

  • 在service文件夹下创建UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

9、修改认证

  • config文件夹下–>UserRealm类
//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了==>授权");
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了==>认证");

        //用户名和 密码
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        //数据库取值
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){
            return null;//抛出异常
        }

        //密码认证,shiro做~
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}

10、在数据库增加perms字段

  • 添加user:add,user:update两个值

在这里插入图片描述


SpringBoot_第45张图片


SpringBoot_第46张图片

11、ShiroConfig类里授权

  • 在ShiroFilterFactoryBean:第3步中添加
//授权,没有授权的话会跳转到未授权页面
//必须带有user:add才有权限
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");

12、MyController类里添加

  • 添加未授权页面
//未授权页面
@RequestMapping("/unauthorized")
@ResponseBody
public String unauthorized(){
    return "未经授权无法访问此页面";
}

13、ShiroConfig类里添加

  • 在ShiroFilterFactoryBean:第3步中添加
//未授权页面
bean.setUnauthorizedUrl("/unauthorized");

11-13步骤中ShiroConfig类里的ShiroFilterFactoryBean:第3步完整版:

//ShiroFilterFactoryBean:第3步
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    //关联DefaultWebSecurityManager
    //设置安全管理器
    bean.setSecurityManager(defaultWebSecurityManager);

    //添加shiro的内置过滤器
     /*
        anon:无需认证就可以访问
        authc:必须认证了才能访问
        user: 必须拥有记住我才能访问
        perms:拥有对某个资源的权限
        role:拥有某个角色权限才能访问
     */
    Map<String, String> filterMap = new LinkedHashMap<>();

    //授权,没有授权的话会跳转到未授权页面
    //必须带有user:add才有权限
    filterMap.put("/user/add","perms[user:add]");
    filterMap.put("/user/update","perms[user:update]");

    //登录认证
    //设置权限 *:通配符
    filterMap.put("/user/*","authc");
    bean.setFilterChainDefinitionMap(filterMap);
    //登陆拦截,设置登录请求
    bean.setLoginUrl("/toLogin");
    //未授权页面
    bean.setUnauthorizedUrl("/unauthorized");

    return bean;
}

14、在User类中添加

  • 添加private String perms;字段
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;
}

15、在UserRealm类中授权

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了==>授权");

    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //info.addStringPermission("user:add");

    //拿到当前登录的对象
    Subject subject = SecurityUtils.getSubject();
    User currentUser = (User)subject.getPrincipal();//拿到User对象
    //设置当前用户的权限
    info.addStringPermission(currentUser.getPerms());

    return info;
}

16、修改UserRealm类中认证

//密码认证,shiro做~
return new SimpleAuthenticationInfo(user,user.getPwd(),"");

15-16步骤中UserRealm类完整版:

//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了==>授权");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //info.addStringPermission("user:add");

        //拿到当前登录的对象
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User)subject.getPrincipal();//拿到User对象
        //设置当前用户的权限
        info.addStringPermission(currentUser.getPerms());

        return info;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了==>认证");

        //用户名和 密码
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        //数据库取值
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){
            return null;//抛出异常
        }

        //密码认证,shiro做~
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

17、运行测试

http://localhost:8080/login?username=张三&password=123  有add的权限  
http://localhost:8080/login?username=root&password=123456 有update的权限 
http://localhost:8080/login?username=李四&password=123  都没有权限 

shiro整合thymeleaf

1、导入依赖


<dependency>
    <groupId>com.github.theborakompanionigroupId>
    <artifactId>thymeleaf-extras-shiroartifactId>
    <version>2.1.0version>
dependency>

2、在ShiroConfig类最后面添加

@Bean
//整合ShiroDialect 用来整合shiro和thymeleaf
public ShiroDialect getShiroDialect(){
    return new ShiroDialect();
}

3、修改index.html页面

方法一:

  1. 修改index.html页面
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr/>


<div shiro:guest="true">
    <a th:href="@{/toLogin}">登录a>div>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">adda>div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">updatea>
div>
body>
html>

方法二:

  1. 在UserRealm类中认证
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);

完整:

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了==>认证");

    //用户名和 密码
    UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
    //数据库取值
    User user = userService.queryUserByName(userToken.getUsername());
    if (user==null){
        return null;//抛出异常
    }
    Subject currentSubject = SecurityUtils.getSubject();
    Session session = currentSubject.getSession();
    session.setAttribute("loginUser",user);

    //密码认证,shiro做~
    return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
  1. 修改index.html页面
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr/>

<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登录a>div>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">adda>div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">updatea>
div>

body>
html>

Swagger

前后端分离

  • 前端 -> 前端控制层、视图层
  • 后端 -> 后端控制层、服务层、数据访问层
  • 前后端通过API进行交互
  • 前后端相对独立且松耦合
  • 前后端可以部署在不同的服务器上

产生的问题

  • 前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发

解决方案

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险

Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP等)
  • 官网:Swagger官网

SpringBoot集成Swagger

SpringBoot集成Swagger => springfox,两个jar包

  • Springfox-swagger2
  • swagger-springmvc

使用Swagger

要求:jdk 1.8 + 否则swagger2无法运行

步骤:

1、新建一个SpringBoot-web项目

2、添加Maven依赖

  • 方法一:(3.0.0版本之前用)
<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger2artifactId>
    <version>2.9.2version>
dependency>
<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger-uiartifactId>
    <version>2.9.2version>
dependency>
  • 方法二:(3.0.0版本之后用)
<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-boot-starterartifactId>
    <version>3.0.0version>
dependency>

3、编写HelloController,测试确保运行成功!

  • 如果报错可以在application.properties添加spring.mvc.pathmatch.matching-strategy=ant_path_matcher

4、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger

  • 方法一:(3.0.0版本之前用)
@Configuration //配置类
@EnableSwagger2 // 开启Swagger2的自动配置
public class SwaggerConfig {
}
  • 方法二:(3.0.0版本之后用)
@Configuration //配置类
@EnableOpenApi // 开启Swagger3的自动配置
public class SwaggerConfig {
}

5、访问测试

  • 方法一(3.0.0版本之前用)
    • 访问测试 :http://localhost:8080/swagger-ui.html,可以看到swagger的界面;

SpringBoot_第47张图片

  • 方法二(3.0.0版本之后用)
    • 访问测试 :http://localhost:8080/swagger-ui/index.html ,可以看到swagger的界面;

SpringBoot_第48张图片

配置Swagger

1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。

//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
    return new Docket(DocumentationType.OAS_30);
}

在这里插入图片描述

  • 3.0用OAS_30

2、可以通过apiInfo()属性配置文档信息

//配置swagger信息=apiInfo
private ApiInfo apiInfo(){
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("潆勖的SwaggerAPI文档") // 标题
            .description("坚持")// 描述
            .version("v1.0")// 版本
            .build();
    return apiInfo;
}

3、Docket 实例关联上 apiInfo()

//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
    return new Docket(DocumentationType.OAS_30)
        .apiInfo(apiInfo());//重新配置了默认的文档信息
}

4、重启项目,访问测试 http://localhost:8080/swagger-ui/index.html 看下效果;

配置扫描接口

1、构建Docket时通过select()方法配置怎么扫描接口。

//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
    return new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
            //RequestHandlerSelectors 要扫描接口的方式
            //指定要扫描的包 basePackage()
            //any() 扫描全部
            //withClassAnnotation() 扫描类上的注解,需要注解的反射对象
            //withMethodAnnotation() 扫描方法上注解
            //只会扫描有RestController注解的类.生成一个接口
            .apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
            .build();//工厂模式
}

2、重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类

3、除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:

any() // 扫描所有,项目中的所有接口都会被扫描到
none() // 不扫描接口
// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口

4、除此之外,我们还可以配置接口扫描过滤:

//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
    return new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
            .apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
            // 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
            .paths(PathSelectors.ant("/yingxu/**"))//过滤路径
            .build();//工厂模式
}

5、这里的可选值还有

any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 通过正则表达式控制
ant(final String antPattern) // 通过ant()控制

配置Swagger开关

1、通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了

//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(){
    return new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问
            .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
            .apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
            // 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
            //.paths(PathSelectors.ant("/yingxu/**"))//过滤路径
            .build();//工厂模式
}

2、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?

//配置了Swagger 的Docket 的bean实例
@Bean
public Docket docket(Environment environment){
    // 设置要显示swagger的环境
    Profiles of = Profiles.of("dev", "test");
    // 判断当前是否处于该环境
    // 通过 enable() 接收此参数判断是否要显示
    boolean flag = environment.acceptsProfiles(of);

    return new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .enable(flag) //配置是否启用Swagger,如果是false,在浏览器将无法访问
            .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
            .apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
            // 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
            //.paths(PathSelectors.ant("/yingxu/**"))//过滤路径
            .build();//工厂模式
}

3、添加开发环境和发布环境

  • 转换环境:application.properties
spring.profiles.active=dev
  • 开发环境:application-dev.properties
server.port=8081
  • 发布环境:application-pro.properties
server.port=8082

4、可以在项目中增加一个dev的配置文件查看效果!

在这里插入图片描述

配置API分组

1、如果没有配置分组,默认是default。通过groupName()方法即可配置分组:

@Bean
public Docket docket(Environment environment){
    // 设置要显示swagger的环境
    Profiles of = Profiles.of("dev", "test");
    // 判断当前是否处于该环境
    // 通过 enable() 接收此参数判断是否要显示
    boolean flag = environment.acceptsProfiles(of);

    return new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .groupName("潆勖")//分组
            .enable(flag) //配置是否启用Swagger,如果是false,在浏览器将无法访问
            .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
            .apis(RequestHandlerSelectors.basePackage("com.yingxu.controller"))
            // 配置如何通过path过滤,即这里只扫描请求以/yingxu开头的接口
            //.paths(PathSelectors.ant("/yingxu/**"))//过滤路径
            .build();//工厂模式
}

2、重启项目查看分组

SpringBoot_第49张图片

3、如何配置多个分组?配置多个分组只需要配置多个docket即可:

@Bean
public Docket docket1(){
    return new Docket(DocumentationType.OAS_30).groupName("group1");
}
@Bean
public Docket docket2(){
    return new Docket(DocumentationType.OAS_30).groupName("group2");
}
@Bean
public Docket docket3(){
    return new Docket(DocumentationType.OAS_30).groupName("group3");
}

4、重启项目查看即可

SpringBoot_第50张图片

实体配置

1、新建一个实体类

  • private的要加上get和set方法
@ApiModel("用户实体")
public class User {
    @ApiModelProperty("用户名")
    public String username;
    @ApiModelProperty("密码")
    public String password;
}

2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:

  • MyController:
//只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
@RequestMapping("/getUser")
public User getUser(){
    return new User();
}

3、重启查看测试

SpringBoot_第51张图片

注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。

@ApiModel为类添加注释

@ApiModelProperty为类属性添加注释

常用注解

Swagger的所有注解定义在io.swagger.annotations包下

下面列一些经常用到的,未列举出来的可以另行查阅说明:

Swagger注解 简单说明
@Api(tags = “xxx模块说明”) 作用在模块类上
@ApiOperation(“xxx接口说明”) 作用在接口方法上
@ApiModel(“xxxPOJO说明”) 作用在模型类上:如VO、BO
@ApiModelProperty(value = “xxx属性说明”,hidden = true) 作用在类方法和属性上,hidden设置为true可以隐藏该属性
@ApiParam(“xxx参数说明”) 作用在参数、方法和字段上,类似@ApiModelProperty

在类上加

@Api(tags = "潆勖的类接口")
@RestController
public class MyController {
    @ApiOperation("潆勖的接口")
    @PostMapping("/yingxu")
    public String yingxu(@ApiParam("这个名字会被返回")String username){
        return username;
    }
}

SpringBoot_第52张图片

也可以给请求的接口配置一些注释

@ApiOperation("潆勖的接口")
@PostMapping("/yingxu")
public String yingxu(@ApiParam("这个名字会被返回")String username){
   return username;
}

在这里插入图片描述

这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!

相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。

Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。

拓展:其他皮肤

1、默认的 访问 http://localhost:8080/swagger-ui/index.html

<dependency>
   <groupId>io.springfoxgroupId>
   <artifactId>springfox-swagger-uiartifactId>
   <version>3.0.0version>
dependency>

SpringBoot_第53张图片

2、bootstrap-ui 访问 http://localhost:8081/doc.html


<dependency>
    <groupId>com.github.xiaoymingroupId>
    <artifactId>swagger-bootstrap-uiartifactId>
    <version>1.9.6version>
dependency>

在这里插入图片描述

3、Layui-ui 访问 http://localhost:8081/docs.html

<dependency>
    <groupId>com.github.caspar-chengroupId>
    <artifactId>swagger-ui-layerartifactId>
    <version>1.1.3version>
dependency>

SpringBoot_第54张图片

4、mg-ui 访问 http://localhost:8081/document.html

<dependency>
    <groupId>com.zyplayergroupId>
    <artifactId>swagger-mg-uiartifactId>
    <version>2.0.2version>
dependency>

SpringBoot_第55张图片

异步任务

1、创建一个service包

2、创建一个类AsyncService

异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;

@Service
public class AsyncService {

   public void hello(){
       try {
           Thread.sleep(3000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("业务进行中....");
  }
}

3、编写controller包

4、编写AsyncController类

  • 写一个Controller测试一下
@RestController
public class AsyncController {

   @Autowired
   AsyncService asyncService;

   @GetMapping("/hello")
   public String hello(){
       asyncService.hello();
       return "success";
  }

}

5、访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。

问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:

6、给hello方法添加@Async注解;

//告诉Spring这是一个异步方法
@Async
public void hello(){
   try {
       Thread.sleep(3000);
  } catch (InterruptedException e) {
       e.printStackTrace();
  }
   System.out.println("业务进行中....");
}

SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;

@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {

   public static void main(String[] args) {
       SpringApplication.run(SpringbootTaskApplication.class, args);
  }

}

7、重启测试,网页瞬间响应,后台代码依旧执行!

定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口 任务执行
  • TaskScheduler接口 任务调度

两个注解:

  • @EnableScheduling
  • @Scheduled

在main方法上加入

Cron表达式

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可为空) 留空, 1970-2099 , - * /

时间

字段 详情
允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每隔1秒钟触发

“,” 代表在指定的秒数触发,比如"0,15,45"代表0秒、15秒和45秒时触发任务

“-” 代表在指定的范围内触发,比如"25-45"代表从25秒开始触发到45秒结束触发,每隔1秒触发1次

“/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/20"或者”/20"代表从0秒钟开始,每隔20秒钟触发1次,即0秒触发1次,20秒触发1次,40秒触发1次;"5/20"代表5秒触发1次,25秒触发1次,45秒触发1次;"10-45/20"代表在[10,45]内步进20秒命中的时间点触发,即10秒触发1次,30秒触发1次
分钟 允许值范围: 0~59 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每隔1分钟触发

“,” 代表在指定的分钟触发,比如"10,20,40"代表10分钟、20分钟和40分钟时触发任务

“-” 代表在指定的范围内触发,比如"5-30"代表从5分钟开始触发到30分钟结束触 发,每隔1分钟触发

“/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/25"或者”/25"代表从0分钟开始,每隔25分钟触发1次,即0分钟触发1次,第25分钟触发1次,第50分钟触发1次;"5/25"代表5分钟触发1次,30分钟触发1次,55分钟触发1次;"10-45/20"代表在[10,45]内步进20分钟命中的时间点触发,即10分钟触发1次,30分钟触发1次
小时 允许值范围: 0~23 ,不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每隔1小时触发

“,” 代表在指定的时间点触发,比如"10,20,23"代表10点钟、20点钟和23点触发任务

“-” 代表在指定的时间段内触发,比如"20-23"代表从20点开始触发到23点结束触发,每隔1小时触发

“/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"0”),后面的值代表偏移量,比如"0/1"或者”/1"代表从0点开始触发,每隔1小时触发1次;"1/2"代表从1点开始触发,以后每隔2小时触发一次
日期 允许值范围: 1~12 (JAN-DEC),不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每个月都触发

“,” 代表在指定的月份触发,比如"1,6,12"代表1月份、6月份和12月份触发任务

“-” 代表在指定的月份范围内触发,比如"1-6"代表从1月份开始触发到6月份结束触发,每隔1个月触发

“/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"1”),后面的值代表偏移量,比如"1/2"或者”/2"代表从1月份开始触发,每隔2个月触发1次;"6/6"代表从6月份开始触发,以后每隔6个月触发一次;"1-6/12"表达式意味着每年1月份触发
星期 允许值范围: 1~7 (SUN-SAT),1代表星期天(一星期的第一天),以此类推,7代表星期六(一星期的最后一天),不允许为空值,若值不合法,调度器将抛出SchedulerException异常

“*” 代表每星期都触发;

“?” 与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义,以免引起冲突和混乱

“,” 代表在指定的星期约定触发,比如"1,3,5"代表星期天、星期二和星期四触发

“-” 代表在指定的星期范围内触发,比如"2-4"代表从星期一开始触发到星期三结束触发,每隔1天触发

“/” 代表触发步进(step),“/“前面的值代表初始值(”“等同"1”),后面的值代表偏移量,比如"1/3"或者”/3"代表从星期天开始触发,每隔3天触发1次;“1-5/2"表达式意味着在[1,5]范围内,每隔2天触发,即星期天、星期二、星期四触发

“L” 如果{星期}占位符如果是"L”,即意味着星期的的最后一天触发,即星期六触发,L= 7或者 L = SAT,因此,“5L"意味着一个月的最后一个星期四触发

“#” 用来指定具体的周数,”#“前面代表星期,”#"后面代表本月第几周,比如"2#2"表示本月第二周的星期一,"5#3"表示本月第三周的星期四,因此,“5L"这种形式只不过是”#"的特殊形式而已
年份 允许值范围: 1970~2099 ,允许为空,若值不合法,调度器将抛出SchedulerException异常

“*“代表每年都触发

”,“代表在指定的年份才触发,比如"2011,2012,2013"代表2011年、2012年和2013年触发任务

”-“代表在指定的年份范围内触发,比如"2011-2020"代表从2011年开始触发到2020年结束触发,每隔1年触发

”/“代表触发步进(step),”/“前面的值代表初始值(”“等同"1970”),后面的值代表偏移量,比如"2011/2"或者”/2"代表从2011年开始触发,每隔2年触发1次

注意:除了{日期}和{星期}可以使用"?"来实现互斥,表达无意义的信息之外,其他占位符都要具有具体的时间含义,且依赖关系为:年->月->日期(星期)->小时->分钟->秒数

特殊字符

字段 详情
* “*”字符被用来指定所有的值。如:""在分钟的字段域里表示“每分钟”。
? “?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。看下面的例子你就会明白。 月份中的日期和星期中的日期这两个元素时互斥的一起应该通过设置一个问号来表明不想设置那个字段。
- “-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”。
, “,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”。
/ “/”字符用于指定增量。如:“0/15”在秒域意思是每分钟的0,15,30和45秒。“5/15”在分钟域表示每小时的5,20,35和50。符号“”在“/”前面(如:/10)等价于0在“/”前面(如:0/10)。记住一条本质:表达式的每个数值域都是一个有最大值和最小值的集合,如:秒域和分钟域的集合是0-59,日期域是1-31,月份域是1-12。字符“/”可以帮助你在每个字符域中取相应的数值。如:“7/6”在月份域的时候只有当7月的时候才会触发,并不是表示每个6月。
L 字符“L”是‘last’的省略写法可以表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五。
W 字符“W”只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的。
“L”和“W”可以在日期域中联合使用,LW表示这个月最后一周的工作日。
# 字符“#”只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三。
C 字符“C”允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。

测试步骤:

1、创建一个ScheduledService

  • 里面存在一个hello方法,他需要定时执行,怎么处理呢?

每两秒执行一次:

@Service
public class ScheduledService {
    //秒   分   时     日   月   周几
    //注意cron表达式的用法;
    @Scheduled(cron = "0/2 * * * * ?")
    public void hello(){
        System.out.println("hello.....");
    }
}

2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能

@EnableAsync//开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class Springboot09AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot09AsyncApplication.class, args);
    }

}

3、我们来详细了解下cron表达式;

cron表达式

4、常用的表达式

(1)0/2 * * * * ?   表示每2秒 执行任务
(1)0 0/2 * * * ?   表示每2分钟 执行任务
(1)0 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ?   每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED   表示每个星期三中午12点
(7)0 0 12 * * ?   每天中午12点触发
(8)0 15 10 ? * *   每天上午10:15触发
(9)0 15 10 * * ?     每天上午10:15触发
(10)0 15 10 * * ?   每天上午10:15触发
(11)0 15 10 * * ? 2005   2005年的每天上午10:15触发
(12)0 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ?   在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ?   在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED   每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI   周一至周五的上午10:15触发
(18)0 15 10 15 * ?   每月15日上午10:15触发
(19)0 15 10 L * ?   每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L   每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3   每月的第三个星期五上午10:15触发

邮件任务

邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持

  • 邮件发送需要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送

测试:

1、引入pom依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-mailartifactId>
dependency>

看它引入的依赖,可以看到 jakarta.mail

<dependency>
  <groupId>com.sun.mailgroupId>
  <artifactId>jakarta.mailartifactId>
  <version>1.6.7version>
  <scope>compilescope>
dependency>

2、配置文件:

application.properties

spring.mail.username=你的qq号@qq.com
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务

SpringBoot_第56张图片

3、Spring单元测试

@SpringBootTest
class Springboot09AsyncApplicationTests {

    @Autowired
    @Resource
    JavaMailSenderImpl mailSender;

    @Test
    void contextLoads() {
        //邮件设置1:一个简单的邮件
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setSubject("hello,潆勖");//邮件标题
        mailMessage.setText("加油学习!");//内容
        mailMessage.setTo("[email protected]");//收件人
        mailMessage.setFrom("[email protected]");//发件人
        mailSender.send(mailMessage);
    }
    @Test
    void contextLoads2() throws MessagingException {
        //邮件设置2:一个复杂的邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组装   true:启用复杂的邮件
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setSubject("潆勖,你好");//邮件标题
        helper.setText("加油学习呀!",true);//内容

        //发送附件
        helper.addAttachment("1.jpg",new File("G:\\JavaSE\\img\\1.jpg"));

        helper.setTo("[email protected]");//收件人
        helper.setFrom("[email protected]");//发件人
        mailSender.send(mimeMessage);
    }
}

查看邮箱,邮件接收成功!

分布式 Dubbo+Zooker

什么是分布式系统?

在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据

分布式系统(distributed system)是建立在网络之上的软件系统。

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

Dubbo文档

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。

在Dubbo的官网文档有这样一张图

SpringBoot_第57张图片

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

SpringBoot_第58张图片

适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

SpringBoot_第59张图片

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点:公用模块无法重复利用,开发性的浪费

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键。

SpringBoot_第60张图片

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。

SpringBoot_第61张图片

RPC

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;

推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b

RPC基本原理

SpringBoot_第62张图片

步骤解析:

SpringBoot_第63张图片

RPC两个核心模块:通讯,序列化。

测试环境搭建

Dubbo

Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

dubbo官网 :dubbo官网

1.了解Dubbo的特性

2.查看官方文档

dubbo基本概念

SpringBoot_第64张图片

服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

服务容器负责启动,加载,运行服务提供者。

服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者在启动时,向注册中心订阅自己所需的服务。

注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Dubbo环境搭建

点进dubbo官方文档,推荐使用Zookeeper 注册中心:Zookeeper 注册中心

什么是zookeeper呢?可以查看官方文档

Window下安装zookeeper

1、下载zookeeper :地址, 最新版!解压zookeeper

2、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;

可能遇到问题:闪退 !

解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。

SpringBoot_第65张图片


SpringBoot_第66张图片


SpringBoot_第67张图片

3、修改zoo.cfg配置文件

将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。

SpringBoot_第68张图片

注意几个重要位置:

  • dataDir=./ 临时数据存储的目录(可写相对路径)

  • clientPort=2181 zookeeper的端口号

修改完成后再次启动zookeeper

在这里插入图片描述

4、使用zkCli.cmd测试

  • ls /:列出zookeeper根下保存的所有节点
[zk: 127.0.0.1:2181(CONNECTED) 4] ls /
[zookeeper]

在这里插入图片描述

  • create –e /yingxu 123:创建一个yingxu节点,值为123

在这里插入图片描述

  • get /yingxu:获取/yingxu节点的值

在这里插入图片描述

再来查看一下节点

在这里插入图片描述

window下安装dubbo-admin(可以不要)

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。

但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。

我们这里来安装一下:

1、下载dubbo-admin

地址 :dubbo-admin

2、解压进入目录

修改 dubbo-admin\dubbo-admin-server\src\main\resources\application.properties 指定zookeeper地址

# 账号密码
admin.root.user.name=root
admin.root.user.password=root
# 注册中心的地址
admin.registry.address=zookeeper://127.0.0.1:2181
admin.config-center=zookeeper://127.0.0.1:2181
admin.metadata-report.address=zookeeper://127.0.0.1:2181

3、在项目目录下打包dubbo-admin

mvn clean package -D maven.test.skip=true

第一次打包的过程有点慢,需要耐心等待!直到成功!

SpringBoot_第69张图片

4、执行 dubbo-admin\dubbo-admin-distribution\target 下的dubbo-admin-0.5.0.jar

dubbo-admin-0.5.0.jar下载 密码:java

java -jar dubbo-admin-0.5.0.jar

【注意:zookeeper的服务一定要打开!】

【注意:如果运行了 zookeeper,8080 端口会被占用,启动失败,需要在 zoo.cfg 加上 admin.serverPort=8888

  • apache-zookeeper-3.7.1-bin\conf\zoo.cfg

SpringBoot_第70张图片

执行完毕,访问一下 http://localhost:8080/, 这时候需要输入登录账户和密码,都是默认的root-root;

登录成功后,查看界面

SpringBoot_第71张图片

安装完成!

SpringBoot + Dubbo + zookeeper

框架搭建

1. 启动zookeeper !

2. IDEA创建一个空项目;

3.创建一个Sping模块,实现服务提供者:provider-server , 选择web依赖即可

4.项目创建完毕,写一个服务,比如卖票的服务;

提供者

编写接口

public interface TicketService {
    public String getTicket();
}

编写实现类

public class TicketServiceImpl implements TicketService {
    @Override
    public String getTicket() {
        return "《Java》";
    }
}

application.properties配置

server.port=8001

5.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可

6.项目创建完毕,写一个服务,比如用户的服务;

消费者

编写service

public class UserService {
    //需要去拿去注册中心的服务
}

application.properties配置

server.port=8002

需求:现在用户想使用买票的服务,要怎么弄呢 ?

服务提供者(provider-server)

1、将服务提供者注册到注册中心,需要整合Dubbo和zookeeper,所以需要导包

从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包

  • 在provider-server中的pom.xml导入

<dependency>
    <groupId>org.apache.dubbogroupId>
    <artifactId>dubbo-spring-boot-starterartifactId>
    <version>3.1.1version>
dependency>

zookeeper的包去maven仓库下载,zkclient;

<dependency>
    <groupId>com.github.sgroschupfgroupId>
    <artifactId>zkclientartifactId>
    <version>0.1version>
dependency>

【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;


<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-frameworkartifactId>
    <version>5.3.0version>
dependency>
<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-recipesartifactId>
    <version>5.3.0version>
dependency>
<dependency>
    <groupId>org.apache.zookeepergroupId>
    <artifactId>zookeeperartifactId>
    <version>3.8.0version>
    
    <exclusions>
        <exclusion>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-log4j12artifactId>
        exclusion>
    exclusions>
dependency>

2、在springboot配置文件中配置dubbo相关属性!

  • provider-server中的application.properties
server.port=8001
#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.yingxu.provider.service

3、在service的实现类中配置服务注解,发布服务!注意导包问题

//zookeeper:服务注册与发现

@DubboService //将服务发布出去,可以被扫描到,在项目一启动就自动注册到注册中心
@Component //放在容器中
public class TicketServiceImpl implements TicketService {
    @Override
    public String getTicket() {
        return "《Java》";
    }
}

逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!

服务消费者(consumer-server)

1、导入依赖,和之前的依赖一样;


<dependency>
    <groupId>org.apache.dubbogroupId>
    <artifactId>dubbo-spring-boot-starterartifactId>
    <version>3.1.1version>
dependency>

<dependency>
    <groupId>com.github.sgroschupfgroupId>
    <artifactId>zkclientartifactId>
    <version>0.1version>
dependency>

<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-frameworkartifactId>
    <version>5.3.0version>
dependency>
<dependency>
    <groupId>org.apache.curatorgroupId>
    <artifactId>curator-recipesartifactId>
    <version>5.3.0version>
dependency>
<dependency>
    <groupId>org.apache.zookeepergroupId>
    <artifactId>zookeeperartifactId>
    <version>3.8.0version>
    
    <exclusions>
        <exclusion>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-log4j12artifactId>
        exclusion>
    exclusions>
dependency>

2、配置参数

  • application.properties
server.port=8002
#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

3、本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;

SpringBoot_第72张图片

4、完善消费者的服务类

@DubboService
public class UserService {
    //需要去拿去注册中心的服务
    @DubboReference //引用,Pom坐标,可以定义路径相同的接口名
    TicketService ticketService;

    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.out.println("在注册中心买到"+ticket);
    }
}

5、测试类编写;

@SpringBootTest
class ConsumerServerApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
        userService.buyTicket();
    }
}

启动测试

1. 开启zookeeper

2. 打开dubbo-admin实现监控【可以不用做】

3. 开启服务者

4. 消费者消费测试

ok , 这就是SpingBoot + dubbo + zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想;

过程总结

步骤:

  • 前提:zookeeper服务已开启

  • 提供者提供服务:

    • 导入依赖
    • 在application.properties中配置注册中心的地址,以及服务发现名,和要扫描的包
    • 在想要被注册的服务上增加一个注解:@Service (这个@Service注解是dubbo下的,它表明可以被扫描到,在项目一启动时就自动加载到注册中心)
  • 消费者如何消费

    • 导入依赖
    • 在application.properties中配置注册中心的地址,以及服务发现名
    • 从远程注入服务,在具体的类中用@Reference注解来远程引用指定的服务,正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同

你可能感兴趣的:(Java基础,SpringBoot)