SpringBoot

根据以下学习视频,个人整理的笔记

https://www.bilibili.com/video/BV1PE411i7CV?vd_source=7a8946d22777450e46486d5fd60d8d4d

真正学会一个框架

学会自己看官方文档!结合官方文档看源码!

微服务阶段

回顾:

  • javase:OOP
  • mysql:持久化
  • html+css+js+jQuery+Vue框架:视图
  • javaweb:独立开发MVC三层架构的网站
  • ssm:企业级框架,简化了我们的开发流程,配置也开始较为复杂

war:tomcat运行

spring再简化:SpringBoot - jar:内嵌tomcat;微服务架构!

服务越来越多:SpringCloud

程序员和码农的区别

程序猿:程序 = 数据结构 + 算法;

码 农:程序 = 面向对象 + 框架;

SpringBoot简介

回顾什么是Spring

Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

Spring是如何简化Java开发的

为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

3、基于切面(AOP)和惯例进行声明式编程;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

什么是SpringBoot

学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;

言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。

所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

是的这就是Java企业级应用->J2EE->spring->springboot的过程。

随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。

Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

真的很爽,我们快速去体验开发个接口的感觉吧!

微服务

什么是微服务?

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

单体应用架构

​ 所谓单体应用架构(all in one)是指,我们将一个应用的中的所有应用服务都封装在一个应用中。
​ 无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问, 等等各个功能放到一个war包内。(目前我们写的项目就是如此,将一个系统整体打包成war包)

  • 这样做的好处是,易于开发和测试;也十分方便部署;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
  • 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能把所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。

微服务架构

​ all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。
​ 所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时,可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。
​ 下这样做的好处是:

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

在这里插入图片描述

Martin Flower于2014年3月25日写的《Microservices》 ,详细的阐述了什么是微服务。

  • 原文地址: http://martinfowler.com/articles/microservices.html
  • 翻译: https://www.cnblogs.com/liuning8023/p/4493156.html

如何构建微服务

​ 一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过http相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。

​ 但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套、全程产品:

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

在这里插入图片描述

第一个SpringBoot程序

基本环境:

  • JDK1.8
  • maven3.6.1
  • springboot最新版
  • IDEA

官方提供了一个快速生成的网站!IDEA集成了这个网站

  • 我们可以在官网直接下载后,导入IDEA开发
  • 也可以直接使用IDEA创建一个SpringBoot项目(我们一般用这个)

使用网站生成的例子

在这里插入图片描述

在这里插入图片描述

然后直接用IDEA去打开这个生成的项目即可

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

看一下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.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.kuang</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>
		<!--所有的SpringBoot依赖都是使用spring-boot-starter作为前缀的
			没有版本号是因为可以继承父依赖的
		-->

		<!--单元测试-->
		<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>
		</plugins>
	</build>

</project>

