SripingBoot 启程
微服务(Microservices)——Martin Flower - 船长&CAP - 博客园 (cnblogs.com)
SpringBoot官方学习文档:https://spring.io/projects/spring-boot
1、Spring
Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
1.1、Spring如何简化Java开发
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
-
基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
-
通过IOC,依赖注入(DI)和面向接口实现松耦合;
-
基于切面(AOP)和惯例进行声明式编程;
-
通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;
1.2、什么是SpringBoot
就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。
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整合了所有的框架 。
1.3、Spring Boot主要优点:
- 为所有Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
2、微服务
2.1、什么是微服务
微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合;可以通过http的方式进行互通,要说微服务架构,先得说说过去的单体应用架构。
2.2、单体应用架构
所谓单体应用架构(all in one)是指,我们将一个应用中的所有应用服务都封装在一个应用中。
无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放在一个war包内。
- 这么做的好处是,易于开发和测试;也十分方便部署;当需要扩展时,只需把war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
- 单体应用架构缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能把所有内容都放在一个应用里,我们如何维护、如何分工合作都是问题。
2.3、微服务架构
all in one 的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。
所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。
这样做的好处 :
- 节省了调用资源
- 每个功能元素的服务都是一个可替换的、可独立升级的软件代码。
2.4、如何构建微服务架构
一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过http相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。
但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套、全程产品:
- 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用
- 大型分布式网络服务的调用,这部分由spring cloud来完成,实现分布式
- 在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow。
- spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案。
3、第一个SpringBoot程序
配置需要:
- jdk1.8
- maven
- springboot:最新版
- IDEA
官方:提供了一个快速生成的网站! IDEA集成了这个网站!
- 可以在官网下载后,导入idea开发
- 直接使用idea创建一个springboot项目(一般都是这个)
项目创建方式一
使用Spring Initializr 的 Web页面创建项目
2、填写项目信息
3、点击”Generate Project“按钮生成项目;下载此项目
4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
项目创建方式二
使用 IDEA 直接创建项目
1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可)
5、填写项目路径
6、等待项目构建成功
pom.xml分析
<?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.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lc</groupId>
<artifactId>helloworld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>helloworld</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web依赖:tomcat,dispatcherServlet,xml-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-start所有的springboot依赖都是使用这个开头-->
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!--打jar包插件-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
</plugins>
</build>
</project>
如上所示,主要有四个部分:
- 项目元数据信息:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括:groupId、artifactId、version、name、description等
- parent:继承
spring-boot-starter-parent
的依赖管理,控制版本与打包等内容 - dependencies :项目具体依赖,这里包含了
spring-boot-starter-web
用于实现HTTP接口(该依赖中包含了Sring MVC),官网对它的描述是:使用 Spring MVC 构建Web(包括RESTFul)应用程序的入门者,使用Tomcat作为默认嵌入式容器;spring-boot-starter-test
用于编写单元测试的依赖包。更多功能模块的使用我们将在后面展开。 - build :构建配置部分。 默认使用了
spring-boot-maven-plugin
,配合spring-boot-starter-parent
就可以把 Spring Boot 应用打包成 Jar 直接运行。
创建完springboot 遇到问题及解决方案:
创建完springboot 项目之后的情景:
不能运行,其问题是没有导入 maven依赖 并且idea右边也没有出现 maven 窗口
解决方案:右键点击 pom.xml 文件,然后再点击 Add as Maven Project
。
最后,在pom.xml 重新加载maven依赖就好了。 或者 ctrl+shift+o
原因分析:SpringBoot使用了3.0或者3.0以上,因为Spring官方发布从Spring6以及SprinBoot3.0开始最低支持JDK17,所以仅需将SpringBoot版本降低为3.0以下即可。
Spring Boot 报错:Web server failed to start. Port 8080 was already in use.
-
使用cmd命令查看端口号占用情况,例如查看端口 8014,可以看出进程号为10728;
查询语句:
netstat -ano | findstr 端口号
可以在任务管理器的详细信息中找到 端口号,结束进程
-
使用命令关闭
命令:
taskkill -PID 进程号 -F
-
修改配置文件,加上参数:server.port=8014
或者:
server: port: 8014
错误的类文件: /D:/Mysql/IntelliJ IDEA 2020.2.3/Environment/.m2/repository/org/springframework/spring-web/6.0.6/spring-web-6.0.6.jar!/org/springframework/web/bind/annotation/RequestMapping.class
原因:我的 pom.xml
的依赖导入了,所以把它删除就行了。
<!-- <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.6</version>
</dependency>-->
3.1、编写http接口
在主程序的同级目录下,新建一个controller包
在包中新建一个HelloController类:
@Controller
@RequestMapping
public class HelloController {
//接口:http://localhost:8080/hello
@GetMapping("/hello")
@ResponseBody
public String hello(){
//调用业务,接收前端参数
return "hello,world";
}
}
编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!
3.2、修改端口号以及 主程序启动弹出的图形
-
端口号
在
application.properties
核心配置文件中修改#核心配置文件 #更改端口号 server.port=8081
-
图形
在 resources包新建 banner.txt file文件,图形寻找地址如下:
简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。所以我们常用它来建立我们的微服务项目!
4、原理初探
自动配置
4.1、pom.xml:
- springboot-dependencies:核心依赖在父工程中
- 我们在写或者引入一些springboot依赖的时候,不需要指定版本,就因为有这些版本仓库
4.2、启动器:
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
-
启动器:说白了就是Springboot 的启动场景;
-
比如说spring-boot-starter-web ,它就会帮我们自动导入web环境的所有依赖!
-
springboot会将所有的功能场景,都变成一个个的启动器
-
我们要使用什么功能,就只需要找到对应的启动器就行了,
starter
4.3、主程序:
//程序的主入口
//@SpringBootApplication:标注这个类是一个springboot的应用
@SpringBootApplication
public class Helloworld1Application {
public static void main(String[] args) {
//SpringApplication 将springboot的应用启动
SpringApplication.run(Helloworld1Application.class, args);
}
}
注解:
1.@SpringBootConfiguration: springboot的配置
@Configuration: spring配置类
@Component: 说明着也是spring的组件
1.@EnableAutoConfiguration: 自动配置
.1@AutoConfigurationPackage: 自动配置包
@Import({Registrar.class}):导入选择器 `包注册`
.1@Import({AutoConfigurationImportSelector.class}):自动导入包的核心
AutoConfigurationImportSelector:自动导入选择器(选择什么)
.2getAutoConfigurationEntry():获得自动配置的实体
.2getCandidateConfigurations():获取候选的配置
//标注了EnableAutoConfiguration注解的类
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
.2 loadFactoryNames()
//获取所有的加载配置
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
.2 loadSpringFactories()
//项目资源
classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
"META-INF/spring.factories" //从这里获取配置文件
spring-boot-test-autoconfigure-2.7.8.jar
META-INF
spring.factories 所有的自动配置类都在这里
.3 从这些资源中遍历了所有的 nextElement(自动配置),遍历完成之后,封装为Properties 为我们使用
//获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
思考:为什么自动配置为什么有的没有生效
需要导入对应的 start才能起作用
核心注解: @ConditionalOn XXX:如果这里面的条件都满足,才会生效
获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories :核心配置无
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有资源加载到配置类中!
注解 :
@SpringBootApplication : 标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
- @@SpringBootConfiguration : Spring配置类,标注在某个类上,表示这是一个SpringBoot的配置类;
- @Configuration : 说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
- @Component : 说明启动类本身也是Spring中的一个组件而已,负责启动应用!
- @ComponentScan : 这个注解在Spring中很重要 ,它对应XML配置中的元素,扫码当前主启动类同级的包。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
@EnableAutoConfiguration : 开启自动配置功能
AutoConfigurationPackage : 自动配置包
@Import({Registrar.class}) : 自动配置 ‘包组件’
Spring底层注解@import , 给容器中导入一个组件
Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
@Import({AutoConfigurationImportSelector.class}) : 给容器导入组件
AutoConfigurationImportSelector :自动配置导入选择器
结论:springboot所有的自动配置都是在启动的时候扫描加载:spring.factories
所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的 start,就有对应的启动器了,有了启动器,我们的自动配置才会生效,然后配置成功!
- springboot在启动的时候,从类路径下 META-INF/spring.factories 获取指定的值;
- 将这些自动装配的类导入容器,自动配置就会生效,帮我们进行自动装配!
- 以前我们需要自动配置的东西,现在springboot帮我们做了!
- 整合JavaEE,解决方案和自动配置的东西都在
spring-boot-test-autoconfigure-2.7.8.jar
这个包下 - 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
- 容器中也会存在非常多的 XXXautoconfigure文件,就是这些类给容器中导入了这个场景所需要的所有组件;并自动配置
- 有了自动配置类,就面取了我们编写配置文件的工作。
SpringApplication这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
JavaConfig @Configuration @Bean
Docker:进程
关于Springboot,谈谈你的理解:
- 自动装配(如何加载的)
- run()
- 推断应用的类型是普通的项目还是Web项目
- 推断并设置main方法的定义类,找到运行的主类
- run方法里面的监听器(全局存在的),能够获取上下文处理一些Bean
5、yaml语法
配置文件
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
- application.properties
-
- 语法结构 :key=value
- application.yml
-
- 语法结构 :key:空格 value
配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
server.port=8081
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
#对空格的要求很高
#注入到我们的配置类中!
server:
port: 8081
#普通的key-value
name: Jan
#对象
name: Jan
age: 18
#行内写法
student: {name: Jan,age: 18}
#数组
pets:
- cat
- dog
- pig
pets: [cat,dog,pig]
5.1、yaml注入配置文件
编写一个实体类 Dog,原来使用@Value给bean注入属性值 :
@Component
public class Dog {
@Value("旺财")
private String name;
@Value("3")
private Integer age;
//有参无参,get ,set方法
}
在SpringBoot的测试类下注入狗狗输出一下 :
@SpringBootTest
class Springboot02ConfigApplicationTests {
@Autowired //将狗狗自动注入进来
private Dog dog;
@Test
void contextLoads() {
System.out.println(dog);//打印一下狗狗对象
}
}
结果成功输出,value 成功注入 Dog{name='旺财', age=3}
编写一个复杂的实体类:Person类:
@Component //注册到容器中
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@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;
//有参,无参,get,set ,tostring()方法
}
在yaml配置完成注入:
person:
name: Jan
age: 18
happy: false
birth: 2023/4/4
maps: {k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: 旺财
age: 3
IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
指定配置文件
先调配置
实体类:
@Component
//@ConfigurationProperties(prefix = "person")
//指定加载配置文件
@PropertySource(value = "classpath:Jan.properties")
public class Person {
@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;
}
Jan.properties
name=Jan
5.2、配置文件占位符
配置文件还可以编写占位符生成随机数
person:
name: Jan${random.uuid}
age: ${random.int}
happy: false
hello: happyy
birth: 2023/4/4
maps: {k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: ${person.hello:hello}_旺财
age: 3
name: ${person.hello:hello}_旺财
如果配置文件的 person 中没有 hello 这个类,则默认选择值为hello ,若有就显示hello: 里的值,这里的例子的值就是 happyy。
结论
配置yml和配置properties都可以获取到值 , 强烈推荐 yaml;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
5.3、JSR303校验
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。这里写个注解让我们的name只能支持Email格式;
添加依赖 :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Component
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message = "格式错误")
private String name;
}
运行结果 :default message [不是一个合法的电子邮件地址]:
使用数据校验,可以保证数据的正确性:
常见参数:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
5.4、多环境装配
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境
官方外部配置文件说明参考文档:
- file:./config/
- file:./
- classpath:./config/
- classpath:/
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
多配置文件:
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件。
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;spring.profiles.active=dev
yaml的多文件配置:
server:
port: 8081
spring:
profiles:
active: test
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
拓展,运维小技巧:
指定位置加载配置文件
我们还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
6、自动配置原理
分析自动配置原理:(ctrl +f 搜索)
我们以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启动会加载大量的自动配置类
- 我们看我们需要的功能有没有在Springboot默认写号的自动配置类当中
- 我们再来看这个自动配置类中到底配置; 哪些组件(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解:@Conditional :
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类debug=true
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
懒人笔记压缩(步骤):主打一个“找”字
yaml
spring:
activemq:
broker-url: //按住CTRL 键,点击broker-url
点击进去,鼠标往上滑动
然后在自己 External Liberaries
找到自己对应的 spring-boot-autoconfigure-xxx.jar
下的 spring.factories
然后在里面搜索
掌握原理,天下皆在我手!
7、SpringBoot Web开发
使用SpringBoot的步骤:
-
创建Springboot应用,选择我们需要的模块,Springboot就会默认我们需要的模块自动配置好
-
手动在配置文件中配置部分配置项目就可以运行起来了
-
专注编写业务代码,不需要考虑以前那样一大堆的配置
要熟悉掌握开发,之前学习的自动配置的原理一定要搞明白
比如Springboot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?
- XXXAutoConfiguration.. 向容器中自动配置组件
- XXXProperties: 自动配置类,装配配置文件中自定义的一些内容!
要解决的问题:
- 导入静态资源
- 首页
- jsp,模板引擎Thymeleaf
- 装配扩展SpringMVc
- 增删改查
- 拦截器
- 国际化
7.1、静态资源
学会如何看源码(ctrl +shift + -或者+ 收缩或者扩展)
我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
有一个方法:addResourceHandlers 添加资源处理
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
//已禁用默认资源处理
logger.debug("Default resource handling disabled");
return;
}
//webjars 配置
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/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;
第一种方式:
添加依赖
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
启动项目,然后在浏览器输入 localhost:8080/webjars/jquery/3.4.1/jquery.js
第二种方式:静态资源映射
先分析源代码,点击 getStaticPathPattern()
,追溯到 private String staticPathPattern = "/**";
,说明访问当前任意的资源,它回去寻找resourceProperties这个类(WebProperties .java),我们点进去分析:
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
};
即它会去寻找资源文件夹,即上面数组的内容。
我们可以在resources根目录下创建新的对应的文件夹,来存放我们的静态文件:
然后访问 localhost:8080/1.js ,它就会去这些文件夹中寻找对应的静态资源文件;
优先级:resources > static > public
7.2、首页定制
继续向下看源码, 可以看欢迎页的映射,就是我们说的首页!
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
继续往下看
private Resource getIndexHtml(Resource location) {
try {
Resource resource =
// 欢迎页就是一个location下的的 index.html 而已
location.createRelative("index.html");
if (resource.exists() && (resource.getURL() != null)) {
return resource;
}
}
catch (Exception ex) {
}
return null;
}
欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。
新建一个index.html,在我们上面的3个目录(resources ,static , public)中任意一个,然后访问 localhost:8080
7.3、thymeleaf模板引擎
前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。
jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,它现在默认是不支持jsp的。
那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦。
引入thymeleaf依赖
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf 学习文档:Tutorial: Using Thymeleaf
Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
Spring官方文档:找到我们对应的版本
https://docs.spring.io/spring-boot/docs/2.7.8/reference/htmlsingle/#using-boot-starter
找到对应的pom依赖:可以适当点进源码看下本来的包!
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Maven会自动下载jar包,我们可以去看下下载的东西;
引入了依赖包后,该如何使用?看源码
我们去找一下Thymeleaf的自动配置类:ThymeleafProperties
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
}
源码说道,我们只需要将我们的html页面放在classpath:/templates目录下,thymeleaf就可以帮我们自动渲染了。在templates里创建一个html文件即可。
Thymeleaf语法学习
修改测试代码,增加数据传输:
//在templates目录下的所有页面,只能通过controller来跳转!
//这个需要模板引擎的支持! thymeleaf
//新版本得 RestController才行
@Controller
public class IndexController {
@RequestMapping("/test")
public String test(Model model){
//存入数据
model.addAttribute("msg","hello,springboot");
//classpath:/templates/test.html
return "test";
}
要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。可以去官方文档的3.1里看一下命名空间拿来过来:
xmlns:th="http://www.thymeleaf.org"
然后再去编写自己创建的html文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有的html元素都可以被thymeleaf接管: th:元素名-->
<div th:text="${msg}"></div>
</body>
</html>
问题:"${msg}"爆红
解决:File->settings->Editor->Inspections->Thymeleaf->Expression variables validation取消勾选
测试:
官网对应的属性值:
测试:
在原有的代码上加入一些数组
@Controller
public class IndexController {
@RequestMapping("/test")
public String test(Model model){
model.addAttribute("msg","<h1>hello,springboot</h1>");
model.addAttribute("users", Arrays.asList("jan","xiao"));
return "test";
}
}
测试页面取出数据
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有的html元素都可以被thymeleaf接管: th:元素名-->
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
<hr>
<h3 th:each="user:${users}" th:text="${user}"></h3>
<!--<h3 th:each="user:${users}" >[[${user}]]</h3>-->
</body>
</html>
启动项目测试:
7.4、MVC自动配置
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!
地址:Spring Boot Reference Documentation
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 (covered later in this document).
//自动注册了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.
//初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
//想自己定义自己的Spring MVCConfig 不能用 @EnableWebMvc
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
(文档说的步骤:Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
)
我们找到 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 uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
然后点击进入 ContentNegotiatingViewResolver
这个类看看!看一些核心的代码,它继承了 ViewResolver
(视图解析接口)
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
//...
}
点击进入 ViewResolver
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
resolveViewName
点击不能进去,那我们就在 ContentNegotiatingViewResolver
里搜索它;
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
//...
}
然后继续点进去看,它时如何获得候选的视图的?
getCandidateViews中看到它是把所有的视图解析器拿来
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
使用for循环,遍历所有的视图解析器,逐个解析!
得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
7.5、扩展SpringMVC
自定义一个自己的视图解析器MyViewResolver
//如果,你向自定义一些定制化的功能,只要写这个组件,然后将它交给springboot
//扩展springmvc dispatchservlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer{
//ViewResolver 实现了试图解析器接口类,我们就可以把它看作视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
那么自定义的视图解析器有没有生效呢?我们可让它测一下。
所有的请求会经过 DispatcherServlet
,然后我们去搜一下,DispatcherServlet
里面有个方法 doDispatch
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
//....
try {
doDispatch(request, response);
}
//...
}
在 doService
这个类打个断点,点击Debug按钮,然后再访问网站
总之:如果,你向自定义一些定制化的功能,只要写这个组件,然后将它交给springboot。
Spring MVC Auto-configuration(源码):
- Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.
查找 ``Formatter
:从
WebMvcAutoConfiguration` 找
@Bean
@Override
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
addFormatters(conversionService);
return conversionService;
}
再进去点击 getFormat
public Format getFormat() {
return this.format;
}
再点击 format
private final Format format = new Format();
使用: Format format = this.mvcProperties.getFormat();
在Properties里 输入spring.mvc.format.date
-----> dd/MM/yyyy,新版的有 spring.mvc.format.date-time
---->yyyy-MM-dd HH:mm:ss
修改SpringBoot的默认配置
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们在resources下新建一个包叫config,写一个类MyMvcConfig;
//如果我们要扩展SpringMVC,官方建议我们这样去做!
@Configuration
//@EnableWebMvc //这个玩意就是导入一个类: DelegatingWebMvcConfiguration: 从容器中获取所有的WebMvcconfig;
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/Jan").setViewName("test");
}
分析原理:
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
得出结论:所有的WebMvcConfiguration都会被调用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
保留Mvc自定义与全面接管Mvc的区别:
- If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.
分析:其中的but without @EnableWebMvc
.
点击 EnableWebMvc
,导入了一个类: DelegatingWebMvcConfiguration :从容器中获取所有的Webmvcconfig;
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
其中extends WebMvcConfigurationSupport
与 WebMvcAutoConfiguration 的 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 相违背--->意思就是没有这个WebMvcConfigurationSupport类,WebMvcAutoConfiguration 自动配置才会生效。
全面接管SpringMVC
官方文档:
If you want to take complete control of Spring MVCyou can add your own @Configuration annotated with @EnableWebMvc.
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
@EnableWebMvc 就是导入一个类DelegatingWebMvcCondfiguration:从容器中获取所有的webmvcconfig;
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;
当然,我们开发中,不推荐使用全面接管SpringMVC
总结:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
在Springboot中,有非常多的 xxx Configuration 帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!
8、员工管理系统
素材的下载:https://sc.chinaz.com
处理常见的HTTP请求类型
@RequestMapping("/foo") is equivalent to @RequestMapping(path="/foo")
GetMapping其中一注释
/**
* Alias for {@link RequestMapping#path}.
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
5种常见的请求类型:
- GET :请求从服务器获取特定资源。例如:
GET /users
(获取所有学生) - POST :在服务器上创建一个新的资源。例如:
POST /users
(创建学生) - PUT :更新服务器上的资源(客户端提供更新后的整个资源)。例如:
PUT /users/12
(更新编号为 12 的学生) - DELETE :从服务器删除特定的资源。例如:
DELETE /users/12
(删除编号为 12 的学生) - PATCH :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。
@PathVariable 注解,其用来获取请求路径(url )中的动态参数。
/**
* @RequestMapping(value = "user/login/{id}/{name}/{status}") 中的 {id}/{name}/{status}
* 与 @PathVariable int id、@PathVariable String name、@PathVariable boolean status
* 一一对应,按名匹配。
*/
8.1、准备工作
将下载好的素材解压,将 assets里的文件夹导入到 satic
里,其余的html导入到 templates
里。
然后创建一些实体类:
Department 实体类
package com.jan.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
DepartmentDao
//部门dao
@Repository
public class DepartmentDao {
//模拟数据库的数据
private static Map<Integer, Department> departments = null;
static {
departments = new HashMap<Integer, Department>(); //创建一个表
//初始数据
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"教研部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
//获取部门信息
public Collection<Department> getDepartments(){
return departments.values();
}
//通过id获取部门
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
Employee
package com.jan.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
//员工表
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //0:女 1:男
private Department department;
private Date birth;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
//默认的创建日期
this.birth = new Date();
}
}
EmployeeDao
//员工Dao
@Repository
public class EmployeeDao {
//模拟数据库的数据
private static Map<Integer, Employee> employees = null;
//员工有所属的部门
@Autowired
private DepartmentDao departmentDao;
static {
//创建一个表
employees = new HashMap<Integer,Employee>();
//初始数据
employees.put(1001,new Employee(1001,"AA","123456@qq.com",0,new Department(101,"教学部")));
employees.put(1002,new Employee(1002,"BB","123456@qq.com",1,new Department(102,"市场部")));
employees.put(1003,new Employee(1003,"CC","123456@qq.com",0,new Department(103,"教研部")));
employees.put(1004,new Employee(1004,"DD","123456@qq.com",1,new Department(104,"运营部")));
employees.put(1005,new Employee(1005,"EE","123456@qq.com",0,new Department(105,"后勤部")));
}
//主键自增!
private static Integer initId = 1006;
//增加一个员工
public void save(Employee employee){
if(employee.getId()== null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询全部员工信息
public Collection<Employee> getAll(){
return employees.values();
}
//通过Id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
//删除员工
public void deleteById(Integer id){
employees.remove(id);
}
}
首页实现:
导入相应的文件:
如何使用:
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
然后在浏览器输入:http://localhost:8081/ ,运行成功。
properties的一些配置:
#关闭模板引擎的缓存
spring.thymeleaf.cache=false
server.port=8081
server.servlet.context-path=/Jan
此事,输入 http://localhost:8081/Jan即可!
注意:所有的页面的静态资源都需要使用thymeleaf接管; @{}
8.2、页面国际化
有的时候,我们的网站会去涉及中英文甚至多语言的切换,这时候我们就需要学习国际化了!
先在IDEA中统一设置properties的编码问题!
配置文件的编写:
我们在resources资源文件下新建一个i18n目录,存放国际化配置文件
然后再里面创建一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作;文件夹变了!
我们可以在这上面去新建一个文件;
然后弹出,添加英文的;
这样就快捷多了!
接下来,编写可视化配置:
在这个视图我们点击+ 号直接添加属性;
我们新建一个login.tip,可以看到边上有三个文件框可以输入
添加完首页的内容!
我们真实 的情况是放在了i18n目录下,所以我们要去application.properties配置这个messages的路径;
#我们的配置文件放在的真实位置
spring.messages.basename=i18n.login
现在我们要去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。 下面是官方文档的简单语句:#4
- Variable Expressions:
${...}
- Selection Variable Expressions:
*{...}
- Message Expressions:
#{...}
- Link URL Expressions:
@{...}
- Fragment Expressions:
~{...}
启动项目,访问一下,发现页面已经自动识别为中文的了!
假如现在我们要实现中英文页面的自由切换,需要让我们的国际化资源生效,就要让我们自己的Locle生效!
我们在WebMvcAutoConfiguration.java
里找到 localeResolver
,看到new 了一个 AcceptHeaderLocaleResolver
(Spring Boot Reference Documentation ,#8.1.1 的 Automatic registration of MessageCodesResolver (covered later in this document). )
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
再点击去看一下源码,它访问了 LocaleResolver 接口
public class AcceptHeaderLocaleResolver implements LocaleResolver {
//....
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
}
从此来看,我们也可以自己写一个自己的国际化资源,可以在链接上携带区域信息!
修改一下前端页面的跳转连接:
<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')}">English</a>
我们去写一个处理的组件类!
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取语言参数链接
String language = request.getParameter("l");
System.out.println("Debug====>"+language);
Locale locale = Locale.getDefault();//如果没有就使用默认的
//如果请求的链接携带了国际化的参数
if(!StringUtils.isEmpty(language)){
//zh_CN
String[] split = language.split("_");
//国家,地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MyMvcConfig下添加bean;
//自定义的国际化组件生效了
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
重启项目,来访问一下,发现点击按钮可以实现成功切换!
总结:
- 我们需要配置i18n文件
- 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleResolver
- 记得将自己写的组件配置到spring容器@Bean
- {}
8.3、登录功能的实现
在编写项目是,不要想一步就直接能完成项目,应完成一小项就必须要进行测试,进而能做到有错误及时修改
在 LoginController 中编写登陆方法
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
//Model model 回显数据
Model model){
//具体的业务:
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//return "dashboard";
//重映像 ,之前登录进去 /user/login?username=232323&password=123456 修改之后是 /Jan/main.html
return "redirect:/main.html";
}else{
//告诉用户,你登录失败了!
model.addAttribute("msg","用户名或者密码错误!");
return "index";
}
}
}
在MyMvcConfig中添加跳转视图控制 :
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//添加视图控制
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
//自定义的国际化组件生效了
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
在templates中编写index.html :
运行,输入任意账号和123456密码成功登陆:
8.4、登录拦截器
新建LoginHandlerInterceptor继承HandlerInterceptor方法实现拦截器 :
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登陆成功后,应该有用户的session
Object loginUser = request.getSession().getAttribute("loginUser");
if(loginUser == null){ //没有登陆
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
return true;
}
}
在 LoginController 中有些修改:
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
//Model model 回显数据
Model model, HttpSession session){
//具体的业务:
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//return "dashboard";
//重映像 ,之前登录进去 /user/login?username=232323&password=123456 修改之后是 /Jan/main.htm
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}else{
//告诉用户,你登录失败了!
model.addAttribute("msg","用户名或者密码错误!");
return "index";
}
}
在MyMvcConfig中添加自定义拦截器:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).
addPathPatterns("/**").
excludePathPatterns("/","/index.html","/user/login","/css/**","/js/**","/img/**");
}
直接访问main.html拦截成功,转入登录页面 :
8.5、展示员工列表
在EmployeeController中写入员工列表代码 ,传入员工列表:
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";
}
}
这里的list.html放在 templates下新建的emp里
在原来的dashboard.html 里改 customers(带有部分修改)
</li>
<li class="nav-item">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{emps}">
员工管理
</a>
</li>
公共部分:
th:fragment="sidebar"
orth:fragment="topbar"
ht:replace="~{commons/commons::topbar}"
- 如果要传递参数,可以直接用()传参,接收判断即可!
在这里要明白。无论是首页还是员工管理,这些都有公共的部分,比如侧边栏和头部导航栏,把它们抽取出来
在templates在创建一个commons包,再在里头创建commons.html
已修改的代码都用回车空出来了
<!--头部导航栏-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">注销</a>
</li>
</ul>
</nav>
<!--侧边栏-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
首页 <span class="sr-only">(current)</span>
</a>
</li>
高亮:
把公共部分取出来后,原来的list.html 与dashboard.html关于头部导航栏和侧边栏的代码也要修改
list.html
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
dashboard.html
<!--头部导航栏-->
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<!--侧边栏-->
<!--传递参数给组件-->
<div th:replace="~{commons/commons::sidebar(active ='main.html')}"></div>
头部导航栏和侧边栏都调试好后,我们要解决高亮的问题,点谁谁亮
dashboard.html
<!--侧边栏-->
<!--传递参数给组件-->
<div th:replace="~{commons/commons::sidebar(active ='main.html')}"></div>
commons.html
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
首页 <span class="sr-only">(current)</span>
</a>
<li class="nav-item">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{emps}">
员工管理
</a>
</li>
list.html
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
员工管理的数据显示问题:
<body>
<div th:replace="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2>Section title</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td >[[${emp.getLastName()}]]</td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.department.getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
19-date (Tutorial: Using Thymeleaf)
/*
* Format date with the specified pattern
* Also works with arrays, lists or sets
*/
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
8.6、增加员工实现
添加员工:
- 按钮提交
- 跳转到添加页面
- 添加员工成功
- 返回首页
添加员工 的按钮
在 list.html
里
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2> <a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a> </h2>
全局 CSS 样式 · Bootstrap v3 中文文档 | Bootstrap 中文网 (bootcss.com)
点击添加员工按钮后 跳转到添加页面
添加员工的html的编写:复制 list.html
,给名为 add 放在同一个文件夹 emp 里,然后 main标签里面的东西全删掉,修改为下面的代码
<!--department 那的是 @GetMapping("/emp")
而我们表单的提交都是 method="post" -->
<form th:action="@{emp}" method="post">
<div class="form-group">
<label>LastName</label>
<!--name="lastName" 因为最后实体类要传参,封装成Employee,要一一对应-->
<input type="text" name="lastName" class="form-control" placeholder="Jan">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" placeholder="1075954259@qq.com">
</div>
<div class="form-group">
<label>Gender</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<!--department是对象,应该取里面的属性 -->
<select class="form-control" name="department.id">
<!--dept是个对象,然后要拿出它的名字-->
<!--用户提交上来的是部门的Id-
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));-->
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" name="birth" class="form-control" placeholder="Jan">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
EmployeeController
//在添加信息中的数字 获取部门信息
@GetMapping("/emp")
//Model model 返回前端,传递参数
public String toAddpage(Model model){
//add.html 的12345需要找到相对于的数据
//查出所有部门的信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}
修改添加页面的 department 是数字而不是 相应的部门的问题:
<select class="form-control" name="department.id">
<!--dept是个对象,然后要拿出它的名字-->
<!--用户提交上来的是部门的Id-
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));-->
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
th:value="${dept.getId()}"></option>
</select>
最后是点击添加按钮所来带的问题:点击之后应该提交表单信息 ;然后是添加成功后返回到首页
<!--department 那的是 @GetMapping("/emp")
而我们表单的提交都是 method="post" -->
<form th:action="@{emp}" method="post">
<div class="form-group">
<label>LastName</label>
//提交表单
@PostMapping("/emp")
public String addEmp(Employee employee){
//添加的操作 forward
System.out.println("save==>"+employee);
employeeDao.save(employee);//调用底层业务方法保存员工信息
return "redirect:/emps";
}
最后的一点小细节,Birth的输入方式问题 :电脑默认的是 yyyy/MM/dd,若想改为输入方式为:yyyy-MM-dd
在 application.properties
里添加 spring.mvc.format.date=yyyy-MM-dd
。
8.7、修改员工实现
大致思想:(2以后都是边做边发现的问题)
- 点击编辑按钮实现页面跳转到修改界面
- 实现数据的传带(就是修改界面的数据)
- 男女的问题
- 时间的问题
8.7.1、编辑按钮的跳转
先在EmployeeController
编写代码
//编辑功能
//去员工修改页面
@GetMapping("/emp/{id}") //链接形式都是GetMapping
public String toUpdateEmp(@PathVariable("id")Integer id,Model model){
//查出原来的数据
Employee employee = employeeDao.getEmployeeById(id);
//查出来的信息要返回前端
model.addAttribute("emp",employee);
return "emp/update";
}
点击按钮会跳转
结果:(原有的数据没有携带)
8.7.2、修改页面数据携带
测试结果:
做进一步的修改:
添加了:查出所有部门信息
EmployeeController
//编辑功能
//去员工修改页面
@GetMapping("/emp/{id}") //链接形式都是GetMapping
public String toUpdateEmp(@PathVariable("id")Integer id,Model model){
//查出原来的数据
Employee employee = employeeDao.getEmployeeById(id);
//查出来的信息要返回前端
model.addAttribute("emp",employee);
//查出所有部门的信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/update";
}
邮箱、性别以及生日是数据携带
结果:
8.7.3、修改后的提交表单(跟上面的提交表单没有什么区别)
EmployeeController
//修改后的提交表单(跟上面的提交表单没有什么区别)
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDao.save(employee);
return "redirect:/emps";
}
修改 update.html
日期修改
<div class="form-group">
<label>Birth</label>
<input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}" type="text" name="birth" class="form-control" placeholder="Jan">
</div>
<button type="submit" class="btn btn-primary">修改</button>
8.7.4、解决点击修改按钮后自动新增员工信息
从图中明显看到,有两个AA的名称
修改:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<!--department 那的是 @GetMapping("/emp")
而我们表单的提交都是 method="post" -->
<form th:action="@{/updateEmp}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label>LastName</label>
8.8、删除
EmployeeController
//删除员工
@GetMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id")Integer id){
employeeDao.deleteById(id);
return "redirect:/emps";
8.9、404
在templates文件夹里创建一个error文件夹
8.10、注销
LoginController
//注销
@RequestMapping("/user/logout")
public String logout(HttpSession session){
session.invalidate();
return "redirect:/index.html";
}
9、Springboot整合JDBC
9.1、Spring Data
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
数据库相关启动器:Spring Boot Reference Documentation
Spring Date 官网:Spring Data JDBC
9.2、整合JDBC
新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块
项目建好之后,发现自动帮我们导入了如下的启动器:
<!--JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--MySQL-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
编写yaml配置文件连接数据:
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 8081
配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下:
@SpringBootTest
class Springboot05DataApplicationTests {
//DI注入数据
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源 :com.zaxxer.hikari.HikariDataSource DataSourceProperties
System.out.println(dataSource.getClass());
//获得数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//xxx Template: SpringBoot已经配置好模板bean,拿来即用 CRUD
//jdbc //redis
//关闭
connection.close();
}
}
结果:我们可以看到他默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置
HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;
有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate。
JDBCTemplate
即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类
JdbcTemplate主要提供以下几类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
在jan目录下创建Controller
,接着再创建JDBCController
Java类文件
//注意点
import org.springframework.jdbc.core.JdbcTemplate;
//看源码
// External Libraries springboot-autoconfigure:2.7.8 的org 的jdbc
@RestController
public class JDBCController {
@Autowired
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 (7,'BB','123456')";
jdbcTemplate.update(sql);
return "update-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]= "zzzzzz";
jdbcTemplate.update(sql,objects);
return "update-ok";
}
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id")int id){
String sql = "delete from mybatis.user where id= ?";
jdbcTemplate.update(sql,id);
return "deleteUser-ok";
}
}
测试请求,结果正常;
到此,CURD的基本操作,使用 JDBC 就搞定了。
9.3、整合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/
配置数据源
添加Druid 依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #自定义数据源
server:
port: 8081
数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;
切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
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: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
导入Log4j 的依赖:
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
现需要写一个配置类与全局变量的参数绑定,再添加到容器中,不再是srpingboot自动生成了
DruidConfig
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
//与yaml文件绑定
public DataSource druidDataSource(){
return new DruidDataSource();
}
}
测试一下,看是否成功:
@SpringBootTest
class Springboot05DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源:class com.zaxxer.hikari.HikariDataSource DataSourceProperties
System.out.println(dataSource.getClass());
//获得数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//xxx Template: SpringBoot已经配置好模板bean,拿来即用 CRUD //jdbc//redis
//关闭
connection.close();
}
}
后台Druid数据源监控
Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。
所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理;
//后台监控功能 : web.xml
//因为Springboot 内置了 servlet容器,所以没有web.xml ,替代方法: ServletRegistrationBean
@Bean
//死代码,不用记,要自己写的我会提示
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登陆,账号密码配置
HashMap<String, String> initParameters = new HashMap<>();//看setInitParameters源码写出来的
//增加配置
initParameters.put("loginUsername","admin");//前面的空是固定的 loginUsername loginPassword
initParameters.put("loginPassword","123456");
//允许谁可以访问
initParameters.put("allow",""); //此时参数为空-->所有人可以访问
//initParameters.put("kuangshen","192.168.11.1");----->禁止访问
bean.setInitParameters(initParameters);//设置初始化参数 (固定的)
return bean;
}
测试:配置完毕后,我们可以选择访问 :http://localhost:8081/druid/login.html
登陆后,再查询数据库的数据,后台监控会有记录
配置 Druid web 监控 filter 过滤器
//filter过滤器
@Bean
public FilterRegistrationBean webStatFiler(){
//构造器
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String, String> initParameters = new HashMap<>();
//中间才是要配的参数,其他的代码都是被迫写的
//这些东西不进行统计(看源码WebStatFilter)
initParameters.put("exclusions","*.js,*.css,/druid/*");
//可以过滤哪些请求呢?
bean.setInitParameters(initParameters);
return bean;
}
平时在工作中,按需求进行配置即可,主要用作监控!
9.4、整合Mybatis
官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot
创建项目所需要勾选的依赖:
整合测试
导入 MyBatis 所需要的依赖
<!-- 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.3.0</version>
</dependency>
其中这里需要注意的是:导入mybatis-spring-boot-starter依赖需与自己的springboot版本相对应
配置数据库连接信息(不变)
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
先进行简单的数据测试,看数据库是否连接成功
@SpringBootTest
class Springboot06MybatisApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException { //基本测试代码
//查看默认的数据源
System.out.println(dataSource.getClass());
//获得数据库连接
System.out.println(dataSource.getConnection());
}
}
测试结果:连接成功
Mybatis中文文档:mybatis – MyBatis 3 | 入门
创建实体类,导入Lombok!
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
创建mapper目录以及对应的 UserMapper 接口
//这个注解表示了,这是一个 Mybatis 的 Mapper 类: Dao
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
对应的Mapper映射文件 (在Mybatis Mapper.xml文件是建在同级目录在的)
UserMapper.xml,建在resources/mybatis/mapper下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jan.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>
在配置文件application.yml中整合Mybatis :
#整合mybatis
mybatis:
type-aliases-package: com.jan.pojo
#classpath:mybatis/mapper/*.xml
mapper-locations: classpath:mapper/*.xml
#核心配置文件 config-location:
创建controller包,再创建UserController
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/queryUserList")
public List<User> queryUserList(){
List<User> userList = userMapper.queryUserList();
for (User user : userList) {
System.out.println(user);
}
return userList;
}
//添加一个用户
@GetMapping("/addUser")
public String addUser(){
userMapper.addUser(new User(6,"阿朵","67890"));
return "ok";
}
//更新
@GetMapping("/updateUser")
public String updateUser(){
userMapper.updateUser(new User(6,"阿朵","12678"));
return "ok";
}
//删除
@GetMapping("/deleteUser")
public String deleteUser(){
userMapper.deleteUser(6);
return "ok";
}
}
启动项目进行测试!!
10、SpringSecurity(安全)
在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。
市面上存在比较有名的:Shiro,Spring Security !
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。
依赖导入:创建Maven时勾选上springweb服务
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
实战测试
新建一个初始的springboot项目web模块,thymeleaf模块
导入静态资源
controller实现跳转 :
package com.jan.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
public class RouterController {
@GetMapping({"/","/index"})
public String index(){
return "index";
}
@GetMapping("/tologin")
public String tologin(){
return "views/login";
}
@GetMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@GetMapping("/level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/"+id;
}
@GetMapping("/level3/{id}")
public String level3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
找文档:(https://docs.xxx/site/docs/
)Index of /spring-security/site/docs
认识Spring Security
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
认证和授权
目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能
引入 Spring Security 模块
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
编写 Spring Security 配置类
对应的版文档,对着学习:Spring Security Reference #15
The custom DSL can then be used like this:
@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.apply(customDsl())
.flag(true)
.and()
...;
}
}
编写基础配置类
选择-configure(hhtp:HTTP Security):viod
固定的架子
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
看configure 源码写
定制请求的授权规则
//AOP : 拦截器!
@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("/level3/**").hasRole("vip3");
}
}
测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!
在configure()方法中加入以下配置,开启自动配置的登录功能!
//没有权限默认会到登陆页面
http.formLogin();
测试一下:发现,没有权限的时候,会跳转到登录的页面!
查看刚才登录页的注释信息;
我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法
//AOP : 拦截器!
@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("/level3/**").hasRole("vip3");
//没有权限默认会到登陆页面,需开启登陆的页面
//Login formLogin(看源码)
http.formLogin();
}
//认证 ,springboot 2.1.x 可以直接使用
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/* 源码
super.configure(auth); 点 configure .inMemoryAuthentication().withUser("user").password("password").roles("USER").and() .withUser("admin").password("password").roles("USER", "ADMIN");
* */
//这写数据正常从数据库取(jdbcAuthentication)
auth.inMemoryAuthentication()
.withUser("jan").password("123456").roles("vip2","vip3").and()
.withUser("root").password("123456").roles("vip1","vip2","vip3").and()
.withUser("guest").password("123456").roles("vip1");
}
}
报错:There is no PasswordEncoder mapped for the id "null"
如何查看PasswordEncoder 的方法:
在我们的继续修改java文件中, auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
点一下 passwordEncoder
来到了
@SuppressWarnings("unchecked")
public C passwordEncoder(PasswordEncoder passwordEncoder) {
this.provider.setPasswordEncoder(passwordEncoder);
return (C) this;
}
再点击 PasswordEncoder
public interface PasswordEncoder {
再点击public 旁边的图标就会显示所有的方法
原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码
//认证 ,springboot 2.1.x 可以直接使用
//密码编码:PasswordEncoder
//在Spring Security 5.0+ 新增了很多的加密方式~
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/* 源码
.inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
* */
//这写数据正常从数据库取(jdbcAuthentication)
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("jan").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3").and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3").and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定
权限控制和注销
数据库连接模板:
@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser(users.username("user").password("password").roles("USER"))
.withUser(users.username("admin").password("password").roles("USER","ADMIN"));
}
注销:
SecurityConfig
//注销 开启注销功能,跳到首页
http.logout().logoutSuccessUrl("/");
index.html
<!--登录注销-->
<div class="right menu">
<!--未登录-->
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
<!--注销-->
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
登录进去之后,显现用户名和注销按钮:
index.xml
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<!--登录注销-->
<div class="right menu">
<!--如果未登录-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<!--如果登录:用户名,注销-->
<div sec:authentication="isAuthenticated()">
<a class="item">
用户名:<span sec:authentication="name"></span>
角色:<span sec:authentication="principal.authorities"></span>
</a>
</div>
<div sec:authentication="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
</div>
</div>
SecurityConfig
//注销 开启注销功能,跳到首页
//防止网站工具: get post
http.csrf().disable();//关闭csrf攻击功能,登录失败肯定存在的原因~
http.logout().logoutSuccessUrl("/");
注意:需要修改版本号
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
登录进去之后,只显现权限页面:本来与Shiro整合thymeleaf一样
sec:authorize="hasRole('vip1')">
vip2、3也是一样
<!--菜单根据用户的角色动态的实现-->
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
记住我及首页定制
源代码 formLogin
@Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").
hasRole("USER").and().formLogin()
* .usernameParameter("username") // default is username
* .passwordParameter("password") // default is password
* .loginPage("/authentication/login") // default is /login with an HTTP get
* .failureUrl("/authentication/login?failed") // default is /login?error
* .loginProcessingUrl("/authentication/login/process"); // default is /login
* // with an HTTP
* // post
* return http.build();
* }
loginPage() 指定登录界面是哪一个 ,这里指 ‘’/toLogin"
loginProcessingUrl:指定处理登录请求的,也就是登录的form 提交的地址,这里是“/login”
登录失败:
login.html
<form th:action="@{/usr/login}" method="post">
改为 <form th:action="@{/login}" method="post">
还是404,找不到页面
<form th:action="@{/toLogin}" method="post">
其他一些问题:
login.html
<form th:action="@{/login}" method="post">
SecurityConfig
//Login formLogin(看源码)
http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
但我的表单能接收到吗?源码
.usernameParameter("username") // default is username
* .passwordParameter("password") // default is password
改变一下 name="user"
name="pwd"
,而不是原来默认的值 username password,就接收不到表单
<input type="text" placeholder="Username" name="user">
<input type="password" name="pwd">
解决办法
http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
记住我
//开启记住我功能 cookie,默认保存两周 ,自定义接收前端的参数
http.rememberMe().rememberMeParameter("remember");
<div class="field">
<input type="checkbox" name="remember">记住我
</div>
11、Shiro
什么是Shiro
- Apache Shiro是一个Java的安全(权限)框架。
- Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
- Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
- 下载地址: http://shiro.apache.org/
- GitHub:GitHub - apache/shiro: Apache Shiro
功能
- Authentication:身份认证、登录,验证用户是不是拥有相应的身份;
- Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
- Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
- Web Support: Web支持,可以非常容易的集成到Web环境;
- Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
- Concurrency: Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限自动的传播过去
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
三大核心组件
Shiro有三大核心组件,即Subject、SecurityManager和Realm
Subject(the current "user")
为认证主体。应用代码直接交互的对象是Subject,Subject代表了当前的用户。包含Principals和Credentials两个信息。
Pricipals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登陆主题的身份。
Credentials:代表凭证。常见的有密码、数字证书等等。
也就是说两者代表了认证的内容,最常见就是用户名密码了。用Shiro进行身份认证,其中就包括主体认证。
SecurityManager(manages all Subjects)
为安全管理员。是Shiro架构的核心。与Subject的所有交互都会委托给SecurityManager, Subject相当于是一个门面,而SecurityManager才是真正的执行者。它负责与Shiro 的其他组件进行交互。
Realm(access your security data)
是一个域。充当了Shiro与应用安全数据间的“桥梁”。
Shiro从Realm中获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm中获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能过进行,可以把Realm看成DataSource,即安全数据源。
内部架构
- Subject: 主体,主体可以是任何可以与应用交互的“用户”
- SecurityManager: 是Shiro的核心,所有具体的交互都需通过SecurityManager进行,它管理所有的Subject,且负责进行认证授权,会话,及缓存的管理。
- Authenticator:负责主体认证。当用户尝试登录时,该逻辑被Authenticatior执行。Authenticator知道如何与一个或多个Realm协调来存储相关的用户。从Realm中获得的数据被用来验证用户的身份来保证用户确实是他们所说的他们是谁。
- Autentication Strategy:如果不止一个Realm被配置,其会协调这些Realm来决定身份认证尝试成功或失败下的条件(比如,如果一个Realm成功,而其他的失败,是否该尝试成功?)
- Authorizer:负责在应用程序中决定用户的访问控制。它是一种最终判定用户是否被允许做某事的机制。与Authenticator相似,Authorizer也知道如何协调多个后台数据源来访问角色恶化权限信息。Authorizer使用该信息来准确度的决定用户是否被允许执行给定的动作。
- SessionManager:知道如何去创建及管理用户Session生命周期来为所有环境下的用户提供一个强健的session体验。
- SessionDAO:代表SessionManager执行Session持久化操作。允许数据存储**入到会员管理的基础之中。
- CacheManager:创建并管理其他Shiro组件使用的Cache实例声明周期。因为Shiro能访问许多后台数据源,由于身份验证、授权和会话管理,缓存在框架中一直是一流 的架构功能,用来在通过还是使用这些数据源时提高性能。
- Cryptograhy:是对企业安全框架的一个自然的补充。密码模块,shrio提高了一些常见的加密组件用于密码加密,解密等。
快速启动:https://github.com/apache/shiro/tree/main/samples/quickstart
看着quickstart 里面到底内容来创建,提示:ini要先插入插件
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
if (!currentUser.isAuthenticated())
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted
currentUser.logout();
SpringBoot 整合Shiro环境搭建
Sprigboot创建勾选spring web 就行了
导入shiro、thymeleaf 依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.10.0</version>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
环境搭建:
在 templates 创建 index.html
<h1>首页</h1>
创建 controller包 MyController类
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello,shiro");
return "index";
}
}
运行测试,环境搭建成功。
编写Shiro配置类,创建 config包 ShiroConfig类
从下往上写(死代码) 2和3——先new一个,在 return 一个
@Configuration
public class ShiroConfig {
//shiroFilterFactoryBean :3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//DefaultWebSecurityManager :2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSessionManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联 UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建 Realm 对象 ,需要自定义类:1
@Bean(name = "userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
}
config包 下 UserReal
//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
return null;
}
}
下一步就是如何使用它们:
templates创建 user包- add.html update.html
再去 MyController 写页面跳转
@GetMapping("/user/add")
public String add(){
return "user/add";
}
@GetMapping("/user/update")
public String update(){
return "user/update";
}
最后在 index.html
body>
<h1>首页</h1>
<div th:text="${msg}"></div>
<a th:href="@{user/add}">add</a>
<a th:href="@{user/update}">update</a>
</body>
测试一下,实现页面的跳转,下一步就是 shiro的安全配置。
shiro实现登录拦截
//shiroFilterFactoryBean :3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro到底内置过滤器
/*
* anon: 无需认证可以访问
* authc: 必须认证
* user: 必须拥有 记住我 功能才能使用
* perms: 拥有对某个资源的权限才能访问的
* role: 拥有某个角色权限才能访问
* */
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登陆的请求
bean.setLoginUrl("/toLogin");
return bean;
}
login.html
<body>
<h1>登陆</h1>
<hr>
<form action="">
<p>用户名: <input type="text" name="username"></p>
<p>密码: <input type="text" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
核心代码在 UserRealm 里面,但是我们调用的是 Mycontroller
用户名和密码错误的提示:
@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);//执行登录的方法,如果没有异常就OK了
return "index";
}catch (UnknownAccountException e){ //用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
login.html
<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>
测试结果:
进入了这个 doGetAuthenticationInfo
方法,说明能做一些操作
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
//用户名,密码 数据库取
String name="root";
String password="123456";
UsernamePasswordToken userToken = (UsernamePasswordToken) token;//取我们认识的Token
if (!userToken.getUsername().equals(name)){
return null; //抛出一个异常 UnknownAccountException
}
//密码认证,shiro 做
return new SimpleAuthenticationInfo("",password,"");
}
成功登入,说明已经完成了用户名、密码的认证,接下来就是权限授权。
Shiro整合Mybatis
环境搭建:
依赖导入:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
application.yml 切换druid的数据库
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
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: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
绑定(整合)Mybatis
application.properties
server.port=8081
# mybatis的配置
mybatis.type-aliases-package=com.jan.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
创建完整的流程:
创建pojo包 ,User类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
创建mapper包 ,UserMapper接口----dao与Mapper 都是接口类
@Mapper
@Repository
public interface UserMapper {
public User queryUserByName(String name);
}
在 resources 下创建mapper包 ,UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jan.mapper.UserMapper">
<select id="queryUserByName" parameterType="String" resultType="User">
select * from mybatis.user where name = #{name}
</select>
</mapper>
创建service包 ,UserService接口,UserServiceImpl类
public interface UserService {
public User queryUserByName(String name);
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
测试:
@SpringBootTest
class ShiroSpringbootApplicationTests {
@Autowired
UserServiceImpl userService;
@Test
void contextLoads() {
System.out.println(userService.queryUserByName("jan"));
}
}
查询出数据库的数据,成功。
UserRealm:从数据库中获取数据
//自定义的 UserRealm extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;//取我们认识的Token
//连接真实的数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){//没有这个人
return null;// UnknownAccountException
}
//可以加密 MD5:e10adc3949ba59abbe56e057f20f883e
// MD5盐值加密:e10adc3949ba59abbe56e057f20f883eusername
//密码认证,shiro 做 加密了
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}
我们来看密码加密的原理:
先打一个断点
在网站进行登录后,查看 this 下的 CredentialsMatcher
,发现 默认的是简单的认证
点击 UserRealm类中的AuthorizingRealm
,再点击旁边的 AuthenticatingRealm
,搜索 CredentialsMatcher
看代码
/**
* Returns the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted
* credentials with those stored in the system.
* <p/>
* <p>Unless overridden by the {@link #setCredentialsMatcher setCredentialsMatcher} method, the default
* value is a {@link org.apache.shiro.authc.credential.SimpleCredentialsMatcher SimpleCredentialsMatcher} instance.
*
* @return the <code>CredentialsMatcher</code> used during an authentication attempt to verify submitted
* credentials with those stored in the system.
*/
public CredentialsMatcher getCredentialsMatcher() {
return credentialsMatcher;
}
再点击CredentialsMatcher
,看这个接口的所有方法
默认的是 SimpleCredentialsMatcher
,在这里要讲的是 Md5CredentialsMatcher
,它继承了HasCode,进一步点击
public class HashedCredentialsMatcher extends SimpleCredentialsMatcher {
/**
* @since 1.1
*/
private String hashAlgorithm;
private int hashIterations;
private boolean hashSalted;
private boolean storedCredentialsHexEncoded;
只要想办法替换我们当前的 UserRealm
的方法就可以了
Shiro请求授权实现
MyController 编写没授权页面跳转
//没授权跳转
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未经授权无法访问此页面";
}
ShiroConfig
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
//授权,正常情况下会跳转到未授权页面
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/*", "authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登陆的请求
bean.setLoginUrl("/toLogin");
//未授权页面
bean.setUnauthorizedUrl("/noauth");
return bean;
UserRealm
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
//SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:add");
//return info
return info;
}
info.addStringPermission("user:add");
把用户授权写死了,每个用户登录之后都有add权限,应在数据库取。
从数据库取权限
UserRealm 里修改刚才写死的代码以及 认证的一点代码
//info.addStringPermission("user:add");
//拿到当前登录这个对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();//拿到User对象
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
//return info
return info;
认证修改的代码 user
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
网页测试成功,成功在数据库取得权限
Shiro整合Thymeleaf
导入依赖
<!--shiro-thymeleaf整合-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
ShiroConfig
//整合ShiroDialect : 用来整合Shrio thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
index.xml xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro" >
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<p>
<a th:href="@{/toLogin}">登录</a>
</p>
<div th:text="${msg}"></div>
<hr>
<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>
进一步优化,登录进去之后,登录按钮消失:
index.html
<body>
<h1>首页</h1>
<!--从session中判断值!-->
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
UserRealm
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;//取我们认识的Token
//连接真实的数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){//没有这个人
return null;// UnknownAccountException
}
//用户登录成功,登录按钮的消失 获得seesion
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
测试结果:
12、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-springui
使用Swagger
要求:jdk 1.8 + 否则swagger2无法运行
步骤:
1、新建一个SpringBoot-web项目
2、添加Maven依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
3、编写HelloController,测试确保运行成功!
4、要使用Swagger,我们需要编写一个配置类 SwaggerConfig 来配置 Swagger
@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {
}
5、访问测试 :http://localhost:8081/swagger-ui.html ,可以看到swagger的界面;
配置Swagger
1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger,放入SwaggerConfig中。
@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2);
}
2、可以通过apiInfo()属性配置文档信息
//配置swagger信息 = apiInfo
private ApiInfo apiInfo(){
Contact contact = new Contact("Jan",
"https://www.cnblogs.com/zhongjianYuan/", "1075954259@qq.com");
return new ApiInfo(
"Jan的Api文档",
"即使再小的帆也能远航",
"v1.0",
"https://www.cnblogs.com/zhongjianYuan/",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
3、Docket 实例关联上 apiInfo()
//配置了swagger 的 bean 实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
总代码:
@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {
//配置了swagger 的 bean 实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
//配置swagger信息 = apiInfo
private ApiInfo apiInfo(){
Contact contact = new Contact("Jan",
"https://www.cnblogs.com/zhongjianYuan/", "1075954259@qq.com");
return new ApiInfo(
"Jan的Api文档",
"即使再小的帆也能远航",
"v1.0",
"https://www.cnblogs.com/zhongjianYuan/",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
4、重启项目,访问测试 http://localhost:8081/swagger-ui.html 看下效果;
问题:
出现Caused by: java.lang.NullPointerException: null
在application.properties中加入
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
配置扫描接口
1、构建Docket时通过select()方法配置怎么扫描接口。
//配置了swagger 的 bean 实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//RequestHandlerSelectors 配置要扫描接口的方式
//basePackage 指定要扫面的包 basePackage("com.jan.controller")
//any 扫描全部
//none 不扫描
//withClassAnnotation 扫描类的注解,参数是一个注解的反射对象
//withMethodAnnotation 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.jan.controller"))
//paths 过滤 路径
.paths(PathSelectors.ant("/jan/**"))
.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、除此之外,我们还可以配置接口扫描过滤:
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.jan.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口
.paths(PathSelectors.ant("/jan/**"))
.build();
}
5、这里的可选值还有
any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 通过正则表达式控制
ant(final String antPattern) // 通过ant()控制
6.是否启动swagger
//配置了swagger 的 bean 实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) //enable 是否启动swagger false 不启动
.select()
.apis(RequestHandlerSelectors.basePackage("com.jan.controller"))
//.paths(PathSelectors.ant("/jan/**"))
.build();
}
题目:只希望swagger在生产环境中使用,在发布的时候不使用?
- 判断是不是生产环境 flag = false
- 注入enable(flag)
1、添加 application-dev.properties application-pro.properties
2、在application.properties 添加配置
server.port=8081
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
spring.profiles.active=dev
3、SwaggerConfig 代码
//配置了swagger 的 bean 实例
@Bean
public Docket docket(Environment environment){
//设置要显示的swagger环境
Profiles profiles = Profiles.of("dev","test");
//获取项目环境: acceptsProfiles 参数是Profiles 只能是一个的 String:可以多个
//通过 environment.acceptsProfiles 贩毒案是否处在自己设定的环境当中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag) //enable 是否启动swagger false 不启动
.select()
.apis(RequestHandlerSelectors.basePackage("com.jan.controller"))
//.paths(PathSelectors.ant("/jan/**"))
.build();
}
4、查看dev的配置文件的效果
访问的不是dev的端口,所设的dev 端口是 8082
配置API分组
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("Jan")
1、如何配置多个分组?配置多个分组只需要配置多个docket即可:
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}
实体配置
1、新建一个实体类
//@Api(注释)
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:
//只要我们的接口中,返回值中存在实体类,它就会被扫描到Swagger
@PostMapping(value = "/user")
public User user(){
return new User();
}
3、重启查看测试
注:并不是因为@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 = "Hello控制类")
@RestController //==component 组件
public class HelloController {
@GetMapping(value = "/hello")
public String hello(){
return "hello";
}
//只要我们的接口中,返回值中存在实体类,它就会被扫描到Swagger
@PostMapping(value = "/user")
public User user(){
return new User();
}
//Operation 接口,不是放在类上,是方法
@ApiOperation("Hello2控制类")
@GetMapping("/hello2")
public String hello2(@ApiParam("用户名") String username){
return "hello2"+username;
}
}
这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!
接口测试
1.先编写一个接口测试
@ApiOperation("post控制类")
@PostMapping("/postt")
public User postt(@ApiParam("postt用户名") User user){
return user;
}
结果:
2.编写一个500错误
@ApiOperation("post控制类")
@PostMapping("/postt")
public User postt(@ApiParam("postt用户名") User user){
int i = 5/0;
return user;
}
结果:
总结:
相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。
Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通