SpringBoot
文章目录
- SpringBoot
- SpringBoot简介
-
- 回顾什么是Spring
- Spring是如何简化Java开发的
- 什么是SpringBoot
- Spring开发-HelloWord
-
- 准备工作
- 创建基础项目说明
- pom.xml 分析
- 更改端口号
- 更改Banner
- 运行原理探究
-
- pom.xml
-
- 父依赖
- 启动器 spring-boot-starter
- 主启动类
- 结论
- SpringApplication
-
- 不简单的方法
- SpringApplication
- run方法流程分析
- yaml
-
- 配置文件
- yaml概述
-
- yaml基础语法
- 注入配置文件
-
- yaml注入配置文件
- 加载指定的配置文件
- 配置文件占位符
- 对比小结
-
- JSR303检验
- 多环境切换
-
- 多配置文件
- yaml的多文档块
- 配置文件加载位置
- 自动配置原理
-
- 分析自动配置原理
- 精髓
- SpringBootWeb开发
-
- 静态资源
- 首页如何定制
- Thymeleaf模板引擎
- 模板引擎
- 引入Thymeleaf
- Thymeleaf分析
- Thymeleaf 语法学习
-
- 使用thymeleaf
- MVC配置原理
-
- 官网阅读
- ViewResolver 视图解析器
- 自定义ViewResolver
- 转换器和格式化器
- 修改SpringBoot的默认配置
- 页面国际化
- 自定义starter
- 整合JDBC
-
- SpringData简介
- 参数
- 写配置JDBC文件,application.xml
- JDBCTemplate
-
- 测试CRUD
- 集成Druid
-
- Druid简介
- 参数详解
- 配置数据源
- 整合Mybatis
-
- 测试
-
- 1.导入依赖
- 2.连接数据库
- 3.测试是否连接成功
- 4.导入lombok
- 5.实体类
- 6.mapper
- 7.mapper.xml
- 8.整合mybatis,让spring可以识别mappper
- 9.编写controller测试
- SpringSecurity
-
- 1 简介
- 2.导入依赖
- 3.配置SecurityConfigure
- 4.配置Controller层
- 5.认证和授权
- 6.注销以及控制权限
- 整合Shiro
-
- quickstart
- SpringBoot集成
-
- 1.导入依赖
- 2.编写配置文件application
- 3.连接数据库后编写实体类
- 4.mapper
- 5.service层
- 6.Controller
- 7.界面html
- 8.shiro配置
- 9.Realm
- Swagger
-
- SpringBoot集成Swagger
- 配置Swagger
- 配置扫描接口
- 配置Swagger是否启动
- 配置API分组
- 实体配置
-
- 常用注解
- Swagger测试
- 异步、定时、邮件任务
-
- 异步任务
- 邮件发送
- 定时任务
-
- Cron表达式
-
- 秒
- 分钟
- 小时
- 日期
- 星期
- 年份
- 特殊字符
-
- “*”
- “?”
- “-”
- “,”
- “/”
- “L”
- “W”
- “#”
- “C”
- **测试步骤:**
- 分布式 Dubbo+Zooker
-
- **什么是分布式系统?**
- RPC
- Dubbo
- Dubbo环境搭建
-
- 安装dubbo-admin
- SpringBoot-Dubbo-Zookeeper
SpringBoot简介
回顾什么是Spring
Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
Spring是如何简化Java开发的
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
2、通过IOC,依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;
什么是SpringBoot
学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;
言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。
所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。
是的这就是Java企业级应用->J2EE->spring->springboot的过程。
随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;
Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。
简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。
Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。
Spring Boot的主要优点:
- 为所有Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
Spring开发-HelloWord
准备工作
我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。
我的环境准备:
- java version “1.8.0_181”
- Maven-3.6.1
- SpringBoot 2.x 最新版
开发工具:
- IDEA
创建基础项目说明
Spring官方提供了非常方便的工具让我们快速构建应用
Spring Initializr:https://start.spring.io/
**项目创建方式一:**使用Spring Initializr 的 Web页面创建项目
1、打开 https://start.spring.io/
2、填写项目信息
3、点击”Generate Project“按钮生成项目;下载此项目
4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
**项目创建方式二:**使用 IDEA 直接创建项目
1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可)
5、填写项目路径
6、等待项目构建成功
项目结构分析:
通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
1、程序的主启动类
2、一个 application.properties 配置文件
3、一个 测试类
4、一个 pom.xml
pom.xml 分析
打开pom.xml,看看Spring Boot项目的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--有一个父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--所有的SpringBoot依赖都是spring-boot-starte 开头的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
更改端口号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rw2UNLK7-1642048144673)(C:/Users/77/AppData/Roaming/Typora/typora-user-images/image-20220104152933318.png)]
更改Banner
如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;
只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。
图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!
运行原理探究
我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;
pom.xml
父依赖
核心依赖在父工程中
这里我们在引入一些SpringBoot依赖的时候,不需要指定版本,就因为有这些版本仓库
其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点进去,发现还有一个父依赖
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
启动器 spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
-
启动器:说白了就是SpringBoot的启动场景
-
比如spring-boot-starter-web,会帮我们自动导入web环境所有的依赖
-
springboot会将所有功能场景,都变成一个个的启动器
-
我们要是用什么功能,就需要找到一个个启动器就可以了
Spring Boot Reference Documentation
主启动类
package com.example.springboot01helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//标注这个类是一个SpringBoot的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
//通过这个方法将springboot启动
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
-
@SpringBootConfiguration:SpringBoot的配置
-
@Configuration: spring配置类 @Component:说明这也是一个spring的组件
-
-
@EnableAutoConfiguration:自动配置
-
@AutoConfigurationPackage:自动配置包 @Import({Registrar.class}):导入了选择器 @Import({AutoConfigurationImportSelector.class}):自动配置选择器 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); //获取所有的配置
-
-
获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }
-
META-INF/spring.factories:自动配置的核心文件
结论
SpringBoot所有的自动配置,都在启动类中被扫描并加载:所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的starter们就有对应的启动器了,有了启动器,我们的自动装配就会生效,然后就配置成功了
- SpringBoot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值
- 将这些自动配置的类导入容器,自动配置类就会生效,帮我们进行自动配置
- 以前我们需要自动配置的东西,现在不需要了
- 整合javaEE,解决方案和自动配置的东西都在Spring-boot-autoconfigure下
- 它会把所有需要导入的组件,以类名的方式返回这些组件,这些组件就会被添加到容器
- 容器中也会存在非常多的XXXAutoConfigure的文件(@Bean),就是这个类给容器导入了这个场景所需要的所有组件并自动配置
SpringApplication
不简单的方法
我最初以为就是运行了一个main方法,没想到却开启了一个服务;
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.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,谈谈你的理解:
- 自动装配:如何加载
- run方法:如何启动
全面接管SpringMVC的配置!
yaml
配置文件
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
-
application.properties
-
- 语法结构 :key=value
-
application.yml
-
- 语法结构 :key:空格 value
**配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
yaml概述
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点!
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
传统xml配置:
<server>
<port>8081<port>
</server>
yaml配置:
server:
prot: 8080
yaml基础语法
# k-v键值对
name: xiaoqi
#相当于name=xiaoqi
# 存对象
student:
name: xiaoqi
age: 12
# 行内写法
student1: {name: xiaoqi,age: 13}
#数组
pets:
- cat
- dog
- pyg
pets1: [cat,dog]
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
注意:
-
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
-
‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
在下一行来写对象的属性和值得关系,注意缩进;比如:
student:
name: qinjiang
age: 3
行内写法
student: {name: qinjiang,age: 3}
数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
修改SpringBoot的默认端口号
配置文件中添加,端口号的参数,就可以切换端口;
server:
port: 8082
可以注入到我们的配置类当中
注入配置文件
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
yaml注入配置文件
1、在springboot项目中的resources目录下新建一个文件 application.yml
2、编写一个实体类 Dog;
package com.example.springboot02configure.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
//添加到spring组件中
public class Dog {
private String name;
private Integer age;
}
3、编写一个person类
package com.example.springboot02configure.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "person")
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: xiaoqi
age: 13
happy: false
birth: 2009/01/15
maps: {k1: v1,k2: v2}
lists:
- code
- dog
dog:
name: qq
age: 1
5、 在测试程序中测试
package com.example.springboot02configure;
import com.example.springboot02configure.pojo.Dog;
import com.example.springboot02configure.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.swing.*;
@SpringBootTest
class SpringBoot02ConfigureApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
加载指定的配置文件
**@PropertySource :**加载指定的配置文件;
@configurationProperties:默认从全局配置文件中获取值;
1、我们去在resources目录下新建一个person.properties文件
name=kuangshen
2、然后在我们的代码中指定加载person.properties文件
@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
@Value("${name}")
private String name;
...... }
配置文件占位符
配置文件还可以编写占位符生成随机
Person:
name: xiaoqi${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2009/01/15
maps: {k1: v1,k2: v2}
lists:
- code
- dog
dog:
name: ${person.hello:other}_旺财 #如果hello没有那么就默认值hello+旺财
age: 1
对比小结
@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
4、复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
JSR303检验
空检查
@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 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@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=)]()]()
多环境切换
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
多配置文件
我们在主配置文件编写的时候,文件名可以是 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
---
server:
port: 8082
Spring:
profiles: dev #命名
---
server:
port: 8083
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
配置文件加载位置
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
官方外部配置文件说明参考文档
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件优先级
2:项目路径下配置文件优先级
3:资源路径下的config文件夹配置文件优先级
4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部
加载主配置文件;互补配置;
我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;
#配置项目的访问路径server.servlet.context-path=/kuang
自动配置原理
配置文件到底能写什么?怎么写?
SpringBoot官方文档中有大量的配置,我们无法全部记住
分析自动配置原理
我们以**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 {
// .....
}
这就是自动装配的原理!
精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
SpringBootWeb开发
参考笔记:https://mp.weixin.qq.com/s/vYbEYlveseIGWWL_eEM36w
speingboot到底帮我们配置了什么?我们能不能修改?能修改哪些东西?能不能扩展?
- xxxAutoconfiguration:向容器中自动配置组件
- xxxxProperties:自动配置类,装配配置文件中自定义的一些内容
解决问题:
- 导入静态资源
- 首页
- jsp,模板引擎Thymeleaf
- 装配扩展SpringMVC
- 增删改查
- 拦截器
- 国际化
静态资源
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
什么是webjars’:WebJars是将web前端资源(js,css等)打成jar包文件,然后借助Maven工具,以jar包形式对web前端资源进行统一依赖管理,保证这些Web资源版本唯一性。WebJars的jar包部署在Maven中央仓库上。
-
拿到静态资源的第一种方式
http://localhost:8080/webjars/github-com-jquery-jquery/3.4.1/jquery.js
-
使用/**/static或者/resources或者/public来访问静态资源
localhost:8080/**/
优先级:resources>static(默认)>puiblic
很少使用webjars
首页如何定制
Thymeleaf模板引擎
模板引擎
前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。
jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。
那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?
SpringBoot推荐你可以来使用模板引擎:
模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是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.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
找到对应的pom依赖:可以适当点进源码看下本来的包
<dependency>
<!--都是基于3.xx开发的-->
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
结论:使用Thymeleaf,只需要导入对应的依赖即可,如果想要访问页面,我们将html放入Templates目录下即可
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什么都不需要配置,只需要将他放在指定的文件夹下即可!
Thymeleaf 语法学习
要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;
Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
<p>Today is: <span th:text="${today}">13 february 2011</span></p>
-
简单表达式:
- 变量表达式:
${...}
- 选择变量表达式:
*{...}
- 消息表达式:
#{...}
- 链接网址表达式:
@{...}
- 片段表达式:
~{...}
- 变量表达式:
-
文字
- 文本文本:,,…
'one text'``'Another one!'
- 数字文字: , , , ,…
0``34``3.0``12.3
- 布尔文字: ,
true``false
- 空文本:
null
- 文字标记: , , ,…
one``sometext``main
- 文本文本:,,…
-
文本操作:
- 字符串串联:
+
- 文字替换:
|The name is ${name}|
- 字符串串联:
-
算术运算:
- 二元运算符: , , , , ,
+``-``*``/``%
- 减号(一元运算符):
-
- 二元运算符: , , , , ,
-
布尔运算:
- 二元运算符: ,
and``or
- 布尔否定(一元运算符):,
!``not
- 二元运算符: ,
-
比较和平等:
- 比较器: , , , ( , , ,
>``<``>=``<=``gt``lt``ge``le
) - 等运算符: , (,
==``!=``eq``ne
)
- 比较器: , , , ( , , ,
-
条件运算符:
- 如果-那么:
(if) ? (then)
- 如果-然后-否则:
(if) ? (then) : (else)
- 违约:
(value) ?: (defaultvalue)
- 如果-那么:
-
特殊代币:
- 无操作:
_
- 无操作:
-
Simple expressions:
- Variable Expressions:
${...}
- Selection Variable Expressions:
*{...}
- Message Expressions:
#{...}
- Link URL Expressions:
@{...}
- Fragment Expressions:
~{...}
- Variable 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 literals: , ,…
-
Text operations:
- String concatenation:
+
- Literal substitutions:
|The name is ${name}|
- String concatenation:
-
Arithmetic operations:
- Binary operators: , , , ,
+``-``*``/``%
- Minus sign (unary operator):
-
- Binary operators: , , , ,
-
Boolean operations:
- Binary operators: ,
and``or
- Boolean negation (unary operator): ,
!``not
- Binary operators: ,
-
Comparisons and equality:
- Comparators: , , , (, , ,
>``<``>=``<=``gt``lt``ge``le
) - Equality operators: , (,
==``!=``eq``ne
)
- Comparators: , , , (, , ,
-
Conditional operators:
- If-then:
(if) ? (then)
- If-then-else:
(if) ? (then) : (else)
- Default:
(value) ?: (defaultvalue)
- If-then:
-
Special tokens:
- No-Operation:
_
- No-Operation:
使用thymeleaf
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有的html元素都可以被thymelaeaf替换接管;th:元素名-->
<div th:text="${msg}"></div>
</body>
</html>
package com.example.springboot03web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
//在templates目录下的所有页面,只能通过controller跳转
//需要模板引擎的依赖
@Controller
public class IndexContriller {
@RequestMapping("/test")
public String test(Model model){
model.addAttribute("msg","hello");
return "test";
}
}
MVC配置原理
官网阅读
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!
地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration
Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration
(interceptors, formatters, view controllers, and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
ViewResolver 视图解析器
自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法!
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
自定义ViewResolver
https://mp.weixin.qq.com/s/9AY48uLjR9bI9TUlulcBNA
转换器和格式化器
修改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) {
// 浏览器发送/test , 就会跳转到test页面;
registry.addViewController("/test").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自己的配置类,我们自己的配置类当然也会被调用;可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个
@EnableWebMvc 就是导入了一个 DelegatingWebMvcConfiguration 类,从容其中获取所有的webmvcconfig
在SpringBoot中由非常多的xxxConfiguration,会帮助我们进行扩展,只要看见了这个东西我们就要注意了
页面国际化
https://mp.weixin.qq.com/s/e4Jd3xIMF4C4HBzPQfakvg
自定义starter
https://mp.weixin.qq.com/s/2eB2uT088BvzaqRULezdsw
整合JDBC
SpringData简介
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
Sping Data 官网:https://spring.io/projects/spring-data
数据库相关的启动器 :可以参考官方文档:
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
参数
rl | 数据库的jdbc连接地址。一般为连接oracle/mysql。示例如下: | |
---|---|---|
mysql : jdbc:mysql://ip:port/dbname?option1&option2&… | ||
oracle : jdbc:oracle:thin:@ip:port:oracle_sid | ||
username | 登录数据库的用户名 | |
password | 登录数据库的用户密码 | |
initialSize | 启动程序时,在连接池中初始化多少个连接 | 10-50已足够 |
maxActive | 连接池中最多支持多少个活动会话 | |
maxWait | 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池 | 100 |
没有可用连接,单位毫秒,设置-1时表示无限等待 | ||
minEvictableIdleTimeMillis | 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将 | 见说明部分 |
回收该连接,要小于防火墙超时设置 | ||
net.netfilter.nf_conntrack_tcp_timeout_established的设置 | ||
timeBetweenEvictionRunsMillis | 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查 | |
keepAlive | 程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执 | true |
行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超 | ||
过minIdle指定的连接个数。 | ||
minIdle | 回收空闲连接时,将保证至少有minIdle个连接. | 与initialSize相同 |
removeAbandoned | 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该 | false,当发现程序有未 |
连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。 | 正常close连接时设置为true | |
removeAbandonedTimeout | 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此 | 应大于业务运行最长时间 |
值后,druid将强制回收该连接,单位秒。 | ||
logAbandoned | 当druid强制回收连接后,是否将stack trace 记录到日志中 | true |
testWhileIdle | 当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效) | true |
validationQuery | 检查池中的连接是否仍可用的 SQL 语句,drui会连接到数据库执行该SQL, 如果 | |
正常返回,则表示连接可用,否则表示连接不可用 | ||
testOnBorrow | 程序 申请 连接时,进行连接有效性检查(低效,影响性能) | false |
testOnReturn | 程序 返还 连接时,进行连接有效性检查(低效,影响性能) | false |
poolPreparedStatements | 缓存通过以下两个方法发起的SQL: | true |
public PreparedStatement prepareStatement(String sql) | ||
public PreparedStatement prepareStatement(String sql, | ||
int resultSetType, int resultSetConcurrency) | ||
maxPoolPrepareStatementPerConnectionSize | 每个连接最多缓存多少个SQL | 20 |
filters | 这里配置的是插件,常用的插件有: | stat,wall,slf4j |
监控统计: filter:stat | ||
日志监控: filter:log4j 或者 slf4j | ||
防御SQL注入: filter:wall | ||
connectProperties | 连接属性。比如设置一些连接池统计方面的配置。 | |
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 | ||
比如设置一些数据库连接属性: |
写配置JDBC文件,application.xml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&userUniceode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
测试
package com.example.springboot04date;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class SpringBoot04DateApplicationTests {
@Autowired
//注入数据
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看默认的数据源
System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource
//获取数据库连接
Connection connection = dataSource.getConnection();
//Template
//CURD豆子SpringBoot中封装好了,拿来即用
System.out.println(connection);
//关闭
connection.close();
}
}
我们来全局搜索一下,找到数据源的所有自动配置都在 :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方法:用于执行存储过程、函数相关语句。
测试CRUD
package com.example.springboot04date.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
//没有实体类,数据库中的东西如何获取
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from user";
List<Map<String,Object>> List_map= jdbcTemplate.queryForList(sql); //查询
return List_map;
}
@GetMapping("/ADD")
public String addUser(){
String sql = "insert into user(id,name,pwd) values(6,'xioa','1234567')";
jdbcTemplate.update(sql);
return "update-ok";
//自动提交了事务
}
@GetMapping("/update/{id}")
public String update(@PathVariable("id") int id){
String sql = "update user set name = ? ,pwd= ?where id ="+id;
//封装
Object[] objects = new Object[2];
objects[0] = "qq";
objects[1] = "12345678";
jdbcTemplate.update(sql,objects);
return "update-ok2";
}
@GetMapping("/delete/{id}")
public String delete(@PathVariable("id") int id){
String sql = "delete from user where id = ?";
jdbcTemplate.update(sql,id);
return "delet-ok";
}
}
集成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 基本配置参数如下:
参数详解
DruidDataSource的使用、配置
https://mp.weixin.qq.com/s/wVAGOP1JdXZi5DMEsX1Aug
配置数据源
1、添加上 Druid 数据源依赖。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
2、去配置Druid,配置自定义的数据源
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&userUniceode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
3、接下来就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters:
commons-log.connection-logger-name: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
4、因为Druid中包含log4j所以需要导入依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
5、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性
public class DruidConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
//绑定到application.xml文件中 生效
public DataSource druidDateSource(){
return new DruidDataSource();
}
}
6、Druid具有看监控功能,可以方便用户在web端看见后台的操作,配置后台监控和过滤器
package com.example.springboot04date.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.io.File;
import java.util.HashMap;
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
//绑定到application.xml文件中 生效
public DataSource druidDateSource(){
return new DruidDataSource();
}
//因为iSpringBoot内置了servlet容器,所有没有web.xml,替代方法、:ServletRegistrationBean
//后台监控功能
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");//访问这个就可以进入后台监控页面,写死的
//后台需要有人登录
//账号密码配置也是死的
HashMap<String,String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin");
initParameters.put("loginPassword","123456");//key是固定的
//允许谁能访问
initParameters.put("allow","");
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
//filter后台过滤
@Bean
public FilterRegistrationBean webServletFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求
HashMap<String,String> map = new HashMap<>();
map.put("exclusions","*.js,*.css,/druid/*");//这些东西不进行统计
bean.setInitParameters(map);
return bean;
}
}
整合Mybatis
官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
Maven仓库地址:Maven Repository: org.mybatis.spring.boot » mybatis-spring-boot-starter » 2.1.3 (mvnrepository.com)
测试
1.导入依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
2.连接数据库
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?userUnicode=true&useSSL=true&characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3.测试是否连接成功
package com.example.springboot05mybatis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.SQLException;
@SpringBootTest
class SpringBoot05MybatisApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
System.out.println(dataSource.getConnection());
}
}
4.导入lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
5.实体类
package com.example.springboot05mybatis.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private Integer id;
private String name;
private String pwd;
}
6.mapper
package com.example.springboot05mybatis.mapper;
import com.example.springboot05mybatis.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
//这个实体类既需要具有增删改查的功能又需要注册到spring中托管所有需要Repository和mapper
@Mapper
@Repository //Dao层
//这个注解代表了这是一个mybatis的mapper接口
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int updateUser(User user);
int addUser(User user);
int deleteUser(int id);
}
7.mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springboot05mybatis.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select * from user
</select>
<select id="queryUserById" parameterType="int" resultType="User">
select * from user where id = #{id}
</select>
<update id="updateUser" parameterType="User" >
update user set name=#{name},pwd=#{pwd} where id = #{id}
</update>
<insert id="addUser" parameterType="User">
insert into user(id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
</mapper>
8.整合mybatis,让spring可以识别mappper
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?userUnicode=true&useSSL=true&characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#整合Mybatis
#让spring识别到mapper文件
mybatis.type-aliases-package=com.example.springboot05mybatis.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
9.编写controller测试
package com.example.springboot05mybatis.controller;
import com.example.springboot05mybatis.mapper.UserMapper;
import com.example.springboot05mybatis.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/query")
public List<User> quertyUserList(){
List<User> users = userMapper.queryUserList();
for (User user : users){
System.out.println(user);
}
return users;
}
@GetMapping("/byid/{id}")
public User queryUserById(@PathVariable("id") int id){
User user = userMapper.queryUserById(id);
System.out.println(user);
return user;
}
@GetMapping("/updata")
public String updateUser(){
User user = new User(4,"xiaoqi","123456");
if (userMapper.updateUser(user)!=0){
return "ok";
}
return "false";
}
}
SpringSecurity
https://mp.weixin.qq.com/s/FLdC-24_pP8l5D-i7kEwcg
shiro、SpringSecurity:很想,除了类不一样
1 简介
-
SpringSecurity是Springboot底层安全模块默认的技术选型,它可以实现强大的Web安全机制,只需要少数的
spring-boot--spring-security
依赖,进行少量的配置,就可以实现 -
SpringBoot中的SpringSecurity依赖:
-
功能权限、访问权限、菜单权限…,我们使用过滤器,拦截器需要写大量的原生代码,这样很不方便
-
所以在网址设计之初,就应该考虑到权限验证的安全问题,其中Shiro、SpringSecurity使用很多
2.导入依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
记住几个类 :
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
两个单词:en是认证,or是权限
- 认证方式:Authentication
- 权限:Authorization
3.配置SecurityConfigure
package com.xiaoqi.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
4.配置Controller层
package com.xiaoqi.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}
//可以实现公用
@RequestMapping("/level1/{id}")
public String level(@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.认证和授权
package com.xiaoqi.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
//授权
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,但是功能页只有对应有权限的人才能访问
//链式编程
//请求授权的规则
http.authorizeHttpRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/levle3/**").hasRole("vip3");
//没有权限默认为登陆也
http.formLogin();
}
//认证
//密码编码 PasswordEncoder
//在SpringSecuritys5中,新增了许多加密方式
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//这些数据应该从数据库中读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("xiaoqi").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");
}
}
写一个类继承WebSecurityConfigurerAdapter
,使用@EnableWebSecurity开启web安全服务
- 地址授权:使用HttpSecurity security
- 账户认证和给予权限:使用AuthenticationManagerBuilder builder
- SpringSecurity5 以后默认需要密码加密方式,推荐使用passwordEncoder(new BCryptPasswordEncoder())
6.注销以及控制权限
-
定制登录
- 开启登录功能:formLogin(),Springboot默认自带一个登录页/login定制看下面
-
注销
- springboot自带注销页:
/logout
- 注销成功后跳转到/控制器
- springboot自带注销页:
-
记住我
- 本质就是存一个cookies,默认保存2周
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,但是功能页只有对应有权限的人才能访问
//链式编程
//请求授权的规则
http.authorizeHttpRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/levle3/**").hasRole("vip3");
//定制登陆页
//没有权限默认为登陆页
http.formLogin();
//防止网站工具:get post
http.csrf().disable();//登出失败可能出现的原因
//注销 跳会首页
//开启了注销功能
http.logout().logoutSuccessUrl("/index");
//自定义
// 开启记住我功能:本质就是记住一个cookies,默认保存2周
//http.rememberMe().rememberMeParameter("remember");
http.rememberMe();
}
- index.html开启权限
-
开始权限首先引入依赖
-
前端根据用户权限选择性展示元素,可以使用SpringSecurity和thymeleaf的整合包,更方便授权
依赖:thymeleaf-extras-springsecurity5
Springboot2.1.X以上需要springSecurity5的版本 -
引入
-
xmlns:sec=“http://www.thymeleaf.org/thymeleaf-extras-springsecurity5”
-
编写
<div sec:authorize="!isAuthenticated()"> 未登录显示 <div sec:authorize="isAuthenticated()"> 以登陆显示
-
<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" th:href="@{/logout}">
<!--从授权那里获取name-->
用户名:<span sec:authentication="name"></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>
- 根据不同的权限显示不同的角色
- 设置登陆页面-把默认的页面替换为我们写的
// 定制登录页,loginPage("/toLogin")
// 指定表单提交url:loginProcessingUrl("/user/login")
http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password");
-
index修改 -表单提交到toLogin
-
login修改 - 表达提交到默认的login
整合Shiro
shiro官网:http://shiro.apache.org/,用Idea观看官方Quickstart源码,配置运行一遍
核心三大对象:用户Subject, 管理用户SecurityManager, 连接数据Realms
- Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
- SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
- Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
quickstart
1.导入依赖
2.配饰shiro.ini
3.quickstart
4.Spring Secutrity也有的方法
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated()
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
currentUser.logout();
SpringBoot集成
1.导入依赖
<dependencies>
<!--整合shiro
Subject:用户
SecurityManager:管理所有用户
Realm:连接数据
-->
<!--连接musql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--整合thymeleaf-->
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
2.编写配置文件application
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&userUniceode=true&characterEncoding=utf8
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
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters:
commons-log.connection-logger-name: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.xiaoqi.pojo
3.连接数据库后编写实体类
package com.example.xiaoqi.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
private String perms;
}
4.mapper
package com.example.xiaoqi.mapper;
import com.example.xiaoqi.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface UserMapper {
public User queryByName(String name);
}
- @Mapper:这个注解会产生响应的实现类
- @Repository:用在Dao层上的注册bean,这是因为该注解的作用不只是将类识别为Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。 Spring本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.xiaoqi.mapper.UserMapper">
<select id="queryByName" parameterType="String" resultType="User">
select * from user where name = #{name}
</select>
</mapper>
5.service层
package com.example.xiaoqi.service;
import com.example.xiaoqi.pojo.User;
public interface UserService {
public User queryByName(String name);
}
package com.example.xiaoqi.service;
import com.example.xiaoqi.mapper.UserMapper;
import com.example.xiaoqi.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User queryByName(String name) {
User user = userMapper.queryByName(name);
return user;
}
}
- @Service是Service层组件
6.Controller
实现跳转
package com.example.xiaoqi.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.jws.WebParam;
@Controller
public class MyController {
@RequestMapping(value = {"/","/test"})
public String toindex(){
return "index";
}
@RequestMapping("/user/add")
public String add(Model model){
model.addAttribute("msg","a");
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";
}
}
@RequestMapping("/unauthorized")
@ResponseBody
public String unauthorized(){
return "unauthorized";
}
}
7.界面html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<!--guest标签(与@RequiresGuest对应),验证用户没有登录认证或被记住过,验证是否是一个guest的请求-->
<div shiro:guest="true">
<a th:href="@{/toLogin}">登录</a></div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a></div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</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>
</form>
</body>
</html>
8.shiro配置
package com.example.xiaoqi.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class shiroConfig {
//ShrioFilterBean 工厂对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//配置安全管理器
shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager());
//条件shiro内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user: 必须拥有记住我才能访问
perms:拥有对某个资源的权限
role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
/*
filterMap.put("/user/add","anon");
filterMap.put("/user/update","authc");
*/
//授权,没有授权的话会跳转到未授权页面
//必须带有user:add才有权限
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
//登录认证
//设置权限
filterMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//登陆拦截,设置登录请求
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//时间遏制未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
return shiroFilterFactoryBean;
}
//DefaulWebSecurityManger
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联realm
defaultWebSecurityManager.setRealm(userRealm());
return defaultWebSecurityManager;
}
//创建realm对象 - 需要自定义 第一步
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
@Bean
//整合ShiroDialect 用来整合shiro和thymeleaf
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
- 1 创建XXXrealm对象,注册进组件
- 2 获取安全管理器DefaultWebSecurityManager,设置xxxRealm
- 3 获取ShiroFilterFactoryBean
- 创建ShiroFilterFactoryBean,设置安全管理器setSecurityManager
- LinkedMap存储url和权限对应,setFilterChainDefinitionMap(map);
- 指定登录映射:setLoginUrl
9.Realm
package com.example.xiaoqi.config;
import com.example.xiaoqi.pojo.User;
import com.example.xiaoqi.service.UserService;
import org.apache.catalina.security.SecurityUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
//自定义Realmd对象
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 curUser = (User)subject.getPrincipal();//取出user
//设置当前用户的权限
info.addStringPermission(curUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
//用户名和 密码
//连接真实的数据
UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
User user = userService.queryByName(usertoken.getUsername());
if (user == null){
return null;//抛出异常
}
return new SimpleAccount(user,user.getPwd(),"");//一个简单的账户
}
}
- 类继承extends AuthorizingRealm
- 先认证AuthenticationInfo
- ShiroConfig中的配置的登录映射走这里,提交表单封装成一个token,通过token中的信息走数据库查询数据库查出用户名是否存在,如果存在就进行密码验证
- new SimpleAuthenticationInfo(currentUser, currentUser.getPwd(), “”);:第一个形参传递查询出用户作为subject,这个subject等待AuthorizationInfo使用;第二个参数是用户密码;第三个参数realmName
- 后授权AuthorizationInfo
- ShiroConfig的Map配置的Url被拦截走这里,创建简单的授权信息 new SimpleAuthorizationInfo()
- AuthenticationInfo最后第一参数传递过来subject获取当前用户,再获取权限,封装进简单授权信息,完成简单权限设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HnpKVeQN-1642048144683)(C:/Users/77/AppData/Roaming/Typora/typora-user-images/image-20220110104617749.png)]
Swagger
前后端分离
- 前端 -> 前端控制层、视图层
- 后端 -> 后端控制层、服务层、数据访问层
- 前后端通过API进行交互
- 前后端相对独立且松耦合
- 前后端可以部署在不同的服务器上
产生的问题
- 前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发
解决方案
- 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险
Swagger
- 号称世界上最流行的API框架
- Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
- 直接运行,在线测试API
- 支持多种语言 (如:Java,PHP等)
- 官网:https://swagger.io/
SpringBoot集成Swagger
SpringBoot集成Swagger => springfox,两个jar包
- Springfox-swagger2
- swagger-springmvc
使用Swagger
要求:jdk 1.8 + 否则swagger2无法运行
1.新建项目
2.导入jar包
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.10.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
3.新建一个hello,测试环境
4.配置swagger ==> config
@Configuration //加载到配置里面
//开启Swagger
@EnableSwagger2
public class SwaggerConfig {
}
5.测试运行
Swagger UI
配置Swagger
1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)//配置了Swaggerbean实例
.apiInfo(apiInfo());//重新配置了默认的文档信息
}
2.创建一个apiinfo 配置基本信息
//配置Swagger信息=apiInfo
public ApiInfo apiInfo(){
Contact contact = new Contact("小七","xiaoqistudy.blog.csdn.net","123");//作者信息
return new ApiInfo("Api Documentation",
"Api Documentation", "1.0",
"xiaoqistudy.blog.csdn.nets", contact,
"Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
}
配置扫描接口
构建Docket时通过select()方法配置怎么扫描接口。
package com.example.xiaoqi.config;
import com.example.xiaoqi.Controller.HelloController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
@Configuration //加载到配置里面
//开启Swagger
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)//配置了Swaggerbean实例
.apiInfo(apiInfo())//重新配置了默认的文档信息
.select()
//RequestHandlerSelectors 要扫描接口的方式
//指定要扫描的包 basePackage()
//any() 扫描全部
//withClassAnnotation() 扫描类上的注解,需要注解的反射对象
//withMethodAnnotation() 扫描方法上注解
//只会扫描有ResrController注解的类.生成一个接口
.apis(RequestHandlerSelectors.basePackage("com.example.xioaqi.controller"))
.paths(PathSelectors.ant("/xiaoqi/**"))//过滤路径
.build();//工厂模式
}
//配置Swagger信息=apiInfo
public ApiInfo apiInfo(){
Contact contact = new Contact("小七","xiaoqistudy.blog.csdn.net","123");//作者信息
return new ApiInfo("Api Documentation",
"Api Documentation", "1.0",
"xiaoqistudy.blog.csdn.nets", contact,
"Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
}
}
除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:
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) // 根据包路径扫描
路径过滤这里的可选值还有
any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 通过正则表达式控制
ant(final String antPattern) // 通过ant()控制
配置Swagger是否启动
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)//配置了Swaggerbean实例
.apiInfo(apiInfo())//重新配置了默认的文档信息
//是否启用swagger,false浏览器不能访问nswagger
.enable(false)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.xioaqi.controller"))
.build();
}
设置在某些环境可以使用swagger,在某些环境下不能使用swagger
@Bean
public Docket docket(Environment environment){
Profiles profiles = Profiles.of("dev");//设置可以使用swagger的环境
//获取环境
//通过environment.acceptsProfiles判断是否出来自己设定的环境
boolean flag = environment.acceptsProfiles(profiles);//获得监听的对象
return new Docket(DocumentationType.SWAGGER_2)//配置了Swaggerbean实例
.apiInfo(apiInfo())//重新配置了默认的文档信息
//是否启用swagger,false浏览器不能访问nswagger
.enable(flag)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.xioaqi.controller"))
.build();
}
配置API分组
.groupName("xiaoqi")
如何配置多个分组?配置多个分组只需要配置多个docket即可:
@Bean
public Docket docket(Environment environment){
return new Docket(DocumentationType.SWAGGER_2)//配置了Swaggerbean实例
.apiInfo(apiInfo())
.groupName("xiaoqi")
}
//配置多个Docket
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("xiaoqi2");
}
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("xiaoqi3");
}
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("xiaoqi4");
}
实体配置
package com.example.xiaoqi.pojo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
@ApiModel("用户实体类")
public class User {
public String username;
public String password;
}
在controller配置user
package com.example.xiaoqi.Controller;
import com.example.xiaoqi.pojo.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
//只要我们的接口中,返回值中存在实体类,他就会被扫描到
@PostMapping("/user")
public User user(){
return new User();
}
}
并不是因为@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 |
我们也可以给请求的接口配置一些注释
@ApiOperation("狂神的接口")
@PostMapping("/kuang")
@ResponseBody
public String kuang(@ApiParam("这个名字会被返回")String username){
return username;
}
这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!
相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。
Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。
Swagger测试
package com.example.xiaoqi.pojo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ApiModel("用户实体类")
@Api
@AllArgsConstructor
@Data
@NoArgsConstructor
public class User {
public String username;
public String password;
}
@RestController
public class HelloController {
@ApiOperation("post")
@RequestMapping("/hello2")
public User hello2(@ApiParam("用户名") User user){
return user;
}
}
异步、定时、邮件任务
异步任务
package com.example.xiaoqi.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsynService {
//告诉Spring这是一个异步的方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理");
}
}
package com.example.xiaoqi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
//开启异步功能
@EnableAsync
@SpringBootApplication
public class SpringBoot09Application {
public static void main(String[] args) {
SpringApplication.run(SpringBoot09Application.class, args);
}
}
package com.example.xiaoqi.Controller;
import com.example.xiaoqi.service.AsynService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AsynController {
@Autowired
AsynService asynService;
@RequestMapping("/hello")
public String hello(){
asynService.hello();//停止三秒钟
return "ok";
}
}
邮件发送
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
设置自己的邮箱
spring.mail.username=719189161@qq.com
spring.mail.password=xxxxxxxxxx
spring.mail.host=smtp.qq.com
spring.mail.properties.mail.smtp.ssl.enable=true
import javax.mail.internet.MimeMessage;
@SpringBootTest
class SpringBoot09ApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
//简单的邮件
@Test
void contextLoads() {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("12");
mailMessage.setText("11");
mailMessage.setTo("719189161@qq.com");
mailMessage.setFrom("719189161@qq.com");
mailSender.send(mailMessage);
}
}
发送复杂的邮件,使用helper
//复杂的邮件
@Test
void contextLoads2() throws MessagingException {
//一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装
//正文
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,true);
messageHelper.setSubject("12");
//true 支持html
messageHelper.setText("<p>123</p>",true);
//附件
messageHelper.addAttachment("1.jpg",new File("D:\\java\\SpringBoot\\SpringBoot-09\\src\\main\\resources\\images\\1.jpg"));
messageHelper.setTo("719189161@qq.com");
messageHelper.setFrom("719189161@qq.com");
mailSender.send(mimeMessage);
}
定时任务
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。
- TaskExecutor接口 任务执行
- TaskScheduler接口 任务调度
两个注解:
- @EnableScheduling
- @Scheduled
在main方法上加入
@EnableScheduling //开始定时功能的主角
public class SpringBoot09Application {
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方法,他需要定时
package com.example.xiaoqi.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class ScheduledService {
//Cron表达式
//秒 分 时 月 星期
//每一天的0秒启动
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("Scheduled被执行了");//在一个特定的时间执行这个方法
}
}
2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能
@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
}
}
3、常用的表达式
(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触发
分布式 Dubbo+Zooker
什么是分布式系统?
在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
分布式系统(distributed system)是建立在网络之上的软件系统。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。
RPC
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;
RPC两个核心模块:通讯,序列化。
Dubbo
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明
l 服务容器负责启动,加载,运行服务提供者。
l 服务提供者在启动时,向注册中心注册自己提供的服务。
l 服务消费者在启动时,向注册中心订阅自己所需的服务。
l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo环境搭建
点进dubbo官方文档,推荐我们使用Zookeeper 注册中心
什么是zookeeper呢?可以查看官方文档
下载带有bin文件的zookeeper
解压后,将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。
新版本更改zoo.cfg,添加一行audit.enable=true
启动cli客户端测试,链接成功
在客户端测试
ls /
创建节点
create -e /xiaoqi
安装dubbo-admin
1、下载dubbo-admin
地址 :https://github.com/apache/dubbo-admin/tree/master
2、解压进入目录
修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
3、在项目目录下打包dubbo-admin
mvn clean package -Dmaven.test.skip=true
SpringBoot-Dubbo-Zookeeper
https://mp.weixin.qq.com/s/sKu9-vH7NEpUd8tbxLRLVQ