如上所示,主要有四个部分:

  • 项目元数据信息:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括: groupld、 artifactld、 version、 name、 description等
  • parent:继承spring- boot -starter-parent的依赖管理,控制版本与打包等内容
  • dependencies:项目具体依赖,这里包含了spring-boot-starter-web用于实现HTTP接口(该依赖中包含了SpringMVC),官网对它的描述是:使用SpringMVC构建Web (包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。spring-boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用我们将在后面逐步展开。
  • build: 构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR包来直接运行

打包成JAR包来直接运行

在这里插入图片描述

使用IDEA生成的例子

使用vpn才能访问外网,保证项目访问spring网站流畅

在这里插入图片描述

在这里插入图片描述

  • 更改端口号

在核心配置文件 application.properties 中添加如下代码

#更改项目的端口号
server.port=9090

在这里插入图片描述

  • SpringBoot自定义启动Banner

在resources目录下新建一个banner.txt,然后把我们自定义的内容写入banner.txt,即可。SpringBoot会自动识别

在这里插入图片描述

原理初步探索【重点】

我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;

其中有一个父依赖,这个父依赖主要是管理项目的资源过滤及插件!

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.0</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点进去,发现还有一个父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.7.0</version>
</parent>

这个父依赖才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本(因为有这些版本仓库);但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

pom.xml中还有启动器

<!--启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

启动器:说白了就是SpringBoot的启动场景。

比如spring-boot-starter-web,它就会帮我们自动导入web环境所有的依赖。

SpringBoot会将所有的功能场景,都变成一个个的启动器

我们要使用什么功能,就只需要找到对应的启动器就可以了

主程序

package com.kuang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


//@SpringBootApplication:标注这个类是一个SpringBoot的应用,启动类下的所有资源被导入
@SpringBootApplication
public class Springboot01HelloworldApplication {

    public static void main(String[] args) {
        //将SpringBoot应用启动
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}

注解剖析:

@SpringBootApplication

	@SpringBootConfigurationSpringBoot的配置
		@Configuration:spring配置类
			@Component:说明这也是一个spring的组件
			
	@EnableAutoConfiguration:自动配置
		@AutoConfigurationPackage:自动配置包
			@Import({Registrar.class}):自动配置包注册
		@Import({AutoConfigurationImportSelector.class}):自动导入包的核心
			List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);获取所有的配置
        		获取候选的配置
				protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    				List<String> configurations = 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这是自动配置的核心文件,所有的自动配置类都在这里了

在这里插入图片描述

初步总结:SpringBoot所有的自动配置都是在启动的时候扫描并加载spring.factories(所有的自动配置类都在这里面,但不是所有的自动配置类都生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动配置类就会生效,然后就配置成功了)

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

SpringApplication.run(Springboot01HelloworldApplication.class, args);

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

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

在这里插入图片描述

SpringBoot配置

SpringBoot这个配置文件中到底可以配置哪些东西呢?

官方的配置太多了

我们了解原理:一通百通

application.properties这个文件可以删掉,官方不推荐使用 properties 配置文件。推荐使用 yaml 配置文件

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

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

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

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

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

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

传统xml配置:

<server>    
    <port>9090<port>
</server>

yaml配置:

server:
  port: 9090

很明显yaml看起来更加轻巧一点

yaml和properties对比

yaml

# 对空格的要求十分高!
# 可以注入到我们的配置类中!

# 普通的key-value
name: qinjiang

# 对象
student1:
  name: qinjiang
  age: 3

# 行内写法
student2: {name: qinjiang,age: 3}

# 数组
pets1:
  - cat
  - dog
  - pig

# 数组
pets2: [cat,dog,pig]

properties

# properties 只能保存键值对

name=qinjiang

student.name=qinjiang
student.age=3

yaml给实体类赋值

yaml可以直接给实体类赋值

例子:

package com.kuang.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Dog {

    private String name;
    private Integer age;

    public Dog() {
    }

    public Dog(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package com.kuang.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;

/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component
@ConfigurationProperties(prefix = "person")//如果不配置使用了这个注解:就会提示报红,但不影响运行。可以添加相关依赖解决报红
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

    public Person() {
    }

    public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
        this.name = name;
        this.age = age;
        this.happy = happy;
        this.birth = birth;
        this.maps = maps;
        this.lists = lists;
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getHappy() {
        return happy;
    }

    public void setHappy(Boolean happy) {
        this.happy = happy;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public List<Object> getLists() {
        return lists;
    }

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", happy=" + happy +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}
person:
  name: qinjiang
  age: 3
  happy: false
  birth: 2019/11/02
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: 旺财
    age: 3

这是测试类,最后测试一下

package com.kuang;

import com.kuang.pojo.Dog;
import com.kuang.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {

    @Autowired
    private Person person;

    @Test
    void contextLoads() {
        System.out.println(person);
        //Person{name='qinjiang', age=3, happy=false, birth=Sat Nov 02 00:00:00 CST 2019, maps={k1=v1, k2=v2}, lists=[code, music, girl], dog=Dog{name='旺财', age=3}}
    }

}

yaml配置文件功能很强大,还能这么写。SpringBoot推荐我们使用yaml。所以我们一般用yaml配置文件,不用properties

person:
    name: qinjiang${random.uuid} # 随机uuid
    age: ${random.int}  # 随机int
    happy: false
    birth: 2000/01/01
    maps: {k1: v1,k2: v2}
    lists:
      - code
      - girl
      - music
    dog:
      name: ${person.hello:other}_旺财 # 如果有person.hello就用person.hello的值,如果没有就默认为other
      age: 1

yaml支持松散绑定

松散绑定:这个什么意思呢? 比如我的yaml中写的 first-name,这个和 firstName 是一样的,后面跟着的字母 默认是大写的。这就是松散绑定。可以测试一下 (yaml文件中写first-name,实体类中写firstName,是可以对应起来的)

例子:

package com.kuang.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "dog")
public class Dog {

    private String firstName;
    private Integer age;

    public Dog() {
    }

    public Dog(String firstName, Integer age) {
        this.firstName = firstName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "firstName='" + firstName + '\'' +
                ", age=" + age +
                '}';
    }
}
dog:
  first-name: 阿黄
  age: 3
package com.kuang;

import com.kuang.pojo.Dog;
import com.kuang.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {

    @Autowired
    private Dog dog;

    @Test
    void contextLoads() {
        System.out.println(dog);
        //Dog{firstName='阿黄', age=3}
    }

}

小结:

  • 配置yaml和配置properties都可以获取到值,强烈推荐 yaml;
  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下@value**(这是properties配置文件方式)**
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!(这是yaml配置文件方式)

yaml支持JSR303数据校验

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

常见的参数

在这里插入图片描述

@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 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

例子:

可能需要添加这个依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
package com.kuang.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Component
@ConfigurationProperties(prefix = "person")//如果不配置使用了这个注解:就会提示报红,但不影响运行
@Validated //数据校验
public class Person {

    @Email()//表明这个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;

    public Person() {
    }

    public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
        this.name = name;
        this.age = age;
        this.happy = happy;
        this.birth = birth;
        this.maps = maps;
        this.lists = lists;
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getHappy() {
        return happy;
    }

    public void setHappy(Boolean happy) {
        this.happy = happy;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public List<Object> getLists() {
        return lists;
    }

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", happy=" + happy +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}
person:
  name: qinjiang # 这个值必须为电子邮箱数据格式,否则测试运行会报错
  age: 3
  happy: false
  birth: 2019/11/02
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: 旺财
    age: 3

package com.kuang;

import com.kuang.pojo.Dog;
import com.kuang.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {

    @Autowired
    private Person person;

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

}

多环境配置及配置文件位置

配置文件yaml可以存在的位置以及它们的优先级

在这里插入图片描述

多环境配置

server:
  port: 8081
# 默认启动8081端口
# 我们也可以选择激活某个端口
spring:
  profiles:
    active: dev # 这里选择激活了下面8082端口

# --- 这个代表分割线
---
server:
  port: 8082
spring:
  profiles: dev

---
server:
  port: 8083
spring:
  profiles: test

自动装配原理【重点】

精髓

  • SpringBoot启动会加载大量的自动配置类xxxxAutoConfiguration,这些都在spring.factories核心文件中),每一个自动配置类中都有对应的 xxxxProperties,这些xxxxProperties中有着相关的属性与默认值,这些属性又是和配置文件(application.yaml)绑定的(通过这个注解@ConfigurationProperties绑定),所以我们可以通过配置文件来修改SpringBoot自动配置的默认值;

  • 如果我们想写一个自动配置类,首先看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

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

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

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

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

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

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

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

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

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

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

#开启springboot的调试类
debug: true

输出的日志中大致分为一下几类:

Positive matches:已经启用了,并且生效的

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

Unconditional classes:没有条件的类

掌握吸收原理,以不变应万变

SpringBoot Web开发

需要思考:SpringBoot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?

静态资源导入

通过webjars方式导入静态资源的例子:

导入依赖

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

在这里插入图片描述

在这里插入图片描述

直接在目录下导入静态资源的例子:

这三个存放静态资源的路径都是默认的,如果你在SpringBoot核心配置文件里自己修改了存放静态资源默认路径的值,则存放静态资源的路径以修改的路径的值为准**(所以一般我们用默认的这三个路径存放静态资源,不要自己去修改存放静态资源路径)**

在这里插入图片描述

首页和图标定制

首页例子:

在这里插入图片描述

图标定制(了解即可):不同版本的SpringBoot实现方式不一样!!!

在这里插入图片描述

Thymeleaf模板引擎

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

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

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

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

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

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

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

引入Thymeleaf
怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。

给大家三个网址:

  • Thymeleaf 官网:https://www.thymeleaf.org/
  • Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
  • Spring官方文档:找到我们对应的版本https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

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

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

**导入依赖即可!**注意版本问题。

Thymeleaf所有的模板引擎都写在templates目录下

在这里插入图片描述

例子:

test.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:元素名 -->

<!--必须要加下面的注释否则会报错-->
<!--/*@thymesVar id="msg" type=""*/-->
<div th:text="${msg}"></div>


</body>
</html>

IndexController.java

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


//在templates目录下的所有页面,只能通过controller来跳转
//这个需要模板引擎的支持! 需要导入thymeleaf依赖
@Controller
public class IndexController {

    @RequestMapping("/test")
    public String test(Model model){
        model.addAttribute("msg","hello,springboot");
        return "test";
    }

}

Thymeleaf语法

Thymeleaf语法具体更多的语法自行去官方文档了解

先展示一两个语法的例子,更多的语法后面都会用到

例子:

test.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:元素名-->

<!--必须要加下面的注释否则会报错-->
<!--/*@thymesVar id="msg" type=""*/-->
<div th:text="${msg}"></div>

<!--utext不会转义msg传过来的html标签元素-->
<div th:utext="${msg}"></div>

<hr>

<!--/*@thymesVar id="users" type=""*/-->
<h3 th:each="user:${users}" th:text="${user}"></h3>
<!--第二种写法-->
<!--<h3 th:each="user:${users}">[[ ${user} ]]</h3>-->


</body>
</html>

IndexController.java

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Arrays;


//在templates目录下的所有页面,只能通过controller来跳转
//这个需要模板引擎的支持! 需要导入thymeleaf依赖
@Controller
public class IndexController {

    @RequestMapping("/test")
    public String test(Model model){
        model.addAttribute("msg","<h1>hello,springboot</h1>");
        //Arrays.asList()把数组转换成集合
        model.addAttribute("users", Arrays.asList("qinjiang","kuangshen"));
        return "test";
    }

}

SpringMVC自动配置原理

  • 在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。途径一:源码分析,途径二:官方文档!

  • 只有把这些都搞清楚了,我们在之后使用才会更加得心应手。

  • 地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

页面搜索:Spring MVC Auto-configuration,学会阅读官方文档!

扩展SpringMVC

扩展 SpringMVC 的功能,其中有很多功能

**看官方文档,结合例子理解,**这个例子主要是自定义视图解析器:

package com.kuang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//如果想自定义一些定制化的功能,只要写这个组件,然后将它交给SpringBoot,SpringBoot就会帮我们自动装配
//扩展 SpringMVC 的功能,其中有很多功能,这个例子主要是自定义视图解析器
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //public interface 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;
        }
    }

}

有些扩展功能的具体实现方法因SpringBoot版本而异,所以学会结合官方文档剖析源码很重要

这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。

SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

例子:

package com.kuang.config;


import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;



//如果我们要扩展SpringMVC,官方建议我们这样去做!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/kuang").setViewName("test");
        //http://localhost:8080/kuang 能够跳到templates目录下的test.html页面
    }
}

在SpringBoot源码中会有非常多的 xxxxConfiguration 扩展配置(注意不是xxxxAutoConfiguration),只要看见了这个@Configuration,我们就应该多留心注意!!!

员工管理系统

这个系统不是完整的,它只是为了看一下,使用SpringBoot开发,我们要进行哪些操作!

  • 首页配置
    • 所有页面的静态资源都需要使用thymeleaf接管!
    • 具体实现的方法,看thymeleaf语法。
    • url用 @{…}

比如:

<!DOCTYPE html>
<!--添加命名空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <meta name="description" content="">
      <meta name="author" content="">
      <title>Signin Template for Bootstrap</title>
       
      <!-- Bootstrap core CSS -->
      <!--加载静态资源,添加样式-->
      <!--thymeleaf模板引擎的语法,url用 @{...} -->
      <!--这样就算项目访问路径改变了,也照样能读取对应的静态资源,路径不需要写死-->
      <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
       
      <!-- Custom styles for this template -->
      <link th:href="@{/css/signin.css}" rel="stylesheet">
       
   </head>
   <body class="text-center">
      <form class="form-signin" action="dashboard.html">
         <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
         <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
         <label class="sr-only">Username</label>
         <input type="text" class="form-control" placeholder="Username" required="" autofocus="">
         <label class="sr-only">Password</label>
         <input type="password" class="form-control" placeholder="Password" required="">
         <div class="checkbox mb-3">
            <label>
          <input type="checkbox" value="remember-me"> Remember me
        </label>
         </div>
         <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
         <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
         <a class="btn btn-sm">中文</a>
         <a class="btn btn-sm">English</a>
      </form>
   </body>
</html>
  • 页面国际化

    • 我们需要配置 i18n(internationalization,头尾字母i和n,中间18个字母) 文件
    • 如果需要在项目中进行按钮自动切换,我们需要自定义一个组件 LocaleResolver
    • 记得将自己写的组件配置到 spring 容器中 @Bean
    • 国际化用:#{…}
  • 登录和拦截器

    • 注意:有些资源加载不出来,需要用vpn才能加载,因为有些资源是国外服务器的
  • 员工列表展示

    • 提取公共页面

      • th:fragment="topbar"   <!--抽取顶部导航栏的代码-->
        
      • th:replace="~{commons/commons::topbar}"   <!--实现顶部导航栏代码的复用-->
        
      • 如果要传递参数可以直接使用 () 传参,其它地方接收判断(一般用三元运算符判断)即可

    • 列表循环展示

  • 员工的增删改查

  • 404

具体代码,查看项目文件,这个项目是没有用到数据库的

整合JDBC使用

SpringData简介

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

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

Sping Data 官网:https://spring.io/projects/spring-data

数据库相关的启动器 :可以参考官方文档:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

例子

创建一个SpringBoot项目

添加 JDBC API、MySQL Driver 两个依赖,后面有需要用到web依赖自行去pom.xml里面导依赖

在这里插入图片描述

新建 application.yaml

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

Springboot04DataApplicationTests.java

package com.kuang;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot04DataApplicationTests {


    //不再推荐使用@Autowired,请使用@Resource来标记,实现bean的自动装配
    @Resource
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        //查看一下默认的数据源:class com.zaxxer.hikari.HikariDataSource 曾经接触过的数据源有 dbcp、c3p0、Tomcat jdbc
        //HikariDataSource 号称javaweb 当前速度最快的数据源,相比于传统的连接池更加优秀
        System.out.println(dataSource.getClass());

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

        //在SpringBoot里面有很多 xxxxtemplate ,这是SpringBoot已经配置好的模板bean,拿来就能用

        //关闭
        connection.close();

    }

}

新建 JDBCController.java

package com.kuang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

@RestController
public class JDBCController {

    @Resource
    JdbcTemplate jdbcTemplate;

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

    
    //增加用户
    @GetMapping("/addUser")
    public String addUser(){
        String sql = "insert into mybatis.user(id,name,pwd) values (4,'小明','123456')";
        jdbcTemplate.update(sql);
        return "add-ok!";
    }

    //修改用户
    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
        //预编译传参,自己回顾原生的jdbc代码
        String sql = "update mybatis.user set name=?,pwd=? where id=" + id;

        //封装数据
        Object[] objects = new Object[2];
        objects[0] = "小明2";
        objects[1] = "zzzxxx";

        jdbcTemplate.update(sql,objects);//预编译传参,自己回顾原生的jdbc代码

        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 "delete-ok!";
    }

}

去Springboot04DataApplication主程序入口启动一下,查询数据库的所有信息 结果如下

在这里插入图片描述

其它三个 增加、修改、删除 请求也是能够实现的!

整合Druid数据源

Druid简介

Java程序很大一部分要操作数据库,为了提高性能,操作数据库的时候,又不得不使用数据库连接池。

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP、PROXOOL 等 DB(数据库) 池的优点,同时加入了日志监控。

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

Druid 已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源

我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控

Github地址:https://github.com/alibaba/druid/

  • com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

例子

添加 Druid 数据源依赖

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

添加log4j依赖,因为我们配置中用到了

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

application.yaml

spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就ok了,serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    # com.mysql.jdbc.Driver 这个已经过时
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 可以配置自己想使用的数据源,指定了之后,SpringBoot默认的数据源就不会用了
    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

JDBCController.java 中的代码照样能跑

Druid强大的地方在于它能够自定义一些配置

新建DruidConfig.java

package com.kuang.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.annotation.WebFilter;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

    //@ConfigurationProperties 绑定 application.yaml 中的 spring.datasource
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    //后台监控功能
    //这个 ServletRegistrationBean 相当于 web.xml
    //因为SpringBoot内置了Servlet容器,所以没有web.xml,替代方法:把 ServletRegistrationBean 注册到Spring中
    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        //后台需要有人登录,账号密码配置
        Map<String, String> initParameters = new HashMap<>();

        //增加配置
        initParameters.put("loginUsername","admin");//登录的key:loginUsername是固定参数
        initParameters.put("loginPassword","123456");//登录的key:loginPassword是固定参数

        //允许谁可以访问
        initParameters.put("allow","");//这是允许所有人都可以访问

        //禁止谁能访问
        //initParameters.put("kuangshen","192.168.11.123");//禁止这个ip:192.168.11.123 访问

        bean.setInitParameters(initParameters); //初始化参数
        return bean;
    }

    //filter 过滤器
    @Bean
    public FilterRegistrationBean webStatFilter(){

        FilterRegistrationBean bean = new FilterRegistrationBean();

        bean.setFilter(new WebStatFilter());

        //可以过滤哪些请求呢?
        Map<String, String> initParameters = new HashMap<>();

        //这些请求不进行过滤
        //exclusions:排除在外
        initParameters.put("exclusions","*.js,*.css,/druid/*");


        bean.setInitParameters(initParameters);

        return bean;
    }

}

启动SpringBoot看看效果!

在这里插入图片描述

在这里插入图片描述

整合MyBatis框架

整合包

mybatis-spring-boot-starter

导入这个依赖才能整合

<!-- 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.2.2</version>
</dependency>

创建一个新项目,添加 SpringWeb、MySQLDriver、JDBC API 依赖,导入整合mybatis的依赖

项目的整体目录结构

在这里插入图片描述

application.properties

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

# 整合mybatis
mybatis.type-aliases-package=com.kuang.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

User.java

package com.kuang.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

UserMapper.java

package com.kuang.controller;


import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
public class UserController {

    @Resource
    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(4,"kkkxxx","123123"));

        return "add--ok";
    }

    @GetMapping("/updateUser")
    public String updateUser(){
        userMapper.updateUser(new User(4,"xxxppp","321www"));

        return "update--ok";
    }

    @GetMapping("/deleteUser")
    public String deleteUser(){
        userMapper.deleteUser(4);

        return "delete--ok";
    }



}

UserMapper.xml

<?xml version="1.0" encoding="utf8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.UserMapper">

    <select id="queryUserList" resultType="User">
        select * from user;
    </select>

    <select id="queryUserById" resultType="User">
        select * from user where id = #{id};
    </select>

    <insert id="addUser" parameterType="User">
        insert into user (id, name, pwd) values (#{id},#{name},#{pwd});
    </insert>

    <update id="updateUser" parameterType="User">
        update user set name=#{name},pwd=#{pwd} where id = #{id};
    </update>

    <delete id="deleteUser" parameterType="int">
        delete from user where id = #{id};
    </delete>

</mapper>

UserController.java

package com.kuang.controller;


import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
public class UserController {

    @Resource
    private UserMapper userMapper;

    @GetMapping("/queryUserList")
    public List<User> queryUserList(){
        List<User> userList = userMapper.queryUserList();

        for (User user : userList) {
            System.out.println(user);
        }

        return userList;
    }


}

启动SpringBoot服务测试一下!

在这里插入图片描述

SpringSecurity

安全简介

**在 Web 开发中,安全一直是非常重要的一个方面。**安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

如果我们使用过滤器、拦截器实现web安全,我们会用到大量的原生代码,繁琐冗余!所以我们用框架实现web安全!

市面上存在比较有名的框架:Shiro,Spring Security !

这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?

  • 首先我们看下它的官网介绍:Spring Security官网地址 https://spring.io/projects/spring-security
    • Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
    • Spring Security是一个框架,侧重于为Java应用程序提供身份验证授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

从官网的介绍中可以知道这是一个权限框架。回想我们之前做项目没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。

怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。

  • 用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。
  • 用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

环境搭建

新建SpringBoot项目,导入web依赖,导入thymeleaf依赖

导入静态资源!具体查看项目

application.properties

# 关闭模板引擎thymeleaf缓存,方便我们调试
spring.thymeleaf.cache=false

RouterController.java

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RouterController {


    @RequestMapping({"/","/index"})
    public String index(){
        //跳到主页
        return "index";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        //跳到登录页面
        return "views/login";
    }

    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id")int id){
        return "views/level1/" + id;
    }

    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id")int id){
        return "views/level2/" + id;
    }

    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id")int id){
        return "views/level3/" + id;
    }
}

启动SpringBoot服务,看下效果

在这里插入图片描述

认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式,(@Enablexxxx 开启某个功能)

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

参考官网:https://spring.io/projects/spring-security

查看我们自己项目中的版本,找到对应的帮助文档:https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5

用户认证和授权

导入SpringSecurity依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

SecurityConfig.java

package com.kuang.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;


//它能够自动将代码横切进去的,AOP思想
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //首页所有人可以访问,功能页只有对应有权限的人才能访问
        //这是请求授权的规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");//链式编程

        //没有权限默认会到登录页面,需要开启登录的页面
        http.formLogin();//它这里面会有一个/login请求
    }


    //认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //一般情况下都是从数据库中进行认证
        //auth.jdbcAuthentication()

        //我们现在没有连数据库,所以从内存中去认证
        //inMemoryAuthentication 从内存里面去认证
        //BCryptPasswordEncoder() 密码加密的一种方式   java有原生的加密方式,md5
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("kuangshen").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");
    }
}

注销及权限控制

导入security-thymeleaf整合包依赖

<!--security-thymeleaf整合包,我们可以在thymeleaf里面写一些关于SpringSecurity的操作-->
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

index.html

<!DOCTYPE html>
<!--需要先导入相关的命名空间 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>首页</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link th:href="@{/qinjiang/css/qinstyle.css}" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
        <div class="ui secondary menu">
            <a class="item"  th:href="@{/index}">首页</a>

            <!--登录注销-->
            <div class="right menu">


                <!--需要先导入相关的命名空间,并且可能需要降低SpringBoot的版本 2.0.9.RELEASE -->
                <div sec:authorize="!isAuthenticated()"><!--如果未登录,显示登录按钮-->
                    <a class="item" th:href="@{/toLogin}">
                        <i class="address card icon"></i> 登录
                    </a>
                </div>


                <div sec:authorize="isAuthenticated()"><!--如果已经登录,显示用户名-->
                    <a class="item">
                        用户名:<span sec:authentication="name"></span>
                        角色:<span sec:authentication="principal.authorities"></span>
                    </a>
                </div>

                <div sec:authorize="isAuthenticated()"><!--如果已经登录,显示注销按钮-->
                    <a class="item" th:href="@{/logout}">
                        <i class="sign-out icon"></i> 注销
                    </a>
                </div>

            </div>
        </div>
    </div>

    <div class="ui segment" style="text-align: center">
        <h3>Spring Security Study by 秦疆</h3>
    </div>

    <div>
        <br>
        <div class="ui three column stackable grid">

            <!--菜单根据用户的角色动态实现-->
            <div class="column" sec:authorize="hasRole('vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip2')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip3')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
    
</div>


<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>

</body>
</html>

可能需要降低SpringBoot的版本

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <!--<version>2.7.0</version>-->
    <!--有些功能需要降低版本-->
    <version>2.0.9.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

SecurityConfig.java

package com.kuang.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;


//它能够自动将代码横切进去的,AOP思想
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //首页所有人可以访问,功能页只有对应有权限的人才能访问
        //这是请求授权的规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");//链式编程

        //没有权限默认会到登录页面,需要开启登录的页面
        http.formLogin();//它这里面会有一个/login请求

        
        //注销,这样就开启了注销功能。注销后一般会跳到首页
        http.logout().logoutSuccessUrl("/");
        //如果注销失败可能需要关闭 跨站域请求伪造(csrf) 功能
        http.csrf().disable();
        
    }


    //认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //一般情况下都是从数据库中进行认证
        //auth.jdbcAuthentication()

        //我们现在没有连数据库,所以从内存中去认证
        //inMemoryAuthentication 从内存里面去认证
        //BCryptPasswordEncoder() 密码加密的一种方式   java有原生的加密方式,md5
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("kuangshen").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");
    }
}

记住我及首页定制

SecurityConfig.java

package com.kuang.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;


//它能够自动将代码横切进去的,AOP思想
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //首页所有人可以访问,功能页只有对应有权限的人才能访问
        //这是请求授权的规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");//链式编程

        //没有权限默认会到登录页面,需要开启登录的页面
        http.formLogin().loginPage("/toLogin")//定制登录页面
                .usernameParameter("user")//自定义接收前端的参数
                .passwordParameter("pwd")//自定义接收前端的参数
                .loginProcessingUrl("/login");//和前端的登录请求一样

        //注销,这样就开启了注销功能。注销后一般会跳到首页
        http.logout().logoutSuccessUrl("/");
        //如果注销失败可能需要关闭 跨站域请求伪造(csrf) 功能
        http.csrf().disable();

        //开启记住我功能 本质就是cookie 默认保存14天
        http.rememberMe().rememberMeParameter("remember");//自定义接收前端的参数

    }


    //认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //一般情况下都是从数据库中进行认证
        //auth.jdbcAuthentication()

        //我们现在没有连数据库,所以从内存中去认证
        //inMemoryAuthentication 从内存里面去认证
        //BCryptPasswordEncoder() 密码加密的一种方式   java有原生的加密方式,md5
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }
}

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>登录</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div class="ui segment">

        <div style="text-align: center">
            <h1 class="header">登录</h1>
        </div>

        <div class="ui placeholder segment">
            <div class="ui column very relaxed stackable grid">
                <div class="column">
                    <div class="ui form">
                        <form th:action="@{/login}" method="post">
                            <div class="field">
                                <label>Username</label>
                                <div class="ui left icon input">
                                    <input type="text" placeholder="Username" name="user">
                                    <i class="user icon"></i>
                                </div>
                            </div>
                            <div class="field">
                                <label>Password</label>
                                <div class="ui left icon input">
                                    <input type="password" name="pwd">
                                    <i class="lock icon"></i>
                                </div>
                            </div>
                            <div class="field">
                                <input type="checkbox" name="remember"> 记住我
                            </div>
                            <input type="submit" class="ui blue submit button"/>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <div style="text-align: center">
            <div class="ui label">
                </i>注册
            </div>
            <br><br>
            <small>blog.kuangstudy.com</small>
        </div>
        <div class="ui segment" style="text-align: center">
            <h3>Spring Security Study by 秦疆</h3>
        </div>
    </div>


</div>

<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>

</body>
</html>

Shiro

简介

  • Apache Shiro是一个强大且易用的Java安全框架
  • 可以完成身份验证、授权、加密、会话管理、web集成、缓存等
  • Shiro 不仅可以用在 JavaSE 环境中,也可以用在 JavaEE 环境中

官网: http://shiro.apache.org/

GitHub:apache/shiro: Apache Shiro (github.com) 可以从GitHub上下载

有哪些功能

在这里插入图片描述

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

Shiro架构(外部)

从外部来看Shiro,即从应用程序角度来观察如何使用shiro完成工作

在这里插入图片描述

应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

  • Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
  • Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个Shiro应用:

  • 应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
  • 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入

Shiro架构(内部)

在这里插入图片描述

  • Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;

  • SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

    • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
    • Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
    • SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所以,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
    • SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached(内存)中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
    • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
    • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的
  • Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

快速体验

创建一个普通的maven项目

在普通的maven项目下新建一个module

  1. 导入依赖

    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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>springboot-08-shiro</artifactId>
            <groupId>org.kuang</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>hello-shiro</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.9.0</version>
            </dependency>
            <!-- configure logging -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jcl-over-slf4j</artifactId>
                <version>1.7.21</version>
            </dependency>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-slf4j-impl</artifactId>
                <version>2.17.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>2.17.2</version>
            </dependency>
    
        </dependencies>
    
    </project>
    
  2. 配置文件

    log4j2.xml

    <Configuration name="ConfigTest" status="ERROR" monitorInterval="5">
    
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Logger name="org.springframework" level="warn" additivity="false">
                <AppenderRef ref="Console"/>
            </Logger>
            <Logger name="org.apache" level="warn" additivity="false">
                <AppenderRef ref="Console"/>
            </Logger>
            <Logger name="net.sf.ehcache" level="warn" additivity="false">
                <AppenderRef ref="Console"/>
            </Logger>
            <Logger name="org.apache.shiro.util.ThreadContext" level="warn" additivity="false">
                <AppenderRef ref="Console"/>
            </Logger>
            <Root level="info">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>
    

    shiro.ini

    #
    # Licensed to the Apache Software Foundation (ASF) under one
    # or more contributor license agreements.  See the NOTICE file
    # distributed with this work for additional information
    # regarding copyright ownership.  The ASF licenses this file
    # to you under the Apache License, Version 2.0 (the
    # "License"); you may not use this file except in compliance
    # with the License.  You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing,
    # software distributed under the License is distributed on an
    # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    # KIND, either express or implied.  See the License for the
    # specific language governing permissions and limitations
    # under the License.
    #
    # =============================================================================
    # Quickstart INI Realm configuration
    #
    # For those that might not understand the references in this file, the
    # definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
    # =============================================================================
    
    # -----------------------------------------------------------------------------
    # Users and their assigned roles
    #
    # Each line conforms to the format defined in the
    # org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
    # -----------------------------------------------------------------------------
    [users]
    # user 'root' with password 'secret' and the 'admin' role
    root = secret, admin
    # user 'guest' with the password 'guest' and the 'guest' role
    guest = guest, guest
    # user 'presidentskroob' with password '12345' ("That's the same combination on
    # my luggage!!!" ;)), and role 'president'
    presidentskroob = 12345, president
    # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
    darkhelmet = ludicrousspeed, darklord, schwartz
    # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
    lonestarr = vespa, goodguy, schwartz
    
    # -----------------------------------------------------------------------------
    # Roles with assigned permissions
    #
    # Each line conforms to the format defined in the
    # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
    # -----------------------------------------------------------------------------
    [roles]
    # 'admin' role has all permissions, indicated by the wildcard '*'
    admin = *
    # The 'schwartz' role can do anything (*) with any lightsaber:
    schwartz = lightsaber:*
    # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
    # license plate 'eagle5' (instance specific id)
    goodguy = winnebago:drive:eagle5
    
  3. java文件

    Quickstart.java

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    //import org.apache.shiro.ini.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    //import org.apache.shiro.lang.util.Factory;
    import org.apache.shiro.util.Factory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    
    /**
     * Simple Quickstart application showing how to use Shiro's API.
     *
     * @since 0.9 RC2
     */
    public class Quickstart {
    
        private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
    
    
        public static void main(String[] args) {
    
            // The easiest way to create a Shiro SecurityManager with configured
            // realms, users, roles and permissions is to use the simple INI config.
            // We'll do that by using a factory that can ingest a .ini file and
            // return a SecurityManager instance:
    
            // Use the shiro.ini file at the root of the classpath
            // (file: and url: prefixes load from files and urls respectively):
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
            SecurityManager securityManager = factory.getInstance();
    
            // for this simple example quickstart, make the SecurityManager
            // accessible as a JVM singleton.  Most applications wouldn't do this
            // and instead rely on their container configuration or web.xml for
            // webapps.  That is outside the scope of this simple quickstart, so
            // we'll just do the bare minimum so you can continue to get a feel
            // for things.
            SecurityUtils.setSecurityManager(securityManager);
    
            // Now that a simple Shiro environment is set up, let's see what you can do:
    
            // get the currently executing user:
            Subject currentUser = SecurityUtils.getSubject();
    
            // Do some stuff with a Session (no need for a web or EJB container!!!)
            Session session = currentUser.getSession();
            session.setAttribute("someKey", "aValue");
            String value = (String) session.getAttribute("someKey");
            if (value.equals("aValue")) {
                log.info("Retrieved the correct value! [" + value + "]");
            }
    
            // let's login the current user so we can check against roles and permissions:
            if (!currentUser.isAuthenticated()) {
                UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
                token.setRememberMe(true);
                try {
                    currentUser.login(token);
                } catch (UnknownAccountException uae) {
                    log.info("There is no user with username of " + token.getPrincipal());
                } catch (IncorrectCredentialsException ice) {
                    log.info("Password for account " + token.getPrincipal() + " was incorrect!");
                } catch (LockedAccountException lae) {
                    log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                            "Please contact your administrator to unlock it.");
                }
                // ... catch more exceptions here (maybe custom ones specific to your application?
                catch (AuthenticationException ae) {
                    //unexpected condition?  error?
                }
            }
    
            //say who they are:
            //print their identifying principal (in this case, a username):
            log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
    
            //test a role:
            if (currentUser.hasRole("schwartz")) {
                log.info("May the Schwartz be with you!");
            } else {
                log.info("Hello, mere mortal.");
            }
    
            //test a typed permission (not instance-level)
            if (currentUser.isPermitted("lightsaber:wield")) {
                log.info("You may use a lightsaber ring.  Use it wisely.");
            } else {
                log.info("Sorry, lightsaber rings are for schwartz masters only.");
            }
    
            //a (very powerful) Instance Level permission:
            if (currentUser.isPermitted("winnebago:drive:eagle5")) {
                log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                        "Here are the keys - have fun!");
            } else {
                log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
            }
    
            //all done - log out!
            currentUser.logout();
    
            System.exit(0);
        }
    }
    
  4. 运行测试

在这里插入图片描述

上面的代码都是分析GitHub源码,复制粘贴过来的。

Shiro的Subject分析

Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
//import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
//import org.apache.shiro.lang.util.Factory;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);


        //获取当前的用户对象 Subject
        Subject currentUser = SecurityUtils.getSubject();

        //通过当前用户拿到 Session
        Session session = currentUser.getSession();

        session.setAttribute("someKey", "aValue");

        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Subject=>Session[" + value + "]");
        }

        //判断当前用户是否被认证
        if (!currentUser.isAuthenticated()) {

            //会根据用户名和密码生成一个 Token:令牌
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);//设置记住我
            try {
                currentUser.login(token);//执行了登录操作
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }


        //currentUser.getPrincipal() 获得当前用户的认证
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //测试当前用户有什么角色身份
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //粗粒度
        //获得当前用户的权限
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //细粒度
        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //注销
        //all done - log out!
        currentUser.logout();

        //结束
        System.exit(0);
    }
}

这些是shiro的主要内容

Subject currentUser = SecurityUtils.getSubject(); //获取当前的用户对象 Subject

Session session = currentUser.getSession(); 	//通过当前用户拿到 Session
currentUser.isAuthenticated() 	//判断当前用户是否被认证
currentUser.getPrincipal() 		//获得当前用户的认证
currentUser.hasRole("...") 		//可以获得当前用户是否拥有什么角色
currentUser.isPermitted("...")		//获得当前用户的权限
currentUser.logout();			//注销

这些在SpringSecurity中基本都有~

SpringBoot整合Shiro环境搭建

新建一个SpringBoot module,添加spring-web、thymeleaf、shiro-spring依赖

shiro-spring的依赖

<!--
	Subject 用户
    SecurityManager 管理所有用户
    Realm   连接数据	
-->

<!--导入shiro整合spring的包-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.9.0</version>
</dependency>

UserRealm.java

package com.kuang.config;


import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

//自定义的 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("执行了=>认证doGetAuthenticationInfo");
        return null;
    }
}

ShiroConfig.java

package com.kuang.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
public class ShiroConfig {

    //ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        return bean;
    }


    //DefaultWebSecurityManager
    @Bean(name="securityManager")//可以指定bean的名称
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //关联 realm
        securityManager.setRealm(userRealm);

        return securityManager;
    }


    //创建 realm 对象,需要自定义类
    @Bean
    public UserRealm userRealm(){//被spring接管
        return new UserRealm();
    }



}

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>add</h1>

</body>
</html>

update.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>update</h1>

</body>
</html>

MyController.java

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.xml.ws.RespectBinding;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro!");
        return "index";
    }


    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

}

index.html

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

<h1>首页</h1>
<p th:text="${msg}"></p>

<hr>
<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>

</body>
</html>

在这里插入图片描述

Shiro实现登录拦截

ShiroConfig.java

package com.kuang.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

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

        filterMap.put("/user/add","authc");
        filterMap.put("/user/update","authc");

        bean.setFilterChainDefinitionMap(filterMap);

        //如果没有权限,一般会返回登录页面
        // shiro不像SpringSecurity那样有默认的登录页面,所以我们要自己新建一个登录页面
        bean.setLoginUrl("/toLogin");


        return bean;
    }


    //DefaultWebSecurityManager
    @Bean(name="securityManager")//可以指定bean的名称
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //关联 realm
        securityManager.setRealm(userRealm);

        return securityManager;
    }


    //创建 realm 对象,需要自定义类
    @Bean
    public UserRealm userRealm(){//被spring接管
        return new UserRealm();
    }



}

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>登录</h1>
<hr>
<form action="">
  <p>用户名:<input type="text" name="username"></p>
  <p>密码:<input type="password" name="password"></p>
  <p><input type="submit"></p>
</form>


</body>
</html>

MyController.java

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.xml.ws.RespectBinding;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro!");
        return "index";
    }


    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

}

Shiro实现用户认证

login.html

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

<h1>登录</h1>
<p th:text="${msg}" style="color: red"></p>
<hr>
<form th:action="@{/login}">
  <p>用户名:<input type="text" name="username"></p>
  <p>密码:<input type="password" name="password"></p>
  <p><input type="submit"></p>
</form>


</body>
</html>

MyController.java

package com.kuang.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.xml.ws.RespectBinding;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro!");
        return "index";
    }


    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前的用户
        Subject subject = SecurityUtils.getSubject();

        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);


        try {

            //登录这个令牌
            subject.login(token);//执行登录的方法,如果没有异常就说明OK了
            return "index";

        } catch (UnknownAccountException e) {//异常:用户名不存在
            model.addAttribute("msg","用户名错误!");
            return "login";//返回到登录页面
        } catch (IncorrectCredentialsException e){//异常:密码不存在
            model.addAttribute("msg","密码错误!");
            return "login";

        }


    }

}

UserRealm.java

package com.kuang.config;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;


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

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

        //用户名,密码   正常是数据库中取,我们现在自己伪造一下数据
        String name = "root";
        String password = "123456";

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        if(!userToken.getUsername().equals(name)){//用户名认证
            return null;//抛出异常 UnknownAccountException
        }

        //密码认证,shiro会自动帮你做
        return new SimpleAuthenticationInfo("",password,"");
        //SimpleAuthenticationInfo 是 接口AuthenticationInfo 的实现类
    }
}

Shiro整合MyBatis

新增依赖

<dependency>
    <groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.22</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

application.yaml

spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就ok了,serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    # com.mysql.jdbc.Driver 这个已经过时
    driver-class-name: com.mysql.jdbc.Driver
    # 可以配置自己想使用的数据源,指定了之后,SpringBoot默认的数据源就不会用了
    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

application.properties

mybatis.type-aliases-package=com.kuang.pojo
mybatis.mapper-locations=classpath:mapper/*.xml

User.java

package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

UserMapper.java

package com.kuang.mapper;


import com.kuang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface UserMapper {

    public User queryUserByName(String name);

}

UserMapper.xml

<?xml version="1.0" encoding="utf8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.UserMapper">

    <select id="queryUserByName" parameterType="String" resultType="User">
        select * from mybatis.user where name = #{name};
    </select>


</mapper>

UserService.java

package com.kuang.service;

import com.kuang.pojo.User;

public interface UserService {

    public User queryUserByName(String name);

}

UserServiceImpl.java

package com.kuang.service;

import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    UserMapper userMapper;

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

UserRealm.java

package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;


//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userServiceImpl;


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

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

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //userToken.getUsername() 从token中拿到用户名,并且从数据库中查找是否存在该用户名的用户
        User user = userServiceImpl.queryUserByName(userToken.getUsername());

        if(user==null){//没有这个人

            return null;//UnknownAccountException 会报出这个异常

        }

        //密码认证,shiro会自动帮你做
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
        //SimpleAuthenticationInfo 是 接口AuthenticationInfo 的实现类
    }
}

Shiro实现请求授权

UserRealm.java

package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;


//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userServiceImpl;


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

        //给用户授予权限 new SimpleAuthorizationInfo()
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();


        //拿到当前登录的这一个对象
        Subject subject = SecurityUtils.getSubject();

        //可以从下面的登录认证返回的值的代码中的user中拿到user
        User currentUser = (User) subject.getPrincipal();//这样就拿到user对象了
        //设置当前用户的权限,这个权限是数据库中用户对应的权限
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

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

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //userToken.getUsername() 从token中拿到用户名,并且从数据库中查找是否存在该用户名的用户
        User user = userServiceImpl.queryUserByName(userToken.getUsername());

        if(user==null){//没有这个人

            return null;//UnknownAccountException 会报出这个异常

        }

        //密码认证,shiro会自动帮你做
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
        //SimpleAuthenticationInfo 是 接口AuthenticationInfo 的实现类
    }
}

ShiroConfig.java

package com.kuang.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

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

        //授权,正常情况下,未授权会跳转到未授权的页面
        filterMap.put("/user/add","perms[user:add]");//这个包含了必须认证后才能访问
        filterMap.put("/user/update","perms[user:update]");//这个包含了必须认证后才能访问

        //注意map放值的时候,如果key相同,则会覆盖key所对应的value

        // filterMap.put("/user/add","authc");
        // filterMap.put("/user/update","authc");
        // filterMap.put("/user/*","authc");



        bean.setFilterChainDefinitionMap(filterMap);


        //如果没有权限,一般会返回登录页面
        // shiro不像SpringSecurity那样有默认的登录页面,所以我们要自己新建一个登录页面
        bean.setLoginUrl("/toLogin");

        //设置未授权的页面
        bean.setUnauthorizedUrl("/noauth");

        return bean;
    }


    //DefaultWebSecurityManager
    @Bean(name="securityManager")//可以指定bean的名称
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //关联 realm
        securityManager.setRealm(userRealm);

        return securityManager;
    }


    //创建 realm 对象,需要自定义类
    @Bean
    public UserRealm userRealm(){//被spring接管
        return new UserRealm();
    }



}

MyController.java

package com.kuang.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.xml.ws.RespectBinding;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro!");
        return "index";
    }


    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前的用户
        Subject subject = SecurityUtils.getSubject();

        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);


        try {

            //登录这个令牌
            subject.login(token);//执行登录的方法,如果没有异常就说明OK了
            return "index";

        } catch (UnknownAccountException e) {//异常:用户名不存在
            model.addAttribute("msg","用户名错误!");
            return "login";//返回到登录页面
        } catch (IncorrectCredentialsException e){//异常:密码不存在
            model.addAttribute("msg","密码错误!");
            return "login";

        }


    }

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

}

User.java(数据库新增了perms字段,用来存储用户对应的权限)

package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

在这里插入图片描述

Shiro整合Thymeleaf

导入shiro-thymeleaf依赖

<!--shiro-thymeleaf整合-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

ShiroConfig.java

package com.kuang.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

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

        //授权,正常情况下,未授权会跳转到未授权的页面
        filterMap.put("/user/add","perms[user:add]");//这个包含了必须认证后才能访问
        filterMap.put("/user/update","perms[user:update]");//这个包含了必须认证后才能访问

        //注意map放值的时候,如果key相同,则会覆盖key所对应的value

        // filterMap.put("/user/add","authc");
        // filterMap.put("/user/update","authc");
        // filterMap.put("/user/*","authc");



        bean.setFilterChainDefinitionMap(filterMap);


        //如果没有权限,一般会返回登录页面
        // shiro不像SpringSecurity那样有默认的登录页面,所以我们要自己新建一个登录页面
        bean.setLoginUrl("/toLogin");

        //设置未授权的页面
        bean.setUnauthorizedUrl("/noauth");

        return bean;
    }


    //DefaultWebSecurityManager
    @Bean(name="securityManager")//可以指定bean的名称
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //关联 realm
        securityManager.setRealm(userRealm);

        return securityManager;
    }


    //创建 realm 对象,需要自定义类
    @Bean
    public UserRealm userRealm(){//被spring接管
        return new UserRealm();
    }


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

}

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>首页</h1>
<!--从session中判断值,如果用户未登录则显示登录按钮-->
<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登录</a>
</div>

<p th:text="${msg}"></p>

<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>

UserRealm.java

package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;


//自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userServiceImpl;


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

        //给用户授予权限 new SimpleAuthorizationInfo()
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();


        //拿到当前登录的这一个对象
        Subject subject = SecurityUtils.getSubject();

        //可以从下面的登录认证返回的值的代码中的user中拿到user
        User currentUser = (User) subject.getPrincipal();//这样就拿到user对象了
        //设置当前用户的权限,这个权限是数据库中用户对应的权限
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

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

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //userToken.getUsername() 从token中拿到用户名,并且从数据库中查找是否存在该用户名的用户
        User user = userServiceImpl.queryUserByName(userToken.getUsername());

        if(user==null){//没有这个人

            return null;//UnknownAccountException 会报出这个异常

        }

        //登录成功存到session
        Subject currentSubject = SecurityUtils.getSubject();
        currentSubject.getSession().setAttribute("loginUser",user);

        //密码认证,shiro会自动帮你做
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
        //SimpleAuthenticationInfo 是 接口AuthenticationInfo 的实现类
    }
}

Swagger

学习目标:

  • 了解Swagger的概念及作用
  • 在SpringBoot中集成Swagger自动生成API文档

Swagger简介

前后端分离时代

  • 前端:前端控制层、视图层

    • 可以伪造后端数据,JSON。即不需要后端,前端工程依旧能跑起来
  • 后端:后端控制层、服务层、数据访问层

  • 前后端通过API接口进行交互

  • 前后端相对独立,且松耦合

  • 前后端甚至可以部署在不同的服务器上

产生的问题

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

解决方案

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

  • 早些年:制定word计划文档

  • 前后端分离时代:

    • 前端测试后端接口:postman
    • 后端提供接口,需要实时更新最新的消息及改动!
    • 这时候swagger应运而生!

swagger

  • swagger号称世界上最流行的API框架

  • Restful Api 文档在线自动生成器,即API文档与API定义同步更新

  • 可以直接运行,在线测试API接口(其实就是controller requsetmapping)

  • 支持多种语言(如:Java,PHP等)

  • 官网:https://swagger.io/

在项目中使用Swagger需要这两个jar包

  • springfox-swagger2
  • springfox-swagger-ui

SpringBoot集成Swagger

新建一个SpringBoot项目,导入springweb依赖

在这里插入图片描述

导入依赖

<!-- 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>

配置Swagger

SwaggerConfig.java

package com.kuang.swagger.config;


import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {
    //什么都没有配置
    //它有默认值
}

测试运行,如果启动报错,可能是版本问题。把spring-boot的版本改为 2.5.7 试一下!

发起请求:http://localhost:8080/swagger-ui.html

在这里插入图片描述

配置Swagger

Swagger的bean实例 Docket

SwaggerConfig.java

package com.kuang.swagger.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {



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

    //配置Swagger信息 apiInfo
    private ApiInfo apiInfo(){

        //作者信息
        Contact DEFAULT_CONTACT = new Contact("秦疆", "https://www.baidu.com", "24736743@qq.com");

        //ApiInfo这个类没有set方法,只能通过构造器
        return new ApiInfo(
                "狂神的SwaggerAPI文档",
                "即使再小的帆也能远航",
                "v1.0",
                "https://www.baidu.com",
                DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList()
        );

    }

}

运行效果

在这里插入图片描述

Swagger配置扫描接口

SwaggerConfig.java

package com.kuang.swagger.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {



    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //RequestHandlerSelectors 配置要扫描接口的方式
                //RequestHandlerSelectors.any() 扫描全部
                //RequestHandlerSelectors.none() 都不扫描
                //RequestHandlerSelectors.withMethodAnnotation() 扫描方法上的注解
                //basePackage() 指定要扫描的包
                .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
                //paths() 过滤什么路径。只扫描什么路径
                .paths(PathSelectors.ant("/kuang/**"))
                .build();
    }

    //配置Swagger信息 apiInfo
    private ApiInfo apiInfo(){

        //作者信息
        Contact DEFAULT_CONTACT = new Contact("秦疆", "https://www.baidu.com", "24736743@qq.com");

        //ApiInfo这个类没有set方法,只能通过构造器
        return new ApiInfo(
                "狂神的SwaggerAPI文档",
                "即使再小的帆也能远航",
                "v1.0",
                "https://www.baidu.com",
                DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList()
        );

    }

}

配置是否启动Swagger

SwaggerConfig.java

package com.kuang.swagger.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {



    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                //enable() 是否启动Swagger,如果为false则不启动,不能在浏览器中访问Swagger
                .enable(false)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
                //.paths(PathSelectors.ant("/kuang/**"))
                .build();
    }

    //配置Swagger信息 apiInfo
    private ApiInfo apiInfo(){

        //作者信息
        Contact DEFAULT_CONTACT = new Contact("秦疆", "https://www.baidu.com", "24736743@qq.com");

        //ApiInfo这个类没有set方法,只能通过构造器
        return new ApiInfo(
                "狂神的SwaggerAPI文档",
                "即使再小的帆也能远航",
                "v1.0",
                "https://www.baidu.com",
                DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList()
        );

    }

}

如果我只希望我的Swagger在生产环境中使用,在发布的时候不使用,怎么办?

思路:

  • 判断是不是生产环境
  • 注入enable()

application.properties

# 激活 application-dev.properties
spring.profiles.active=dev

application-dev.properties

server.port=8081

application-pro.properties

server.port=8082

SwaggerConfig.java

package com.kuang.swagger.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {

    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(Environment environment){

        //设置要显示的Swagger环境
        Profiles profiles = Profiles.of("dev","test");
        //environment.acceptsProfiles() 判断是否处在自己设定的环境当中
        boolean flag = environment.acceptsProfiles(profiles);

        System.out.println(flag);

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(flag) //是否启动Swagger,如果为false则不启动,不能在浏览器中访问Swagger
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
                //.paths(PathSelectors.ant("/kuang/**"))
                .build();
    }

    //配置Swagger信息 apiInfo
    private ApiInfo apiInfo(){

        //作者信息
        Contact DEFAULT_CONTACT = new Contact("秦疆", "https://www.baidu.com", "24736743@qq.com");

        //ApiInfo这个类没有set方法,只能通过构造器
        return new ApiInfo(
                "狂神的SwaggerAPI文档",
                "即使再小的帆也能远航",
                "v1.0",
                "https://www.baidu.com",
                DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList()
        );

    }

}

配置API文档的分组

groupName() 配置分组

SwaggerConfig.java

package com.kuang.swagger.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {

    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(Environment environment){

        //设置要显示的Swagger环境
        Profiles profiles = Profiles.of("dev","test");
        //environment.acceptsProfiles() 判断是否处在自己设定的环境当中
        boolean flag = environment.acceptsProfiles(profiles);

        System.out.println(flag);

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
            
                .groupName("狂神")//配置分组
            
                .enable(flag) //是否启动Swagger,如果为false则不启动,不能在浏览器中访问Swagger
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
                //.paths(PathSelectors.ant("/kuang/**"))
                .build();
    }

    //配置Swagger信息 apiInfo
    private ApiInfo apiInfo(){

        //作者信息
        Contact DEFAULT_CONTACT = new Contact("秦疆", "https://www.baidu.com", "24736743@qq.com");

        //ApiInfo这个类没有set方法,只能通过构造器
        return new ApiInfo(
                "狂神的SwaggerAPI文档",
                "即使再小的帆也能远航",
                "v1.0",
                "https://www.baidu.com",
                DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList()
        );

    }

}

如何配置多个分组?

SwaggerConfig.java

package com.kuang.swagger.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {

    @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");
    }


    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(Environment environment){

        //设置要显示的Swagger环境
        Profiles profiles = Profiles.of("dev","test");
        //environment.acceptsProfiles() 判断是否处在自己设定的环境当中
        boolean flag = environment.acceptsProfiles(profiles);

        System.out.println(flag);

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())

                .groupName("狂神")//配置分组

                .enable(flag) //是否启动Swagger,如果为false则不启动,不能在浏览器中访问Swagger
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
                //.paths(PathSelectors.ant("/kuang/**"))
                .build();
    }

    //配置Swagger信息 apiInfo
    private ApiInfo apiInfo(){

        //作者信息
        Contact DEFAULT_CONTACT = new Contact("秦疆", "https://www.baidu.com", "24736743@qq.com");

        //ApiInfo这个类没有set方法,只能通过构造器
        return new ApiInfo(
                "狂神的SwaggerAPI文档",
                "即使再小的帆也能远航",
                "v1.0",
                "https://www.baidu.com",
                DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList()
        );

    }

}

在这里插入图片描述

Swagger实体类配置

Swagger实体类配置:说白了就是给API文档添加注释,方便更好地看懂API文档

User.java

package com.kuang.swagger.pojo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

//Api("注释") 和 @ApiModel("注释") 是一样的
@ApiModel("用户实体类") //给实体类加一个文档注释
public class User {

    @ApiModelProperty("用户名") //这个也是加注释
    public String username;
    @ApiModelProperty("密码")
    public String password;

}

HelloController.java

package com.kuang.swagger.controller;

import com.kuang.swagger.pojo.User;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello!";
    }

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


    @GetMapping("/hello2")
    //@ApiOperation() 给接口(controller相当于前端和后端之间的接口)加注释
    @ApiOperation("Hello控制类")
    public String hello2(@ApiParam("用户名") String username){
        return "hello"+username;
    }

}

运行效果

在这里插入图片描述

总结

  • 我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
  • 接口文档实时更新
  • 可以在线测试
  • 前端人员或者后端人员能够做到及时协商,尽早解决问题!
  • Swagger是一个优秀的工具,几乎所有大公司都有使用它

注意点:

  • 在正式发布的时候,关闭Swagger。不能让用户看见我们的接口。同时能节省运行内存

任务

异步任务

定时任务

邮件发送(这也是一个任务)

异步任务

新建一个SpringBoot项目,添加springweb依赖

AsyncService.java

package com.kuang.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {

    @Async //告诉Spring这是一个异步方法
    public void hello(){

        try {
            //休眠3秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("数据正在处理...");

    }

}

AsyncController.java

package com.kuang.controller;

import com.kuang.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsyncController {

    @Autowired
    AsyncService asyncService;

    @RequestMapping("/hello")
    public String hello(){
        asyncService.hello();//停止三秒,停止的时候网站会等待
        return "OK";
    }


}

Springboot09TestApplication.java

package com.kuang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;


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

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

}

邮件任务

导入依赖

<!--javax.mail-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
swootxnbmhunbjfj

application.properties

spring.mail.username=398156587@qq.com
# 我的授权码
spring.mail.password=swootxnbmhunbjfj
# 发送的服务器
spring.mail.host=smtp.qq.com
# 开启加密验证,这是qq邮箱需要的
spring.mail.properties.mail.smtp.ssl.enable=true

Springboot09TestApplication.java

package com.kuang;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;


import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

@SpringBootTest
class Springboot09TestApplicationTests {

    @Resource
    JavaMailSenderImpl mailSender;

    @Test
    void contextLoads() {

        //发送一封简单邮件
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();

        simpleMailMessage.setSubject("小狂神你好呀");//邮件的主题
        simpleMailMessage.setText("谢谢你的狂神说JAVA系列课程");//邮件的内容
        simpleMailMessage.setTo("qq398156587@163.com");//发送给谁
        simpleMailMessage.setFrom("398156587@qq.com");//从什么邮箱发的


        mailSender.send(simpleMailMessage);

    }

    @Test
    void contextLoads2() throws MessagingException {

        //发送一封复杂邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();

        //组装
        MimeMessageHelper help = new MimeMessageHelper(mimeMessage,true);

        //正文
        help.setSubject("小狂神你好呀~plus");
        help.setText("<p style='color:red'>谢谢你的狂神说JAVA系列课程</p>",true);//true,开启支持解析html

        //附件
        help.addAttachment("39.jpg",new File("C:\\Users\\CaoGuoWei\\Desktop\\Temp\\新建文件夹\\39.jpg"));

        help.setTo("qq398156587@163.com");//发送给谁
        help.setFrom("398156587@qq.com");//从什么邮箱发的

        mailSender.send(mimeMessage);

    }

}

定时任务

我们会用到两个核心的接口类

  • TaskScheduler 任务调度程序
  • TaskExecutor 任务执行者

Springboot09TestApplication.java

package com.kuang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;


@EnableAsync //开启异步功能的注解
@EnableScheduling //开启定时功能的注解
@SpringBootApplication
public class Springboot09TestApplication {

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

}

ScheduledService.java

package com.kuang.service;


import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ScheduledService {

    //在一个特定的时间执行这个方法
    //cron表达式
    //秒 分 时 日 月 星期(0-7:0和7都代表周日;?代表不管星期几)

    //cron = "0 57 19 * * ?" 这个代表每天的 19:57:00 执行
    //cron = "0 0/5 10,18 * * ?" 这个代表每天的10点和18点,每隔5分钟执行一次
    //cron = "0 15 10 ? * 1-6" 这个代表每个月的周一到周六的 10:15 执行

    @Scheduled(cron = "0 57 19 * * ?")
    public void hello(){
        System.out.println("hello,你被执行了");
    }

}

SpringBoot集成Redis

去看狂神的Redis教学视频

自定义RedisTemplate

去看狂神的Redis教学视频

分布式系统理论

分布式理论

  • 在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
  • 分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据

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

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

  • Dubbo(开源分布式服务框架)

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

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

在这里插入图片描述

单一应用架构

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

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

  • 缺点

    • 性能扩展比较难
    • 协同开发问题
    • 不利于升级维护

在这里插入图片描述

垂直应用架构

  • 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
  • 通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性
  • 缺点
    • 公用模块无法重复利用,开发性的浪费

在这里插入图片描述

分布式服务架构

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

在这里插入图片描述

流动计算架构

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

在这里插入图片描述

RPC

什么是RPC

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

也就是说有两台服务器A、B。一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

为什么要用RPC呢

就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调用远程函数;

说白了就是不同于调用本地的而是调用远程的资源和方法

RPC原理
在这里插入图片描述

基本步骤

在这里插入图片描述

RPC的两个核心模块:通讯、序列化

序列化:数据传输需要转换

这时候我们就需要用到Dubbo,通过它来简化我们的操作

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

Dubbo

什么是Dubbo

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

官网:Apache Dubbo

在这里插入图片描述

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

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

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

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

调用关系说明

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给服务消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从服务提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

Zookeeper是什么?

官方文档上这么解释zookeeper,它是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。

Zookeeper 相当于上图的 Registry(注册中心)

上面的解释有点抽象,简单来说 zookeeper = 文件系统 + 监听通知机制

文件系统:Zookeeper维护一个类似文件系统的数据结构

在这里插入图片描述

每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。

有四种类型的znode:

  • PERSISTENT-持久化目录节点
    客户端与zookeeper断开连接后,该节点依旧存在

  • PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
    客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号

  • EPHEMERAL-临时目录节点
    客户端与zookeeper断开连接后,该节点被删除

  • EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
    客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

监听通知机制

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。

就这么简单,下面我们看看Zookeeper能做点什么呢?

zookeeper功能非常强大,可以实现诸如分布式应用配置管理、统一命名服务、状态同步服务、集群管理等功能,我们这里拿比较简单的分布式应用配置管理为例来说明。

假设我们的程序是分布式部署在多台机器上,如果我们要改变程序的配置文件,需要逐台机器去修改,非常麻烦,现在把这些配置全部放到zookeeper上去,保存在 zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 zookeeper 的通知,然后从 zookeeper 获取新的配置信息应用到系统中。
在这里插入图片描述

window下载安装zookeeper

Index of /dist/zookeeper/zookeeper-3.4.14 (apache.org)

选择这个下载:zookeeper-3.4.14.tar.gz

运行 /bin/zkServer.cmd

初次运行会遇到闪退问题,会报错,原因是没有 zoo.cfg 配置文件

解决办法:在conf目录下拷贝一份zoo_sample.cfg,改名为zoo.cfg即可

在这里插入图片描述

我们查看一下zoo.cfg

在这里插入图片描述

先运行运行 zkServer.cmd 再运行 zkCli.cmd 测试一下

zkCli.cmd

在这里插入图片描述

window下载安装dubbo-admin

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

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

1、下载dubbo-admin

地址 :https://github.com/apache/dubbo-admin/tree/master

在这里插入图片描述

2、解压进入目录

查看 dubbo-admin\src\main\resources\application.properties 指定zookeeper地址

在这里插入图片描述

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

管理员身份进入cmd

mvn clean package -Dmaven.test.skip=true

在这里插入图片描述

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

在这里插入图片描述

打包完成后,在 dubbo-admin\target 目录下有一个jar包,可以通过cmd执行,必须先保证zookeeper的服务先打开!

在这里插入图片描述

我们先打开 zkServer.cmd

然后使用cmd运行jar包

java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

在这里插入图片描述

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

登录成功后,查看界面

在这里插入图片描述

dubbo-admin(这个可以不要):是一个监控管理后台,它可以查看我们注册了哪些服务,哪些服务被消费了

zookeeper(必须要):是一个注册中心

Dubbo(必须要):是一个jar包

整合Dubbo、Zookeeper、SpringBoot

创建一个空项目,配置好项目结构

在这里插入图片描述

新建一个Module,选择SpringBoot,添加springweb依赖。命名为 provider-server (相当于服务提供者)

TicketService.java

package com.kuang.service;

public interface TicketService {

    public String getTicket();

}

TicketServiceImpl.java

package com.kuang.service;


import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;

//注意!!!这个@Service是dubbo中的,而不是spring中的
@Service//加了这个注解,就可以被扫描到,在项目一启动的时候就自动注册到注册中心,我们这里用的注册中心是zookeeper
@Component//使用了Dubbo后尽量不要用spring的那个@Service注解了,我们用万能的@Component
public class TicketServiceImpl implements TicketService{
    @Override
    public String getTicket() {
        return "《狂神说Java》";
    }
}

application.properties

server.port=8001

# 服务应用的名字
dubbo.application.name=provider-server
# 注册中心的地址,127.0.0.1 相当于 localhost
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 哪些服务要被注册
dubbo.scan.base-packages=com.kuang.service

添加依赖

<!--导入依赖:Dubbo + zookeeper-->
<!-- Dubbo Spring Boot Starter -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.9</version>
</dependency>
<dependency>
    <groupId>com.github.sgroschupf</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.1</version>
</dependency>
<!--zookeeper的日志可能会和SpringBoot的日志有冲突,我们要排除日志冲突-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
    <!--排除这个slf4j-log4j12-->
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

运行测试一下:

  • 先启动 zkServer.cmd

  • 再去cmd中启动 dubbo-admin-0.0.1-SNAPSHOT.jar

  • 最后启动 module中的主程序入口 ProviderServerApplication

  • 运行效果如下,说明服务已经注册到注册中心了

    在这里插入图片描述

    在这里插入图片描述

新建一个Module,选择SpringBoot,添加springweb依赖。命名为 consumer-server (相当于服务消费者)

UserService.java

package com.kuang.service;

import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;


@Service //注意:这个是spring中的@Service,作用就是放到spring容器中
public class UserService {

    //想拿到provider-server提供的票,要去注册中心拿到服务
    @Reference //引用。有两种办法:真实开发会通过Pom坐标引用;我们自己也可以定义路径相同的接口名简单测试一下
    TicketService ticketService;

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


}

TicketService.java

package com.kuang.service;

public interface TicketService {

    public String getTicket();

}

application.properties

server.port=8002

# 消费者去注册中心拿服务的时候需要暴露自己的名字
dubbo.application.name=consumer-server
# 注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

添加依赖,和上面导的依赖一模一样

<!--导入依赖:Dubbo + zookeeper-->
<!-- Dubbo Spring Boot Starter -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.9</version>
</dependency>
<dependency>
    <groupId>com.github.sgroschupf</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.1</version>
</dependency>
<!--zookeeper的日志可能会和SpringBoot的日志有冲突,我们要排除日志冲突-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
    <!--排除这个slf4j-log4j12-->
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

ConsumerServerApplicationTests.java

package com.kuang;

import com.kuang.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ConsumerServerApplicationTests {

    @Autowired
    UserService userService;

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

}

运行测试一下:

  • 先启动 zkServer.cmd

  • 再去cmd中启动 dubbo-admin-0.0.1-SNAPSHOT.jar

  • 接着启动 module 中的 provider-server(服务提供者)的主程序入口 ProviderServerApplication;确保服务注册到注册中心

  • 上面三个步骤是上一章节的步骤

  • 最后我们测试一下,启动 module 中的 consumer-server (相当于服务消费者)的测试类 ConsumerServerApplicationTests;看一下运行效果!

    在这里插入图片描述

总结:提供者提供服务,消费者消费服务!!!

聊聊现在和未来

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值